| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/trusted_vault/standalone_trusted_vault_backend.h" |
| |
| #include <cstdint> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "components/signin/public/identity_manager/account_info.h" |
| #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" |
| #include "components/trusted_vault/features.h" |
| #include "components/trusted_vault/local_recovery_factor.h" |
| #include "components/trusted_vault/proto/local_trusted_vault.pb.h" |
| #include "components/trusted_vault/proto_string_bytes_conversion.h" |
| #include "components/trusted_vault/securebox.h" |
| #include "components/trusted_vault/standalone_trusted_vault_server_constants.h" |
| #include "components/trusted_vault/standalone_trusted_vault_storage.h" |
| #include "components/trusted_vault/test/fake_file_access.h" |
| #include "components/trusted_vault/test/mock_trusted_vault_throttling_connection.h" |
| #include "components/trusted_vault/trusted_vault_connection.h" |
| #include "components/trusted_vault/trusted_vault_histograms.h" |
| #include "components/trusted_vault/trusted_vault_server_constants.h" |
| #include "components/trusted_vault/trusted_vault_throttling_connection.h" |
| #include "components/trusted_vault/trusted_vault_throttling_connection_impl.h" |
| #include "google_apis/gaia/gaia_id.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace trusted_vault { |
| |
| namespace { |
| |
| using testing::_; |
| using testing::ByMove; |
| using testing::ElementsAre; |
| using testing::Eq; |
| using testing::Ge; |
| using testing::IsEmpty; |
| using testing::IsNull; |
| using testing::Mock; |
| using testing::Ne; |
| using testing::NotNull; |
| using testing::Return; |
| using testing::SaveArg; |
| using testing::SizeIs; |
| |
| MATCHER_P(MatchTrustedVaultKeyAndVersions, expected, "") { |
| const auto* trusted_vault_keys = |
| std::get_if<std::vector<TrustedVaultKeyAndVersion>>(&arg); |
| if (!trusted_vault_keys) { |
| *result_listener << "does not hold a vector of TrustedVaultKeyAndVersion"; |
| return false; |
| } |
| return testing::ExplainMatchResult(*trusted_vault_keys, expected, |
| result_listener); |
| } |
| |
| MATCHER_P(DegradedRecoverabilityStateEq, expected_state, "") { |
| const trusted_vault_pb::LocalTrustedVaultDegradedRecoverabilityState& |
| given_state = arg; |
| return given_state.degraded_recoverability_value() == |
| expected_state.degraded_recoverability_value() && |
| given_state.last_refresh_time_millis_since_unix_epoch() == |
| expected_state.last_refresh_time_millis_since_unix_epoch(); |
| } |
| |
| MATCHER_P(KeyMaterialEq, expected, "") { |
| const std::string& key_material = arg.key_material(); |
| const std::vector<uint8_t> key_material_as_bytes(key_material.begin(), |
| key_material.end()); |
| return key_material_as_bytes == expected; |
| } |
| |
| MATCHER_P2(TrustedVaultKeyAndVersionEq, expected_key, expected_version, "") { |
| const TrustedVaultKeyAndVersion& key_and_version = arg; |
| return key_and_version.key == expected_key && |
| key_and_version.version == expected_version; |
| } |
| |
| MATCHER_P(PublicKeyWhenExportedEq, expected, "") { |
| const SecureBoxPublicKey& actual_public_key = arg; |
| return actual_public_key.ExportToBytes() == expected; |
| } |
| |
| CoreAccountInfo MakeAccountInfoWithGaiaId(const std::string& gaia_id) { |
| CoreAccountInfo account_info; |
| account_info.gaia = GaiaId(gaia_id); |
| return account_info; |
| } |
| |
| class MockDelegate : public StandaloneTrustedVaultBackend::Delegate { |
| public: |
| MockDelegate() = default; |
| ~MockDelegate() override = default; |
| MOCK_METHOD(void, NotifyRecoverabilityDegradedChanged, (), (override)); |
| }; |
| |
| class FakeLocalRecoveryFactor : public LocalRecoveryFactor { |
| public: |
| FakeLocalRecoveryFactor(StandaloneTrustedVaultStorage* storage, |
| TrustedVaultThrottlingConnection* connection, |
| CoreAccountInfo account) |
| : storage_(storage), connection_(connection), account_(account) {} |
| FakeLocalRecoveryFactor(const FakeLocalRecoveryFactor&) = delete; |
| FakeLocalRecoveryFactor& operator=(const FakeLocalRecoveryFactor&) = delete; |
| ~FakeLocalRecoveryFactor() override = default; |
| |
| LocalRecoveryFactorType GetRecoveryFactorType() const override { |
| return LocalRecoveryFactorType::kPhysicalDevice; |
| } |
| |
| void AttemptRecovery(AttemptRecoveryCallback callback) override { |
| CHECK(connection_); |
| CHECK(recovery_callback_.is_null()); |
| attempt_recovery_was_called_ = true; |
| |
| if (!is_registered_) { |
| std::move(callback).Run(RecoveryStatus::kFailure, |
| /*new_vault_keys=*/{}, |
| /*last_vault_key_version=*/0); |
| return; |
| } |
| if (connection_->AreRequestsThrottled(account_)) { |
| std::move(callback).Run(RecoveryStatus::kFailure, |
| /*new_vault_keys=*/{}, |
| /*last_vault_key_version=*/0); |
| return; |
| } |
| |
| recovery_callback_ = std::move(callback); |
| } |
| |
| bool IsRegistered() override { return is_registered_; } |
| |
| void MarkAsNotRegistered() override { is_registered_ = false; } |
| |
| TrustedVaultRecoveryFactorRegistrationStateForUMA MaybeRegister( |
| RegisterCallback callback) override { |
| CHECK(connection_); |
| CHECK(register_callback_.is_null()); |
| maybe_register_was_called_ = true; |
| |
| auto* per_user_vault = storage_->FindUserVault(account_.gaia); |
| CHECK(per_user_vault); |
| |
| if (is_registered_) { |
| return TrustedVaultRecoveryFactorRegistrationStateForUMA:: |
| kAlreadyRegisteredV1; |
| } |
| if (per_user_vault->last_registration_returned_local_data_obsolete()) { |
| return TrustedVaultRecoveryFactorRegistrationStateForUMA:: |
| kLocalKeysAreStale; |
| } |
| if (connection_->AreRequestsThrottled(account_)) { |
| return TrustedVaultRecoveryFactorRegistrationStateForUMA:: |
| kThrottledClientSide; |
| } |
| |
| register_callback_ = base::BindOnce( |
| base::BindLambdaForTesting([this, per_user_vault]( |
| RegisterCallback cb, |
| TrustedVaultRegistrationStatus status, |
| int key_version, bool had_local_keys) { |
| if (status == TrustedVaultRegistrationStatus::kSuccess || |
| status == TrustedVaultRegistrationStatus::kAlreadyRegistered) { |
| is_registered_ = true; |
| } |
| if (status == TrustedVaultRegistrationStatus::kLocalDataObsolete) { |
| per_user_vault->set_last_registration_returned_local_data_obsolete( |
| true); |
| storage_->WriteDataToDisk(); |
| } |
| std::move(cb).Run(status, key_version, had_local_keys); |
| }), |
| std::move(callback)); |
| |
| if (key_pair_exists_) { |
| return TrustedVaultRecoveryFactorRegistrationStateForUMA:: |
| kAttemptingRegistrationWithExistingKeyPair; |
| } else { |
| key_pair_exists_ = true; |
| return TrustedVaultRecoveryFactorRegistrationStateForUMA:: |
| kAttemptingRegistrationWithNewKeyPair; |
| } |
| } |
| |
| bool AttemptRecoveryWasCalled() const { return attempt_recovery_was_called_; } |
| |
| bool MaybeRegisterWasCalled() const { return maybe_register_was_called_; } |
| |
| void ExpectAttemptRecoveryAndRunCallback( |
| RecoveryStatus status, |
| const std::vector<std::vector<uint8_t>>& new_vault_keys, |
| int last_vault_key_version) { |
| ASSERT_FALSE(recovery_callback_.is_null()); |
| std::move(recovery_callback_) |
| .Run(status, new_vault_keys, last_vault_key_version); |
| } |
| |
| void ExpectMaybeRegisterAndRunCallback(TrustedVaultRegistrationStatus status, |
| int key_version, |
| bool had_local_keys) { |
| ASSERT_FALSE(register_callback_.is_null()); |
| std::move(register_callback_).Run(status, key_version, had_local_keys); |
| } |
| |
| void SetStorage(StandaloneTrustedVaultStorage* storage) { |
| storage_ = storage; |
| } |
| |
| void SetConnection(TrustedVaultThrottlingConnection* new_connection) { |
| connection_ = new_connection; |
| } |
| |
| void ResetCallInfo() { |
| recovery_callback_.Reset(); |
| register_callback_.Reset(); |
| attempt_recovery_was_called_ = false; |
| maybe_register_was_called_ = false; |
| } |
| |
| private: |
| raw_ptr<StandaloneTrustedVaultStorage> storage_; |
| raw_ptr<TrustedVaultThrottlingConnection> connection_; |
| const CoreAccountInfo account_; |
| |
| bool is_registered_ = false; |
| bool key_pair_exists_ = false; |
| bool attempt_recovery_was_called_ = false; |
| bool maybe_register_was_called_ = false; |
| AttemptRecoveryCallback recovery_callback_; |
| RegisterCallback register_callback_; |
| }; |
| |
| // The LocalRecoveryFactor created by TestLocalRecoveryFactorsFactory below is |
| // destroyed every time the primary account of StandaloneTrustedVaultBackend is |
| // changed. However, for testing, we want to keep state in |
| // FakeLocalRecoveryFactor across those events. Thus, this class is introduced - |
| // it can repeatedly be re-created while still pointing to the same |
| // FakeLocalRecoveryFactor instance. |
| class ForwardingLocalRecoveryFactor : public LocalRecoveryFactor { |
| public: |
| explicit ForwardingLocalRecoveryFactor(raw_ptr<LocalRecoveryFactor> delegate) |
| : delegate_(delegate) { |
| CHECK(delegate_); |
| } |
| ForwardingLocalRecoveryFactor(const ForwardingLocalRecoveryFactor&) = delete; |
| ForwardingLocalRecoveryFactor& operator=( |
| const ForwardingLocalRecoveryFactor&) = delete; |
| ~ForwardingLocalRecoveryFactor() override = default; |
| |
| LocalRecoveryFactorType GetRecoveryFactorType() const override { |
| return delegate_->GetRecoveryFactorType(); |
| } |
| void AttemptRecovery(AttemptRecoveryCallback callback) override { |
| delegate_->AttemptRecovery(std::move(callback)); |
| } |
| bool IsRegistered() override { return delegate_->IsRegistered(); } |
| void MarkAsNotRegistered() override { delegate_->MarkAsNotRegistered(); } |
| TrustedVaultRecoveryFactorRegistrationStateForUMA MaybeRegister( |
| RegisterCallback callback) override { |
| return delegate_->MaybeRegister(std::move(callback)); |
| } |
| |
| private: |
| raw_ptr<LocalRecoveryFactor> delegate_; |
| }; |
| |
| class TestLocalRecoveryFactorsFactory |
| : public StandaloneTrustedVaultBackend::LocalRecoveryFactorsFactory { |
| public: |
| explicit TestLocalRecoveryFactorsFactory(size_t num_local_recovery_factors) |
| : num_local_recovery_factors_(num_local_recovery_factors) { |
| CHECK(num_local_recovery_factors_ > 0); |
| } |
| TestLocalRecoveryFactorsFactory(const TestLocalRecoveryFactorsFactory&) = |
| delete; |
| TestLocalRecoveryFactorsFactory& operator=( |
| const TestLocalRecoveryFactorsFactory&) = delete; |
| ~TestLocalRecoveryFactorsFactory() override = default; |
| |
| std::vector<std::unique_ptr<LocalRecoveryFactor>> CreateLocalRecoveryFactors( |
| SecurityDomainId security_domain_id, |
| StandaloneTrustedVaultStorage* storage, |
| TrustedVaultThrottlingConnection* connection, |
| const CoreAccountInfo& account) override { |
| std::vector<FakeLocalRecoveryFactor*> fake_recovery_factors = |
| GetOrCreateRecoveryFactors(storage, connection, account); |
| |
| std::vector<std::unique_ptr<LocalRecoveryFactor>> local_recovery_factors; |
| for (auto* fake_recovery_factor : fake_recovery_factors) { |
| fake_recovery_factor->ResetCallInfo(); |
| local_recovery_factors.emplace_back( |
| std::make_unique<ForwardingLocalRecoveryFactor>( |
| fake_recovery_factor)); |
| } |
| return local_recovery_factors; |
| } |
| |
| std::map<std::optional<GaiaId>, |
| std::vector<std::unique_ptr<FakeLocalRecoveryFactor>>> |
| GetRecoveryFactors() { |
| return std::move(recovery_factors_); |
| } |
| |
| void SetRecoveryFactors( |
| StandaloneTrustedVaultStorage* new_storage, |
| TrustedVaultThrottlingConnection* new_connection, |
| std::map<std::optional<GaiaId>, |
| std::vector<std::unique_ptr<FakeLocalRecoveryFactor>>>&& |
| recovery_factors) { |
| recovery_factors_ = std::move(recovery_factors); |
| // Storage and the connection might have changed, make sure to update all |
| // fake recovery factors to point to the new one. |
| // Note: FakeLocalRecoveryFactor does not store any recovery factor related |
| // state in storage, but needs it to access per user information. |
| for (auto& user_recovery_factors : recovery_factors_) { |
| for (auto& recovery_factor : user_recovery_factors.second) { |
| recovery_factor->SetStorage(new_storage); |
| recovery_factor->SetConnection(new_connection); |
| } |
| } |
| } |
| |
| std::vector<FakeLocalRecoveryFactor*> GetOrCreateRecoveryFactors( |
| StandaloneTrustedVaultStorage* storage, |
| TrustedVaultThrottlingConnection* connection, |
| const CoreAccountInfo& account) { |
| if (!recovery_factors_.contains(account.gaia)) { |
| std::vector<std::unique_ptr<FakeLocalRecoveryFactor>> recovery_factors; |
| for (size_t i = 0; i < num_local_recovery_factors_; ++i) { |
| recovery_factors.emplace_back(std::make_unique<FakeLocalRecoveryFactor>( |
| storage, connection, account)); |
| } |
| recovery_factors_.emplace(account.gaia, std::move(recovery_factors)); |
| } |
| |
| std::vector<FakeLocalRecoveryFactor*> ret; |
| for (const auto& it : recovery_factors_[account.gaia]) { |
| ret.push_back(it.get()); |
| } |
| return ret; |
| } |
| |
| private: |
| const size_t num_local_recovery_factors_; |
| std::map<std::optional<GaiaId>, |
| std::vector<std::unique_ptr<FakeLocalRecoveryFactor>>> |
| recovery_factors_; |
| }; |
| |
| class StandaloneTrustedVaultBackendTest : public testing::Test { |
| public: |
| StandaloneTrustedVaultBackendTest() { ResetBackend(); } |
| |
| ~StandaloneTrustedVaultBackendTest() override = default; |
| |
| void ResetBackend() { |
| ResetBackend(std::make_unique< |
| testing::NiceMock<MockTrustedVaultThrottlingConnection>>()); |
| } |
| |
| void ResetBackend( |
| std::unique_ptr<testing::NiceMock<MockTrustedVaultThrottlingConnection>> |
| connection) { |
| auto file_access = std::make_unique<FakeFileAccess>(); |
| if (file_access_) { |
| // We only want to reset the backend, not the underlying faked file. |
| file_access->SetStoredLocalTrustedVault( |
| file_access_->GetStoredLocalTrustedVault()); |
| } |
| file_access_ = file_access.get(); |
| auto storage = |
| StandaloneTrustedVaultStorage::CreateForTesting(std::move(file_access)); |
| |
| auto delegate = std::make_unique<testing::NiceMock<MockDelegate>>(); |
| |
| auto local_recovery_factors_factory = |
| std::make_unique<TestLocalRecoveryFactorsFactory>( |
| num_local_recovery_factors_); |
| if (local_recovery_factors_factory_) { |
| // We only want to reset the backend, not the underlying faked recovery |
| // factors incl. their state. |
| local_recovery_factors_factory->SetRecoveryFactors( |
| storage.get(), connection.get(), |
| local_recovery_factors_factory_->GetRecoveryFactors()); |
| } |
| local_recovery_factors_factory_ = local_recovery_factors_factory.get(); |
| |
| storage_ = storage.get(); |
| connection_ = connection.get(); |
| |
| backend_ = StandaloneTrustedVaultBackend::CreateForTesting( |
| security_domain_id(), std::move(storage), std::move(delegate), |
| std::move(connection), std::move(local_recovery_factors_factory)); |
| backend_->ReadDataFromDisk(); |
| } |
| |
| // Sets the number of local recovery factors to create during tests. |
| // Note: This only takes effect when ResetBackend() is called. |
| void SetNumLocalRecoveryFactors(size_t num_local_recovery_factors) { |
| num_local_recovery_factors_ = num_local_recovery_factors; |
| } |
| |
| StandaloneTrustedVaultStorage* storage() { return storage_; } |
| |
| FakeFileAccess* file_access() { return file_access_; } |
| |
| MockTrustedVaultThrottlingConnection* connection() { return connection_; } |
| |
| std::vector<FakeLocalRecoveryFactor*> GetOrCreateRecoveryFactors( |
| const CoreAccountInfo& account) { |
| return local_recovery_factors_factory_->GetOrCreateRecoveryFactors( |
| storage_, connection_, account); |
| } |
| |
| // Shorthand to get/create the first recovery factor. |
| FakeLocalRecoveryFactor* GetOrCreateRecoveryFactor( |
| const CoreAccountInfo& account) { |
| return GetOrCreateRecoveryFactors(account)[0]; |
| } |
| |
| std::string GetRecoveryFactorTypeForUMA( |
| FakeLocalRecoveryFactor* recovery_factor) { |
| return GetLocalRecoveryFactorNameForUma( |
| recovery_factor->GetRecoveryFactorType()); |
| } |
| |
| StandaloneTrustedVaultBackend* backend() { return backend_.get(); } |
| |
| SecurityDomainId security_domain_id() const { |
| return SecurityDomainId::kChromeSync; |
| } |
| |
| std::string security_domain_name_for_uma() const { |
| return GetSecurityDomainNameForUma(security_domain_id()); |
| } |
| |
| void SetPrimaryAccountWithUnknownAuthError( |
| std::optional<CoreAccountInfo> primary_account) { |
| backend_->SetPrimaryAccount( |
| primary_account, |
| StandaloneTrustedVaultBackend::RefreshTokenErrorState::kUnknown); |
| } |
| |
| // Stores |vault_keys| and mimics successful recovery factor registration |
| // (using the FakeRecoveryFactor). |
| void StoreKeysAndMimicRecoveryFactorRegistration( |
| const std::vector<std::vector<uint8_t>>& vault_keys, |
| int last_vault_key_version, |
| CoreAccountInfo account_info) { |
| DCHECK(!vault_keys.empty()); |
| backend_->StoreKeys(account_info.gaia, vault_keys, last_vault_key_version); |
| |
| // Setting the primary account will trigger recovery factor registration. |
| SetPrimaryAccountWithUnknownAuthError(account_info); |
| |
| // Pretend that the registration completed successfully. |
| for (auto* recovery_factor : GetOrCreateRecoveryFactors(account_info)) { |
| recovery_factor->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kSuccess, last_vault_key_version, |
| true); |
| } |
| |
| // Reset primary account. |
| SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::nullopt); |
| } |
| |
| private: |
| size_t num_local_recovery_factors_ = 1; |
| scoped_refptr<StandaloneTrustedVaultBackend> backend_; |
| raw_ptr<StandaloneTrustedVaultStorage> storage_ = nullptr; |
| raw_ptr<FakeFileAccess> file_access_ = nullptr; |
| raw_ptr<testing::NiceMock<MockTrustedVaultThrottlingConnection>> connection_ = |
| nullptr; |
| raw_ptr<TestLocalRecoveryFactorsFactory> local_recovery_factors_factory_; |
| }; |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldWriteDegradedRecoverabilityState) { |
| SetPrimaryAccountWithUnknownAuthError(MakeAccountInfoWithGaiaId("user")); |
| trusted_vault_pb::LocalTrustedVaultDegradedRecoverabilityState |
| degraded_recoverability_state; |
| degraded_recoverability_state.set_degraded_recoverability_value( |
| trusted_vault_pb::DegradedRecoverabilityValue::kDegraded); |
| degraded_recoverability_state.set_last_refresh_time_millis_since_unix_epoch( |
| 123); |
| backend()->WriteDegradedRecoverabilityState(degraded_recoverability_state); |
| |
| // Read the file from disk. |
| trusted_vault_pb::LocalTrustedVault proto = |
| file_access()->GetStoredLocalTrustedVault(); |
| ASSERT_THAT(proto.user_size(), Eq(1)); |
| EXPECT_THAT(proto.user(0).degraded_recoverability_state(), |
| DegradedRecoverabilityStateEq(degraded_recoverability_state)); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldInvokeGetIsRecoverabilityDegradedCallbackImmediately) { |
| // The TaskEnvironment is needed because this test initializes the handler, |
| // which works with time. |
| base::test::SingleThreadTaskEnvironment environment{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| EXPECT_CALL(*connection(), DownloadIsRecoverabilityDegraded) |
| .WillOnce([](const CoreAccountInfo&, |
| TrustedVaultConnection::IsRecoverabilityDegradedCallback |
| callback) { |
| std::move(callback).Run(TrustedVaultRecoverabilityStatus::kDegraded); |
| return std::make_unique<TrustedVaultConnection::Request>(); |
| }); |
| base::MockCallback<base::OnceCallback<void(bool)>> cb; |
| // The callback should be invoked because GetIsRecoverabilityDegraded() is |
| // called with the current primary account. |
| EXPECT_CALL(cb, Run(true)); |
| backend()->GetIsRecoverabilityDegraded(kAccountInfo, cb.Get()); |
| environment.FastForwardBy(base::Milliseconds(1)); |
| } |
| |
| TEST_F( |
| StandaloneTrustedVaultBackendTest, |
| ShouldDeferGetIsRecoverabilityDegradedCallbackUntilSetPrimaryAccountIsInvoked) { |
| // TODO(crbug.com/40255601): looks like this test verifies scenario not |
| // possible in prod anymore, remove it together with |
| // |pending_get_is_recoverability_degraded_| logic. |
| |
| // The TaskEnvironment is needed because this test initializes the handler, |
| // which works with time. |
| base::test::SingleThreadTaskEnvironment environment{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| SetPrimaryAccountWithUnknownAuthError(MakeAccountInfoWithGaiaId("user1")); |
| |
| base::MockCallback<base::OnceCallback<void(bool)>> cb; |
| // The callback should not be invoked because GetIsRecoverabilityDegraded() |
| // and SetPrimaryAccount() are invoked with different accounts. |
| EXPECT_CALL(cb, Run(_)).Times(0); |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user2"); |
| // This GetIsRecoverabilityDegraded() is corresponding to a late |
| // SetPrimaryAccount(), in this case the callback should be deferred and |
| // invoked when SetPrimaryAccount() is called. |
| backend()->GetIsRecoverabilityDegraded(kAccountInfo, cb.Get()); |
| |
| Mock::VerifyAndClearExpectations(&cb); |
| |
| ON_CALL(*connection(), DownloadIsRecoverabilityDegraded(Eq(kAccountInfo), _)) |
| .WillByDefault([](const CoreAccountInfo&, |
| TrustedVaultConnection::IsRecoverabilityDegradedCallback |
| callback) { |
| std::move(callback).Run(TrustedVaultRecoverabilityStatus::kDegraded); |
| return std::make_unique<TrustedVaultConnection::Request>(); |
| }); |
| |
| // The callback should be invoked on SetPrimaryAccount() since the last |
| // GetIsRecoverabilityDegraded() was called with the same account. |
| EXPECT_CALL(cb, Run(true)); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| environment.FastForwardBy(base::Milliseconds(1)); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldNotInvokeGetIsRecoverabilityDegradedCallback) { |
| // TODO(crbug.com/40255601): looks like this test verifies scenario not |
| // possible in prod anymore, remove it together with |
| // |pending_get_is_recoverability_degraded_| logic. |
| |
| // The TaskEnvironment is needed because this test initializes the handler, |
| // which works with time. |
| base::test::SingleThreadTaskEnvironment environment{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| EXPECT_CALL(*connection(), DownloadIsRecoverabilityDegraded).Times(0); |
| base::MockCallback<base::OnceCallback<void(bool)>> cb; |
| // The callback should not be invoked because GetIsRecoverabilityDegraded() |
| // and SetPrimaryAccount() are invoked with different accounts. |
| EXPECT_CALL(cb, Run(_)).Times(0); |
| backend()->GetIsRecoverabilityDegraded(MakeAccountInfoWithGaiaId("user1"), |
| cb.Get()); |
| |
| SetPrimaryAccountWithUnknownAuthError(MakeAccountInfoWithGaiaId("user2")); |
| environment.FastForwardBy(base::Milliseconds(1)); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldFetchEmptyKeys) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| // Callback should be called immediately. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty())); |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldReadAndFetchNonEmptyKeys) { |
| const CoreAccountInfo kAccountInfo1 = MakeAccountInfoWithGaiaId("user1"); |
| const CoreAccountInfo kAccountInfo2 = MakeAccountInfoWithGaiaId("user2"); |
| |
| const std::vector<uint8_t> kKey1 = {0, 1, 2, 3, 4}; |
| const std::vector<uint8_t> kKey2 = {1, 2, 3, 4}; |
| const std::vector<uint8_t> kKey3 = {2, 3, 4}; |
| |
| trusted_vault_pb::LocalTrustedVault initial_data; |
| trusted_vault_pb::LocalTrustedVaultPerUser* user_data1 = |
| initial_data.add_user(); |
| trusted_vault_pb::LocalTrustedVaultPerUser* user_data2 = |
| initial_data.add_user(); |
| user_data1->set_gaia_id(kAccountInfo1.gaia.ToString()); |
| user_data2->set_gaia_id(kAccountInfo2.gaia.ToString()); |
| user_data1->add_vault_key()->set_key_material(kKey1.data(), kKey1.size()); |
| user_data2->add_vault_key()->set_key_material(kKey2.data(), kKey2.size()); |
| user_data2->add_vault_key()->set_key_material(kKey3.data(), kKey3.size()); |
| |
| file_access()->SetStoredLocalTrustedVault(initial_data); |
| backend()->ReadDataFromDisk(); |
| |
| // Keys should be fetched immediately for both accounts. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey1))); |
| backend()->FetchKeys(kAccountInfo1, fetch_keys_callback.Get()); |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey2, kKey3))); |
| backend()->FetchKeys(kAccountInfo2, fetch_keys_callback.Get()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldFilterOutConstantKey) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user1"); |
| const std::vector<uint8_t> kKey = {1, 2, 3, 4}; |
| |
| trusted_vault_pb::LocalTrustedVault initial_data; |
| trusted_vault_pb::LocalTrustedVaultPerUser* user_data = |
| initial_data.add_user(); |
| user_data->set_gaia_id(kAccountInfo.gaia.ToString()); |
| user_data->add_vault_key()->set_key_material( |
| GetConstantTrustedVaultKey().data(), GetConstantTrustedVaultKey().size()); |
| user_data->add_vault_key()->set_key_material(kKey.data(), kKey.size()); |
| |
| file_access()->SetStoredLocalTrustedVault(initial_data); |
| backend()->ReadDataFromDisk(); |
| |
| // Keys should be fetched immediately, constant key must be filtered out. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey))); |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldStoreKeys) { |
| const GaiaId kGaiaId1("user1"); |
| const GaiaId kGaiaId2("user2"); |
| const std::vector<uint8_t> kKey1 = {0, 1, 2, 3, 4}; |
| const std::vector<uint8_t> kKey2 = {1, 2, 3, 4}; |
| const std::vector<uint8_t> kKey3 = {2, 3, 4}; |
| const std::vector<uint8_t> kKey4 = {3, 4}; |
| |
| backend()->StoreKeys(kGaiaId1, {kKey1}, /*last_key_version=*/7); |
| backend()->StoreKeys(kGaiaId2, {kKey2}, /*last_key_version=*/8); |
| // Keys for |kGaiaId2| overridden, so |kKey2| should be lost. |
| backend()->StoreKeys(kGaiaId2, {kKey3, kKey4}, /*last_key_version=*/9); |
| |
| // Read the content from storage. |
| trusted_vault_pb::LocalTrustedVault proto = |
| file_access()->GetStoredLocalTrustedVault(); |
| ASSERT_THAT(proto.user_size(), Eq(2)); |
| EXPECT_THAT(proto.user(0).vault_key(), ElementsAre(KeyMaterialEq(kKey1))); |
| EXPECT_THAT(proto.user(0).last_vault_key_version(), Eq(7)); |
| EXPECT_THAT(proto.user(1).vault_key(), |
| ElementsAre(KeyMaterialEq(kKey3), KeyMaterialEq(kKey4))); |
| EXPECT_THAT(proto.user(1).last_vault_key_version(), Eq(9)); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldFetchPreviouslyStoredKeys) { |
| const CoreAccountInfo kAccountInfo1 = MakeAccountInfoWithGaiaId("user1"); |
| const CoreAccountInfo kAccountInfo2 = MakeAccountInfoWithGaiaId("user2"); |
| |
| const std::vector<uint8_t> kKey1 = {0, 1, 2, 3, 4}; |
| const std::vector<uint8_t> kKey2 = {1, 2, 3, 4}; |
| const std::vector<uint8_t> kKey3 = {2, 3, 4}; |
| |
| backend()->StoreKeys(kAccountInfo1.gaia, {kKey1}, /*last_key_version=*/0); |
| backend()->StoreKeys(kAccountInfo2.gaia, {kKey2, kKey3}, |
| /*last_key_version=*/1); |
| |
| // Reset the backend, which makes it re-read the data stored above. |
| ResetBackend(); |
| |
| // Keys should be fetched immediately for both accounts. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey1))); |
| backend()->FetchKeys(kAccountInfo1, fetch_keys_callback.Get()); |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey2, kKey3))); |
| backend()->FetchKeys(kAccountInfo2, fetch_keys_callback.Get()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldFetchPreviouslyStoredKeysWithNullConnection) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| |
| const std::vector<uint8_t> kKey = {0, 1, 2, 3, 4}; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kKey}, /*last_key_version=*/0); |
| |
| // Reset the backend without a connection, which makes it re-read the data |
| // stored above. |
| ResetBackend(/*connection=*/nullptr); |
| |
| // Keys should be fetched immediately. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey))); |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldDeleteNonPrimaryAccountKeys) { |
| const CoreAccountInfo kAccountInfo1 = MakeAccountInfoWithGaiaId("user1"); |
| const CoreAccountInfo kAccountInfo2 = MakeAccountInfoWithGaiaId("user2"); |
| |
| const std::vector<uint8_t> kKey1 = {0, 1, 2, 3, 4}; |
| const std::vector<uint8_t> kKey2 = {1, 2, 3, 4}; |
| const std::vector<uint8_t> kKey3 = {2, 3, 4}; |
| |
| backend()->StoreKeys(kAccountInfo1.gaia, {kKey1}, /*last_key_version=*/0); |
| backend()->StoreKeys(kAccountInfo2.gaia, {kKey2, kKey3}, |
| /*last_key_version=*/1); |
| |
| // Make sure that backend handles primary account changes prior |
| // UpdateAccountsInCookieJarInfo() call. |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo1); |
| SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::nullopt); |
| |
| // Keys should be removed immediately if account is not primary and not in |
| // cookie jar. |
| backend()->UpdateAccountsInCookieJarInfo(signin::AccountsInCookieJarInfo()); |
| |
| // Keys should be removed from both in-memory and disk storages. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty())); |
| backend()->FetchKeys(kAccountInfo1, fetch_keys_callback.Get()); |
| |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty())); |
| backend()->FetchKeys(kAccountInfo2, fetch_keys_callback.Get()); |
| |
| // Read the file from storage and verify that keys were removed. |
| trusted_vault_pb::LocalTrustedVault proto = |
| file_access()->GetStoredLocalTrustedVault(); |
| EXPECT_THAT(proto.user_size(), Eq(0)); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldDeferPrimaryAccountKeysDeletion) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user1"); |
| const std::vector<uint8_t> kKey = {0, 1, 2, 3, 4}; |
| backend()->StoreKeys(kAccountInfo.gaia, {kKey}, /*last_key_version=*/0); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // Keys should not be removed immediately. |
| backend()->UpdateAccountsInCookieJarInfo(signin::AccountsInCookieJarInfo()); |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey))); |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| // Reset primary account, keys should be deleted from both in-memory and disk |
| // storage. |
| SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::nullopt); |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty())); |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| // Read the file from storage and verify that keys were removed. |
| trusted_vault_pb::LocalTrustedVault proto = |
| file_access()->GetStoredLocalTrustedVault(); |
| EXPECT_THAT(proto.user_size(), Eq(0)); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldCompletePrimaryAccountKeysDeletionAfterRestart) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user1"); |
| const std::vector<uint8_t> kKey = {0, 1, 2, 3, 4}; |
| backend()->StoreKeys(kAccountInfo.gaia, {kKey}, /*last_key_version=*/0); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // Keys should not be removed immediately. |
| backend()->UpdateAccountsInCookieJarInfo(signin::AccountsInCookieJarInfo()); |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/ElementsAre(kKey))); |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| // Mimic browser restart and reset primary account. Don't use the default |
| // connection, otherwise FetchKeys() below would perform a recovery factor |
| // registration. |
| ResetBackend(/*connection=*/nullptr); |
| SetPrimaryAccountWithUnknownAuthError(/*primary_account=*/std::nullopt); |
| |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty())); |
| backend()->SetPrimaryAccount( |
| kAccountInfo, |
| StandaloneTrustedVaultBackend::RefreshTokenErrorState::kUnknown); |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| // Read the file from storage and verify that keys were removed. |
| trusted_vault_pb::LocalTrustedVault proto = |
| file_access()->GetStoredLocalTrustedVault(); |
| EXPECT_THAT(proto.user_size(), Eq(0)); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldRegisterRecoveryFactors) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kVaultKey}, kLastKeyVersion); |
| |
| // Setting the primary account will trigger recovery factor registration. |
| base::HistogramTester histogram_tester; |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoveryFactorRegistrationState." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/ |
| TrustedVaultRecoveryFactorRegistrationStateForUMA:: |
| kAttemptingRegistrationWithNewKeyPair, |
| /*expected_bucket_count=*/1); |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoveryFactorRegistered." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/false, |
| /*expected_bucket_count=*/1); |
| |
| // Pretend that the registration completed successfully. |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion, true); |
| EXPECT_TRUE(GetOrCreateRecoveryFactor(kAccountInfo)->IsRegistered()); |
| histogram_tester.ExpectUniqueSample( |
| /*name=*/"TrustedVault.RecoveryFactorRegistrationOutcome." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::kSuccess, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldClearDataAndAttemptRecoveryFactorRegistration) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<std::vector<uint8_t>> kInitialVaultKeys = {{1, 2, 3}}; |
| const int kInitialLastKeyVersion = 1; |
| |
| // Mimic fake recovery factor previously registered with some keys. |
| StoreKeysAndMimicRecoveryFactorRegistration( |
| kInitialVaultKeys, kInitialLastKeyVersion, kAccountInfo); |
| |
| // Set primary account to trigger immediate recovery factor registration |
| // attempt upon reset. |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // Clear data for |kAccountInfo|, keys should be removed and registration |
| // attempt should be triggered. |
| // TODO(crbug.com/405381481): Note that the fake recovery factor isn't reset |
| // by ClearLocalDataForAccount() because it doesn't store its state in the |
| // shared storage. Thus, it's reset explicitly here. |
| GetOrCreateRecoveryFactor(kAccountInfo)->MarkAsNotRegistered(); |
| backend()->ClearLocalDataForAccount(kAccountInfo); |
| // Let the registration attempt fail, so the recovery attempt triggered below |
| // returns with "not registered" immediately. |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kLocalDataObsolete, |
| kInitialLastKeyVersion + 1, false); |
| |
| GetOrCreateRecoveryFactor(kAccountInfo)->ResetCallInfo(); |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/IsEmpty())); |
| // Expect a recovery which fails because the fake recovery factor isn't |
| // registered. This doesn't trigger a new registration attempt. |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| EXPECT_TRUE( |
| GetOrCreateRecoveryFactor(kAccountInfo)->AttemptRecoveryWasCalled()); |
| EXPECT_FALSE( |
| GetOrCreateRecoveryFactor(kAccountInfo)->MaybeRegisterWasCalled()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldRetryRecoveryFactorRegistrationWhenAuthErrorResolved) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kVaultKey}, kLastKeyVersion); |
| |
| base::HistogramTester histogram_tester; |
| backend()->SetPrimaryAccount( |
| kAccountInfo, StandaloneTrustedVaultBackend::RefreshTokenErrorState:: |
| kPersistentAuthError); |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kPersistentAccessTokenFetchError, |
| kLastKeyVersion, true); |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoveryFactorRegistrationState." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/ |
| TrustedVaultRecoveryFactorRegistrationStateForUMA:: |
| kAttemptingRegistrationWithNewKeyPair, |
| /*expected_bucket_count=*/1); |
| |
| // When the auth error is resolved, the registration should be retried. |
| base::HistogramTester histogram_tester2; |
| backend()->SetPrimaryAccount( |
| kAccountInfo, StandaloneTrustedVaultBackend::RefreshTokenErrorState:: |
| kNoPersistentAuthErrors); |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion, true); |
| |
| // The second attempt should NOT have logged the histogram, following the |
| // histogram's definition that it should be logged once. |
| histogram_tester2.ExpectTotalCount( |
| "TrustedVault.RecoveryFactorRegistrationState." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*expected_count=*/0); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldTryToRegisterRecoverFactorsEvenIfLocalKeysAreStale) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kVaultKey}, kLastKeyVersion); |
| ASSERT_TRUE(backend()->MarkLocalKeysAsStale(kAccountInfo)); |
| |
| base::HistogramTester histogram_tester; |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| EXPECT_TRUE( |
| GetOrCreateRecoveryFactor(kAccountInfo)->MaybeRegisterWasCalled()); |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoveryFactorRegistrationState." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/ |
| TrustedVaultRecoveryFactorRegistrationStateForUMA:: |
| kAttemptingRegistrationWithNewKeyPair, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldRecordLocalKeysAreStale) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kVaultKey}, kLastKeyVersion); |
| |
| auto* per_user_vault = storage()->FindUserVault(kAccountInfo.gaia); |
| per_user_vault->set_last_registration_returned_local_data_obsolete(true); |
| storage()->WriteDataToDisk(); |
| |
| base::HistogramTester histogram_tester; |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoveryFactorRegistrationState." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/ |
| TrustedVaultRecoveryFactorRegistrationStateForUMA::kLocalKeysAreStale, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F( |
| StandaloneTrustedVaultBackendTest, |
| ShouldRegisterRecoveryFactorsAlthoughPreviousAttemptFailedUponNewStoredKeys) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kInitialKeys = {1, 2, 3}; |
| const int kInitialKeysVersion = 5; |
| const std::vector<uint8_t> kNewKeys = {1, 2, 3, 4}; |
| const int kNewKeysVersion = 6; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kInitialKeys}, kInitialKeysVersion); |
| |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kLocalDataObsolete, |
| kInitialKeysVersion, true); |
| |
| // StoreKeys() should trigger a registration nevertheless. |
| backend()->StoreKeys(kAccountInfo.gaia, {kNewKeys}, kNewKeysVersion); |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kSuccess, kNewKeysVersion, true); |
| EXPECT_TRUE(GetOrCreateRecoveryFactor(kAccountInfo)->IsRegistered()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldThrottleOnFailedRecoveryFactorRegistration) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kVaultKey}, kLastKeyVersion); |
| |
| // Setting the primary account will trigger recovery factor registration. |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // Mimic transient failure. |
| EXPECT_CALL(*connection(), RecordFailedRequestForThrottling); |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kOtherError, 0, true); |
| Mock::VerifyAndClearExpectations(connection()); |
| |
| // Mimic a restart to trigger recovery factor registration attempt. |
| base::HistogramTester histogram_tester; |
| ResetBackend(); |
| EXPECT_CALL(*connection(), AreRequestsThrottled).WillOnce(Return(true)); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| EXPECT_TRUE( |
| GetOrCreateRecoveryFactor(kAccountInfo)->MaybeRegisterWasCalled()); |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoveryFactorRegistrationState." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/ |
| TrustedVaultRecoveryFactorRegistrationStateForUMA::kThrottledClientSide, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldNotThrottleUponAccessTokenFetchingFailure) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kVaultKey}, kLastKeyVersion); |
| |
| // Setting the primary account will trigger recovery factor registration. |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| base::HistogramTester histogram_tester; |
| // Mimic access token fetching failure. The expectation is that the backend |
| // doesn't treat this as a failure for throttling. |
| EXPECT_CALL(*connection(), RecordFailedRequestForThrottling).Times(0); |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kTransientAccessTokenFetchError, 0, |
| true); |
| |
| histogram_tester.ExpectUniqueSample( |
| /*name=*/"TrustedVault.RecoveryFactorRegistrationOutcome." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/ |
| TrustedVaultRecoveryFactorRegistrationOutcomeForUMA:: |
| kTransientAccessTokenFetchError, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldNotThrottleUponNetworkError) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kVaultKey}, kLastKeyVersion); |
| |
| // Setting the primary account will trigger recovery factor registration. |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // Mimic network error. This should not throttle. |
| EXPECT_CALL(*connection(), RecordFailedRequestForThrottling).Times(0); |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kNetworkError, 0, true); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldRegisterAllLocalRecoveryFactors) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| SetNumLocalRecoveryFactors(2); |
| ResetBackend(); |
| |
| backend()->StoreKeys(kAccountInfo.gaia, {kVaultKey}, kLastKeyVersion); |
| |
| // Setting the primary account will trigger recovery factor registration. |
| base::HistogramTester histogram_tester; |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| std::vector<FakeLocalRecoveryFactor*> recovery_factors = |
| GetOrCreateRecoveryFactors(kAccountInfo); |
| ASSERT_THAT(recovery_factors, SizeIs(2)); |
| |
| // Pretend that the registration completed successfully for the first factor. |
| recovery_factors[0]->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion, true); |
| EXPECT_TRUE(recovery_factors[0]->IsRegistered()); |
| histogram_tester.ExpectBucketCount( |
| /*name=*/"TrustedVault.RecoveryFactorRegistrationOutcome." + |
| GetRecoveryFactorTypeForUMA(recovery_factors[0]) + "." + |
| security_domain_name_for_uma(), |
| /*sample=*/TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::kSuccess, |
| /*expected_count=*/1); |
| |
| // Pretend that the registration failed for the second factor. |
| recovery_factors[1]->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kNetworkError, kLastKeyVersion, true); |
| EXPECT_FALSE(recovery_factors[1]->IsRegistered()); |
| histogram_tester.ExpectBucketCount( |
| /*name=*/"TrustedVault.RecoveryFactorRegistrationOutcome." + |
| GetRecoveryFactorTypeForUMA(recovery_factors[1]) + "." + |
| security_domain_name_for_uma(), |
| /*sample=*/ |
| TrustedVaultRecoveryFactorRegistrationOutcomeForUMA::kNetworkError, |
| /*expected_count=*/1); |
| } |
| |
| // Unless keys marked as stale, FetchKeys() should be completed immediately, |
| // without keys download attempt. |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldFetchKeysImmediately) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2, 3}}; |
| const int kLastKeyVersion = 1; |
| |
| // Make keys downloading theoretically possible. |
| StoreKeysAndMimicRecoveryFactorRegistration(kVaultKeys, kLastKeyVersion, |
| kAccountInfo); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| EXPECT_CALL(*connection(), DownloadNewKeys).Times(0); |
| |
| std::vector<std::vector<uint8_t>> fetched_keys; |
| // Callback should be called immediately. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/Eq(kVaultKeys))); |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| } |
| |
| // The server may clean up some stale keys eventually, client should clean them |
| // up as well to ensure that the state doesn't diverge. In particular, this may |
| // cause problems with registering authentication factors, since the server will |
| // reject request with stale keys. |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldCleanUpOldKeysWhenDownloadingNew) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kInitialVaultKey = {1, 2, 3}; |
| const int kInitialLastKeyVersion = 1; |
| |
| StoreKeysAndMimicRecoveryFactorRegistration( |
| {kInitialVaultKey}, kInitialLastKeyVersion, kAccountInfo); |
| ASSERT_TRUE(backend()->MarkLocalKeysAsStale(kAccountInfo)); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // FetchKeys() should trigger keys downloading. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| const std::vector<uint8_t> kNewVaultKey = {2, 3, 5}; |
| |
| // Note that |fetch_keys_callback| should not receive kInitialVaultKey. |
| EXPECT_CALL(fetch_keys_callback, Run(ElementsAre(kNewVaultKey))); |
| |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectAttemptRecoveryAndRunCallback( |
| LocalRecoveryFactor::RecoveryStatus::kSuccess, {kNewVaultKey}, |
| kInitialLastKeyVersion + 1); |
| } |
| |
| // Regression test for crbug.com/1500258: second FetchKeys() is triggered, while |
| // first is still ongoing (e.g. keys are being downloaded). |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldDownloadKeysAndCompleteConcurrentFetches) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kInitialVaultKey = {1, 2, 3}; |
| const int kInitialLastKeyVersion = 1; |
| |
| StoreKeysAndMimicRecoveryFactorRegistration( |
| {kInitialVaultKey}, kInitialLastKeyVersion, kAccountInfo); |
| ASSERT_TRUE(backend()->MarkLocalKeysAsStale(kAccountInfo)); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // FetchKeys() should trigger keys downloading. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback1; |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback1.Get()); |
| |
| // Mimic second FetchKeys(), note that keys are not downloaded yet and first |
| // fetch is not completed. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback2; |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback2.Get()); |
| |
| // Both fetches should be completed once keys are downloaded. |
| std::vector<uint8_t> kNewVaultKey = {2, 3, 5}; |
| EXPECT_CALL(fetch_keys_callback1, |
| Run(ElementsAre(kInitialVaultKey, kNewVaultKey))); |
| EXPECT_CALL(fetch_keys_callback2, |
| Run(ElementsAre(kInitialVaultKey, kNewVaultKey))); |
| |
| base::HistogramTester histogram_tester; |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectAttemptRecoveryAndRunCallback( |
| LocalRecoveryFactor::RecoveryStatus::kSuccess, |
| {kInitialVaultKey, kNewVaultKey}, kInitialLastKeyVersion + 1); |
| |
| // Recover keys status should be recorded for every fetch. |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoverKeysOutcome." + security_domain_name_for_uma(), |
| /*sample=*/TrustedVaultRecoverKeysOutcomeForUMA::kSuccess, |
| /*expected_bucket_count=*/2); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldDownloadKeysFromFirstRecoveryFactorFirst) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kInitialLastKeyVersion = 1; |
| |
| SetNumLocalRecoveryFactors(2u); |
| ResetBackend(); |
| |
| StoreKeysAndMimicRecoveryFactorRegistration( |
| {GetConstantTrustedVaultKey()}, kInitialLastKeyVersion, kAccountInfo); |
| ASSERT_TRUE(backend()->MarkLocalKeysAsStale(kAccountInfo)); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // FetchKeys() should trigger keys downloading. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| // Fetch should be completed once keys are downloaded. |
| const std::vector<uint8_t> kNewVaultKey = {2, 3, 5}; |
| EXPECT_CALL(fetch_keys_callback, Run(ElementsAre(kVaultKey, kNewVaultKey))); |
| |
| std::vector<FakeLocalRecoveryFactor*> recovery_factors = |
| GetOrCreateRecoveryFactors(kAccountInfo); |
| ASSERT_THAT(recovery_factors, SizeIs(2)); |
| // First recovery factor should have been called to attempt recovery. |
| recovery_factors[0]->ExpectAttemptRecoveryAndRunCallback( |
| LocalRecoveryFactor::RecoveryStatus::kSuccess, {kVaultKey, kNewVaultKey}, |
| kInitialLastKeyVersion + 1); |
| // Second recovery factor should not have been called. |
| EXPECT_FALSE(recovery_factors[1]->AttemptRecoveryWasCalled()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldDownloadKeysFromSecondRecoveryFactorIfFirstFails) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kInitialLastKeyVersion = 1; |
| |
| SetNumLocalRecoveryFactors(2u); |
| ResetBackend(); |
| |
| StoreKeysAndMimicRecoveryFactorRegistration( |
| {GetConstantTrustedVaultKey()}, kInitialLastKeyVersion, kAccountInfo); |
| ASSERT_TRUE(backend()->MarkLocalKeysAsStale(kAccountInfo)); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // FetchKeys() should trigger keys downloading. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| // Fetch should be completed once keys are downloaded. |
| const std::vector<uint8_t> kNewVaultKey = {2, 3, 5}; |
| EXPECT_CALL(fetch_keys_callback, Run(ElementsAre(kVaultKey, kNewVaultKey))); |
| |
| std::vector<FakeLocalRecoveryFactor*> recovery_factors = |
| GetOrCreateRecoveryFactors(kAccountInfo); |
| ASSERT_THAT(recovery_factors, SizeIs(2)); |
| // First recovery factor should have been called to attempt recovery. Let it |
| // fail. |
| recovery_factors[0]->ExpectAttemptRecoveryAndRunCallback( |
| LocalRecoveryFactor::RecoveryStatus::kFailure, /*new_vault_keys=*/{}, |
| /*last_vault_key_version=*/0); |
| // Second recovery factor should have been called. |
| recovery_factors[1]->ExpectAttemptRecoveryAndRunCallback( |
| LocalRecoveryFactor::RecoveryStatus::kSuccess, {kVaultKey, kNewVaultKey}, |
| kInitialLastKeyVersion + 1); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldFailWithLastRecoveryFactorStatus) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const int kInitialLastKeyVersion = 1; |
| |
| SetNumLocalRecoveryFactors(2u); |
| ResetBackend(); |
| |
| StoreKeysAndMimicRecoveryFactorRegistration( |
| {GetConstantTrustedVaultKey()}, kInitialLastKeyVersion, kAccountInfo); |
| ASSERT_TRUE(backend()->MarkLocalKeysAsStale(kAccountInfo)); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // FetchKeys() should trigger keys downloading. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| // Fetch should be completed once last key downloading failed. |
| EXPECT_CALL(fetch_keys_callback, Run(IsEmpty())); |
| |
| base::HistogramTester histogram_tester; |
| std::vector<FakeLocalRecoveryFactor*> recovery_factors = |
| GetOrCreateRecoveryFactors(kAccountInfo); |
| ASSERT_THAT(recovery_factors, SizeIs(2)); |
| // First recovery factor should have been called to attempt recovery. Let it |
| // fail. |
| recovery_factors[0]->ExpectAttemptRecoveryAndRunCallback( |
| LocalRecoveryFactor::RecoveryStatus::kFailure, /*new_vault_keys=*/{}, |
| /*last_vault_key_version=*/0); |
| // Second recovery factor should have been called. Let it also fail. |
| recovery_factors[1]->ExpectAttemptRecoveryAndRunCallback( |
| LocalRecoveryFactor::RecoveryStatus::kFailure, /*new_vault_keys=*/{}, |
| /*last_vault_key_version=*/0); |
| // Status of the last recovery factor should be recorded. |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoverKeysOutcome." + security_domain_name_for_uma(), |
| /*sample=*/TrustedVaultRecoverKeysOutcomeForUMA::kFailure, |
| /*expected_bucket_count=*/1); |
| } |
| |
| // Tests silent recovery factor registration (when no vault keys available yet). |
| // After successful registration, the client should be able to download keys. |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldSilentlyRegisterRecoveryFactorsAndDownloadNewKeys) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const int kServerConstantKeyVersion = 100; |
| |
| // Setting the primary account will trigger recovery factor registration. |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // Pretend that the registration completed successfully. |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectMaybeRegisterAndRunCallback( |
| TrustedVaultRegistrationStatus::kSuccess, kServerConstantKeyVersion, |
| /*had_local_keys=*/false); |
| |
| // Now the fake recovery factor should be registered. |
| EXPECT_TRUE(GetOrCreateRecoveryFactor(kAccountInfo)->IsRegistered()); |
| |
| // FetchKeys() should trigger keys downloading. Note: unlike tests with |
| // following regular key rotation, in this case MarkLocalKeysAsStale() isn't |
| // called intentionally. |
| base::MockCallback<StandaloneTrustedVaultBackend::FetchKeysCallback> |
| fetch_keys_callback; |
| backend()->FetchKeys(kAccountInfo, fetch_keys_callback.Get()); |
| |
| // Mimic successful key downloading, it should make fetch keys attempt |
| // completed. |
| const std::vector<std::vector<uint8_t>> kNewVaultKeys = {{1, 2, 3}}; |
| EXPECT_CALL(fetch_keys_callback, Run(/*keys=*/kNewVaultKeys)); |
| GetOrCreateRecoveryFactor(kAccountInfo) |
| ->ExpectAttemptRecoveryAndRunCallback( |
| LocalRecoveryFactor::RecoveryStatus::kSuccess, kNewVaultKeys, |
| kServerConstantKeyVersion + 1); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldRecordMetricsIfAlreadyRegistered) { |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const std::vector<uint8_t> kVaultKey = {1, 2, 3}; |
| const int kLastKeyVersion = 1; |
| |
| StoreKeysAndMimicRecoveryFactorRegistration({kVaultKey}, kLastKeyVersion, |
| kAccountInfo); |
| |
| // Mimic restart to be able to test histogram recording. |
| ResetBackend(); |
| |
| base::HistogramTester histogram_tester; |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| EXPECT_TRUE( |
| GetOrCreateRecoveryFactor(kAccountInfo)->MaybeRegisterWasCalled()); |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoveryFactorRegistrationState." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/ |
| TrustedVaultRecoveryFactorRegistrationStateForUMA::kAlreadyRegisteredV1, |
| /*expected_bucket_count=*/1); |
| histogram_tester.ExpectUniqueSample( |
| "TrustedVault.RecoveryFactorRegistered." + |
| GetRecoveryFactorTypeForUMA(GetOrCreateRecoveryFactor(kAccountInfo)) + |
| "." + security_domain_name_for_uma(), |
| /*sample=*/true, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, ShouldAddTrustedRecoveryMethod) { |
| const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2}, {1, 2, 3}}; |
| const int kLastKeyVersion = 1; |
| const std::vector<uint8_t> kPublicKey = |
| SecureBoxKeyPair::GenerateRandom()->public_key().ExportToBytes(); |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const int kMethodTypeHint = 7; |
| |
| StoreKeysAndMimicRecoveryFactorRegistration(kVaultKeys, kLastKeyVersion, |
| kAccountInfo); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| TrustedVaultConnection::RegisterAuthenticationFactorCallback |
| registration_callback; |
| EXPECT_CALL( |
| *connection(), |
| RegisterAuthenticationFactor( |
| Eq(kAccountInfo), |
| MatchTrustedVaultKeyAndVersions( |
| GetTrustedVaultKeysWithVersions({kVaultKeys}, kLastKeyVersion)), |
| PublicKeyWhenExportedEq(kPublicKey), |
| Eq(AuthenticationFactorTypeAndRegistrationParams( |
| UnspecifiedAuthenticationFactorType(kMethodTypeHint))), |
| _)) |
| .WillOnce([&](const CoreAccountInfo&, const MemberKeysSource&, |
| const SecureBoxPublicKey& public_key, |
| AuthenticationFactorTypeAndRegistrationParams, |
| TrustedVaultConnection::RegisterAuthenticationFactorCallback |
| callback) { |
| registration_callback = std::move(callback); |
| return std::make_unique<TrustedVaultConnection::Request>(); |
| }); |
| |
| base::MockCallback<base::OnceClosure> completion_callback; |
| backend()->AddTrustedRecoveryMethod(kAccountInfo.gaia, kPublicKey, |
| kMethodTypeHint, |
| completion_callback.Get()); |
| |
| // The operation should be in flight. |
| ASSERT_FALSE(registration_callback.is_null()); |
| |
| // Mimic successful completion of the request. |
| EXPECT_CALL(completion_callback, Run()); |
| std::move(registration_callback) |
| .Run(TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldIgnoreTrustedRecoveryMethodWithInvalidPublicKey) { |
| const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2, 3}}; |
| const int kLastKeyVersion = 0; |
| const std::vector<uint8_t> kInvalidPublicKey = {1, 2, 3, 4}; |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const int kMethodTypeHint = 7; |
| |
| ASSERT_THAT(SecureBoxPublicKey::CreateByImport(kInvalidPublicKey), IsNull()); |
| |
| StoreKeysAndMimicRecoveryFactorRegistration(kVaultKeys, kLastKeyVersion, |
| kAccountInfo); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0); |
| |
| base::MockCallback<base::OnceClosure> completion_callback; |
| EXPECT_CALL(completion_callback, Run()); |
| backend()->AddTrustedRecoveryMethod(kAccountInfo.gaia, kInvalidPublicKey, |
| kMethodTypeHint, |
| completion_callback.Get()); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldDeferTrustedRecoveryMethodUntilPrimaryAccount) { |
| const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2, 3}}; |
| const int kLastKeyVersion = 1; |
| const std::vector<uint8_t> kPublicKey = |
| SecureBoxKeyPair::GenerateRandom()->public_key().ExportToBytes(); |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const int kMethodTypeHint = 7; |
| |
| backend()->StoreKeys(kAccountInfo.gaia, kVaultKeys, kLastKeyVersion); |
| ASSERT_FALSE(backend()->HasPendingTrustedRecoveryMethodForTesting()); |
| |
| // No request should be issued while there is no primary account. |
| base::MockCallback<base::OnceClosure> completion_callback; |
| EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0); |
| backend()->AddTrustedRecoveryMethod(kAccountInfo.gaia, kPublicKey, |
| kMethodTypeHint, |
| completion_callback.Get()); |
| EXPECT_TRUE(backend()->HasPendingTrustedRecoveryMethodForTesting()); |
| |
| // Upon setting a primary account, RegisterAuthenticationFactor() should be |
| // invoked. |
| TrustedVaultConnection::RegisterAuthenticationFactorCallback |
| registration_callback; |
| EXPECT_CALL( |
| *connection(), |
| RegisterAuthenticationFactor( |
| Eq(kAccountInfo), |
| MatchTrustedVaultKeyAndVersions( |
| GetTrustedVaultKeysWithVersions({kVaultKeys}, kLastKeyVersion)), |
| PublicKeyWhenExportedEq(kPublicKey), |
| Eq(AuthenticationFactorTypeAndRegistrationParams( |
| UnspecifiedAuthenticationFactorType(kMethodTypeHint))), |
| _)) |
| .WillOnce([&](const CoreAccountInfo&, const MemberKeysSource&, |
| const SecureBoxPublicKey& public_key, |
| AuthenticationFactorTypeAndRegistrationParams, |
| TrustedVaultConnection::RegisterAuthenticationFactorCallback |
| callback) { |
| registration_callback = std::move(callback); |
| // Note: TrustedVaultConnection::Request doesn't support |
| // cancellation, so these tests don't cover the contract that |
| // caller should store Request object until it's completed or need |
| // to be cancelled. |
| return std::make_unique<TrustedVaultConnection::Request>(); |
| }); |
| SetPrimaryAccountWithUnknownAuthError(kAccountInfo); |
| |
| // The operation should be in flight. |
| EXPECT_FALSE(backend()->HasPendingTrustedRecoveryMethodForTesting()); |
| ASSERT_FALSE(registration_callback.is_null()); |
| |
| // Mimic successful completion of the request. |
| EXPECT_CALL(completion_callback, Run()); |
| std::move(registration_callback) |
| .Run(TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion); |
| } |
| |
| TEST_F(StandaloneTrustedVaultBackendTest, |
| ShouldDeferTrustedRecoveryMethodUntilPersistentAuthErrorFixed) { |
| const std::vector<std::vector<uint8_t>> kVaultKeys = {{1, 2, 3}}; |
| const int kLastKeyVersion = 1; |
| const std::vector<uint8_t> kPublicKey = |
| SecureBoxKeyPair::GenerateRandom()->public_key().ExportToBytes(); |
| const CoreAccountInfo kAccountInfo = MakeAccountInfoWithGaiaId("user"); |
| const int kMethodTypeHint = 7; |
| |
| // Mimic fake recovery factor previously registered with some keys. |
| StoreKeysAndMimicRecoveryFactorRegistration(kVaultKeys, kLastKeyVersion, |
| kAccountInfo); |
| |
| // Mimic entering a persistent auth error. |
| backend()->SetPrimaryAccount( |
| kAccountInfo, StandaloneTrustedVaultBackend::RefreshTokenErrorState:: |
| kPersistentAuthError); |
| |
| // No request should be issued while there is a persistent auth error. |
| base::MockCallback<base::OnceClosure> completion_callback; |
| EXPECT_CALL(*connection(), RegisterAuthenticationFactor).Times(0); |
| backend()->AddTrustedRecoveryMethod(kAccountInfo.gaia, kPublicKey, |
| kMethodTypeHint, |
| completion_callback.Get()); |
| |
| EXPECT_TRUE(backend()->HasPendingTrustedRecoveryMethodForTesting()); |
| |
| // Upon resolving the auth error, the request should be issued. |
| TrustedVaultConnection::RegisterAuthenticationFactorCallback |
| registration_callback; |
| EXPECT_CALL( |
| *connection(), |
| RegisterAuthenticationFactor( |
| Eq(kAccountInfo), |
| MatchTrustedVaultKeyAndVersions( |
| GetTrustedVaultKeysWithVersions({kVaultKeys}, kLastKeyVersion)), |
| PublicKeyWhenExportedEq(kPublicKey), |
| Eq(AuthenticationFactorTypeAndRegistrationParams( |
| UnspecifiedAuthenticationFactorType(kMethodTypeHint))), |
| _)) |
| .WillOnce([&](const CoreAccountInfo&, const MemberKeysSource&, |
| const SecureBoxPublicKey& public_key, |
| AuthenticationFactorTypeAndRegistrationParams, |
| TrustedVaultConnection::RegisterAuthenticationFactorCallback |
| callback) { |
| registration_callback = std::move(callback); |
| // Note: TrustedVaultConnection::Request doesn't support |
| // cancellation, so these tests don't cover the contract that |
| // caller should store Request object until it's completed or need |
| // to be cancelled. |
| return std::make_unique<TrustedVaultConnection::Request>(); |
| }); |
| backend()->SetPrimaryAccount( |
| kAccountInfo, StandaloneTrustedVaultBackend::RefreshTokenErrorState:: |
| kNoPersistentAuthErrors); |
| |
| // The operation should be in flight. |
| EXPECT_FALSE(backend()->HasPendingTrustedRecoveryMethodForTesting()); |
| ASSERT_FALSE(registration_callback.is_null()); |
| |
| // Mimic successful completion of the request. |
| EXPECT_CALL(completion_callback, Run()); |
| std::move(registration_callback) |
| .Run(TrustedVaultRegistrationStatus::kSuccess, kLastKeyVersion); |
| } |
| |
| } // namespace |
| |
| } // namespace trusted_vault |