blob: 0187e56f4dfa3075fba6dc59c6e904e1067b04be [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cryptohome/fingerprint_manager.h"
#include <utility>
#include <vector>
#include <base/test/bind_test_util.h>
#include <biod/biod_proxy/mock_biometrics_manager_proxy_base.h>
#include <chromeos/dbus/service_constants.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace cryptohome {
// Peer class for testing FingerprintManager.
class FingerprintManagerPeer {
public:
explicit FingerprintManagerPeer(FingerprintManager* fingerprint_manager) {
fingerprint_manager_ = fingerprint_manager;
}
// FingerprintManager won't allow any operation unless
// |connected_to_auth_scan_done_signal_| is true, so set that for testing.
void SetConnectedToAuthScanDoneSignal(bool success) {
fingerprint_manager_->connected_to_auth_scan_done_signal_ = success;
}
void SignalAuthScanDone(dbus::Signal* signal) {
fingerprint_manager_->OnAuthScanDone(signal);
}
bool NoAuthSession() {
return fingerprint_manager_->state_ ==
FingerprintManager::State::NO_AUTH_SESSION;
}
bool AuthSessionIsOpen() {
return fingerprint_manager_->state_ ==
FingerprintManager::State::AUTH_SESSION_OPEN;
}
bool AuthSessionIsLocked() {
return fingerprint_manager_->state_ ==
FingerprintManager::State::AUTH_SESSION_LOCKED;
}
private:
FingerprintManager* fingerprint_manager_;
};
namespace {
using testing::_;
using testing::NiceMock;
using testing::Return;
constexpr char kUser[] = "user";
class FingerprintManagerTest : public testing::Test {
public:
FingerprintManagerTest() {
fingerprint_manager_ = std::make_unique<FingerprintManager>();
fingerprint_manager_->SetProxy(&mock_biod_proxy_);
fingerprint_manager_peer_ =
std::make_unique<FingerprintManagerPeer>(fingerprint_manager_.get());
// Mark |connected_to_auth_scan_done_signal_| to true to allow operations.
fingerprint_manager_peer_->SetConnectedToAuthScanDoneSignal(true);
}
void AddMatchToScanResult(dbus::MessageWriter* matches_writer,
const std::string& user) {
dbus::MessageWriter entry_writer(nullptr);
matches_writer->OpenDictEntry(&entry_writer);
entry_writer.AppendString(user);
// A dumb fingerprint record path is sufficient.
entry_writer.AppendArrayOfObjectPaths(std::vector<dbus::ObjectPath>());
matches_writer->CloseContainer(&entry_writer);
}
std::unique_ptr<FingerprintManager> fingerprint_manager_;
std::unique_ptr<FingerprintManagerPeer> fingerprint_manager_peer_;
NiceMock<biod::MockBiometricsManagerProxyBase> mock_biod_proxy_;
bool status_;
FingerprintScanStatus scan_status_;
};
TEST_F(FingerprintManagerTest, StartAuthSessionFail) {
EXPECT_CALL(mock_biod_proxy_, StartAuthSessionAsync(_))
.WillOnce([](base::Callback<void(bool success)> callback) {
std::move(callback).Run(false);
});
status_ = true;
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_FALSE(status_);
EXPECT_TRUE(fingerprint_manager_->GetCurrentUser().empty());
EXPECT_TRUE(fingerprint_manager_peer_->NoAuthSession());
}
TEST_F(FingerprintManagerTest, StartAuthSessionSuccess) {
EXPECT_CALL(mock_biod_proxy_, StartAuthSessionAsync(_))
.WillOnce([](base::Callback<void(bool success)> callback) {
std::move(callback).Run(true);
});
status_ = false;
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_TRUE(status_);
EXPECT_EQ(fingerprint_manager_->GetCurrentUser(), kUser);
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
// Test that we can close the auth session.
EXPECT_CALL(mock_biod_proxy_, EndAuthSession());
fingerprint_manager_->EndAuthSession();
EXPECT_TRUE(fingerprint_manager_peer_->NoAuthSession());
}
TEST_F(FingerprintManagerTest, StartAuthSessionTwice) {
// First auth session still exists.
EXPECT_CALL(mock_biod_proxy_, StartAuthSessionAsync(_))
.WillOnce([](base::Callback<void(bool success)> callback) {
std::move(callback).Run(true);
});
status_ = false;
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_TRUE(status_);
EXPECT_EQ(fingerprint_manager_->GetCurrentUser(), kUser);
// Second time should fail.
status_ = true;
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_FALSE(status_);
// The existing session is unaffected.
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
}
TEST_F(FingerprintManagerTest, AuthScanDoneNoScanResult) {
EXPECT_CALL(mock_biod_proxy_, StartAuthSessionAsync(_))
.WillOnce([](base::Callback<void(bool success)> callback) {
std::move(callback).Run(true);
});
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
// This signal does not include a ScanResult, so it's invalid.
dbus::Signal signal(biod::kBiometricsManagerInterface,
biod::kBiometricsManagerAuthScanDoneSignal);
fingerprint_manager_->SetAuthScanDoneCallback(base::BindLambdaForTesting(
[this](FingerprintScanStatus status) { scan_status_ = status; }));
scan_status_ = FingerprintScanStatus::SUCCESS;
fingerprint_manager_peer_->SignalAuthScanDone(&signal);
EXPECT_EQ(scan_status_, FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED);
// Unrecoverable error should lock the auth session.
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsLocked());
}
TEST_F(FingerprintManagerTest, AuthScanDoneScanResultFailed) {
EXPECT_CALL(mock_biod_proxy_, StartAuthSessionAsync(_))
.WillOnce([](base::Callback<void(bool success)> callback) {
std::move(callback).Run(true);
});
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
dbus::Signal signal(biod::kBiometricsManagerInterface,
biod::kBiometricsManagerAuthScanDoneSignal);
dbus::MessageWriter writer(&signal);
writer.AppendUint32(
static_cast<uint32_t>(biod::ScanResult::SCAN_RESULT_PARTIAL));
fingerprint_manager_->SetAuthScanDoneCallback(base::BindLambdaForTesting(
[this](FingerprintScanStatus status) { scan_status_ = status; }));
scan_status_ = FingerprintScanStatus::SUCCESS;
fingerprint_manager_peer_->SignalAuthScanDone(&signal);
EXPECT_EQ(scan_status_, FingerprintScanStatus::FAILED_RETRY_ALLOWED);
// Auth session should still be open since retry is allowed.
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
}
TEST_F(FingerprintManagerTest, AuthScanDoneNoMatch) {
EXPECT_CALL(mock_biod_proxy_, StartAuthSessionAsync(_))
.WillOnce([](base::Callback<void(bool success)> callback) {
std::move(callback).Run(true);
});
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
dbus::Signal signal(biod::kBiometricsManagerInterface,
biod::kBiometricsManagerAuthScanDoneSignal);
dbus::MessageWriter writer(&signal);
writer.AppendUint32(
static_cast<uint32_t>(biod::ScanResult::SCAN_RESULT_SUCCESS));
dbus::MessageWriter matches_writer(nullptr);
writer.OpenArray("{sao}", &matches_writer);
// No matches.
writer.CloseContainer(&matches_writer);
fingerprint_manager_->SetAuthScanDoneCallback(base::BindLambdaForTesting(
[this](FingerprintScanStatus status) { scan_status_ = status; }));
scan_status_ = FingerprintScanStatus::SUCCESS;
fingerprint_manager_peer_->SignalAuthScanDone(&signal);
EXPECT_EQ(scan_status_, FingerprintScanStatus::FAILED_RETRY_ALLOWED);
// Auth session should still be open since retry is allowed.
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
}
TEST_F(FingerprintManagerTest, AuthScanDoneSuccess) {
EXPECT_CALL(mock_biod_proxy_, StartAuthSessionAsync(_))
.WillOnce([](base::Callback<void(bool success)> callback) {
std::move(callback).Run(true);
});
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
dbus::Signal signal(biod::kBiometricsManagerInterface,
biod::kBiometricsManagerAuthScanDoneSignal);
dbus::MessageWriter writer(&signal);
writer.AppendUint32(
static_cast<uint32_t>(biod::ScanResult::SCAN_RESULT_SUCCESS));
dbus::MessageWriter matches_writer(nullptr);
writer.OpenArray("{sao}", &matches_writer);
AddMatchToScanResult(&matches_writer, kUser);
writer.CloseContainer(&matches_writer);
fingerprint_manager_->SetAuthScanDoneCallback(base::BindLambdaForTesting(
[this](FingerprintScanStatus status) { scan_status_ = status; }));
scan_status_ = FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED;
fingerprint_manager_peer_->SignalAuthScanDone(&signal);
EXPECT_EQ(scan_status_, FingerprintScanStatus::SUCCESS);
// A successful scan should cause further scans in the same session to be
// ignored.
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsLocked());
}
TEST_F(FingerprintManagerTest, AuthScanDoneTooManyRetries) {
EXPECT_CALL(mock_biod_proxy_, StartAuthSessionAsync(_))
.WillOnce([](base::Callback<void(bool success)> callback) {
std::move(callback).Run(true);
});
fingerprint_manager_->StartAuthSessionAsyncForUser(
kUser,
base::BindLambdaForTesting([this](bool success) { status_ = success; }));
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsOpen());
dbus::Signal signal(biod::kBiometricsManagerInterface,
biod::kBiometricsManagerAuthScanDoneSignal);
dbus::MessageWriter writer(&signal);
writer.AppendUint32(
static_cast<uint32_t>(biod::ScanResult::SCAN_RESULT_SUCCESS));
dbus::MessageWriter matches_writer(nullptr);
writer.OpenArray("{sao}", &matches_writer);
// No matches.
writer.CloseContainer(&matches_writer);
for (int i = 0; i < kMaxFingerprintRetries - 1; i++) {
fingerprint_manager_->SetAuthScanDoneCallback(base::BindLambdaForTesting(
[this](FingerprintScanStatus status) { scan_status_ = status; }));
scan_status_ = FingerprintScanStatus::SUCCESS;
fingerprint_manager_peer_->SignalAuthScanDone(&signal);
EXPECT_EQ(scan_status_, FingerprintScanStatus::FAILED_RETRY_ALLOWED);
}
// The last invalid retry should lock the auth session.
fingerprint_manager_->SetAuthScanDoneCallback(base::BindLambdaForTesting(
[this](FingerprintScanStatus status) { scan_status_ = status; }));
scan_status_ = FingerprintScanStatus::SUCCESS;
fingerprint_manager_peer_->SignalAuthScanDone(&signal);
EXPECT_EQ(scan_status_, FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED);
EXPECT_TRUE(fingerprint_manager_peer_->AuthSessionIsLocked());
// Any further operation is denied in the auth session, regardless of the
// scan result.
scan_status_ = FingerprintScanStatus::SUCCESS;
fingerprint_manager_->SetAuthScanDoneCallback(base::BindLambdaForTesting(
[this](FingerprintScanStatus status) { scan_status_ = status; }));
EXPECT_EQ(scan_status_, FingerprintScanStatus::FAILED_RETRY_NOT_ALLOWED);
}
} // namespace
} // namespace cryptohome