| // 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 <chromeos/dbus/service_constants.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "biod/biod_proxy/mock_biometrics_manager_proxy_base.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 |