| // Copyright 2013 The Chromium 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 <algorithm> |
| #include <cstring> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/scoped_observation.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "base/timer/mock_timer.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/signin/core/browser/account_reconcilor.h" |
| #include "components/signin/core/browser/mirror_account_reconcilor_delegate.h" |
| #include "components/signin/public/base/account_consistency_method.h" |
| #include "components/signin/public/base/list_accounts_test_utils.h" |
| #include "components/signin/public/base/signin_buildflags.h" |
| #include "components/signin/public/base/signin_metrics.h" |
| #include "components/signin/public/base/signin_pref_names.h" |
| #include "components/signin/public/base/test_signin_client.h" |
| #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" |
| #include "components/signin/public/identity_manager/identity_test_environment.h" |
| #include "components/signin/public/identity_manager/identity_test_utils.h" |
| #include "components/signin/public/identity_manager/primary_account_mutator.h" |
| #include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "google_apis/gaia/google_service_auth_error.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| #include "components/signin/core/browser/dice_account_reconcilor_delegate.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "chromeos/tpm/stub_install_attributes.h" |
| #include "components/signin/core/browser/active_directory_account_reconcilor_delegate.h" |
| #endif |
| |
| using signin_metrics::AccountReconcilorState; |
| |
| namespace { |
| |
| // An AccountReconcilorDelegate that records all calls (Spy pattern). |
| class SpyReconcilorDelegate : public signin::AccountReconcilorDelegate { |
| public: |
| int num_reconcile_finished_calls_{0}; |
| int num_reconcile_timeout_calls_{0}; |
| |
| bool IsReconcileEnabled() const override { return true; } |
| |
| bool IsAccountConsistencyEnforced() const override { return true; } |
| |
| gaia::GaiaSource GetGaiaApiSource() const override { |
| return gaia::GaiaSource::kChrome; |
| } |
| |
| bool ShouldAbortReconcileIfPrimaryHasError() const override { return true; } |
| |
| CoreAccountId GetFirstGaiaAccountForReconcile( |
| const std::vector<CoreAccountId>& chrome_accounts, |
| const std::vector<gaia::ListedAccount>& gaia_accounts, |
| const CoreAccountId& primary_account, |
| bool first_execution, |
| bool will_logout) const override { |
| return primary_account; |
| } |
| |
| std::vector<CoreAccountId> GetChromeAccountsForReconcile( |
| const std::vector<CoreAccountId>& chrome_accounts, |
| const CoreAccountId& primary_account, |
| const std::vector<gaia::ListedAccount>& gaia_accounts, |
| bool first_execution, |
| bool primary_has_error, |
| const gaia::MultiloginMode mode) const override { |
| return chrome_accounts; |
| } |
| |
| void OnReconcileFinished(const CoreAccountId& first_account) override { |
| ++num_reconcile_finished_calls_; |
| } |
| |
| base::TimeDelta GetReconcileTimeout() const override { |
| // Does not matter as long as it is different from base::TimeDelta::Max(). |
| return base::TimeDelta::FromMinutes(100); |
| } |
| |
| void OnReconcileError(const GoogleServiceAuthError& error) override { |
| ++num_reconcile_timeout_calls_; |
| } |
| }; |
| |
| // gmock does not allow mocking classes with move-only parameters, preventing |
| // from mocking the AccountReconcilor class directly (because of the |
| // unique_ptr<AccountReconcilorDelegate> parameter). |
| // Introduce a dummy class creating the delegate internally, to avoid the move. |
| class DummyAccountReconcilorWithDelegate : public AccountReconcilor { |
| public: |
| DummyAccountReconcilorWithDelegate( |
| signin::IdentityManager* identity_manager, |
| SigninClient* client, |
| signin::AccountConsistencyMethod account_consistency, |
| bool dice_migration_completed) |
| : AccountReconcilor( |
| identity_manager, |
| client, |
| CreateAccountReconcilorDelegate(client, |
| identity_manager, |
| account_consistency, |
| dice_migration_completed)) { |
| Initialize(false /* start_reconcile_if_tokens_available */); |
| } |
| |
| // Takes ownership of |delegate|. |
| // gmock can't work with move only parameters. |
| DummyAccountReconcilorWithDelegate( |
| signin::IdentityManager* identity_manager, |
| SigninClient* client, |
| signin::AccountReconcilorDelegate* delegate) |
| : AccountReconcilor( |
| identity_manager, |
| client, |
| std::unique_ptr<signin::AccountReconcilorDelegate>(delegate)) { |
| Initialize(false /* start_reconcile_if_tokens_available */); |
| } |
| |
| static std::unique_ptr<signin::AccountReconcilorDelegate> |
| CreateAccountReconcilorDelegate( |
| SigninClient* signin_client, |
| signin::IdentityManager* identity_manager, |
| signin::AccountConsistencyMethod account_consistency, |
| bool dice_migration_completed) { |
| switch (account_consistency) { |
| case signin::AccountConsistencyMethod::kMirror: |
| return std::make_unique<signin::MirrorAccountReconcilorDelegate>( |
| identity_manager); |
| case signin::AccountConsistencyMethod::kDisabled: |
| return std::make_unique<signin::AccountReconcilorDelegate>(); |
| case signin::AccountConsistencyMethod::kDice: |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| return std::make_unique<signin::DiceAccountReconcilorDelegate>( |
| signin_client, dice_migration_completed); |
| #else |
| NOTREACHED(); |
| return nullptr; |
| #endif |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| }; |
| |
| class MockAccountReconcilor |
| : public testing::StrictMock<DummyAccountReconcilorWithDelegate> { |
| public: |
| MockAccountReconcilor(signin::IdentityManager* identity_manager, |
| SigninClient* client, |
| signin::AccountConsistencyMethod account_consistency, |
| bool dice_migration_completed); |
| |
| MockAccountReconcilor( |
| signin::IdentityManager* identity_manager, |
| SigninClient* client, |
| std::unique_ptr<signin::AccountReconcilorDelegate> delegate); |
| |
| MOCK_METHOD1(PerformMergeAction, void(const CoreAccountId& account_id)); |
| MOCK_METHOD0(PerformLogoutAllAccountsAction, void()); |
| MOCK_METHOD1(PerformSetCookiesAction, |
| void(const signin::MultiloginParameters& parameters)); |
| }; |
| |
| MockAccountReconcilor::MockAccountReconcilor( |
| signin::IdentityManager* identity_manager, |
| SigninClient* client, |
| signin::AccountConsistencyMethod account_consistency, |
| bool dice_migration_completed) |
| : testing::StrictMock<DummyAccountReconcilorWithDelegate>( |
| identity_manager, |
| client, |
| account_consistency, |
| dice_migration_completed) {} |
| |
| MockAccountReconcilor::MockAccountReconcilor( |
| signin::IdentityManager* identity_manager, |
| SigninClient* client, |
| std::unique_ptr<signin::AccountReconcilorDelegate> delegate) |
| : testing::StrictMock<DummyAccountReconcilorWithDelegate>( |
| identity_manager, |
| client, |
| delegate.release()) {} |
| |
| struct Cookie { |
| std::string gaia_id; |
| bool is_valid; |
| |
| bool operator==(const Cookie& other) const { |
| return gaia_id == other.gaia_id && is_valid == other.is_valid; |
| } |
| }; |
| |
| // Converts CookieParams to ListedAccounts. |
| gaia::ListedAccount ListedAccountFromCookieParams( |
| const signin::CookieParams& params, |
| const CoreAccountId& account_id) { |
| gaia::ListedAccount listed_account; |
| listed_account.id = account_id; |
| listed_account.email = params.email; |
| listed_account.gaia_id = params.gaia_id; |
| listed_account.raw_email = params.email; |
| listed_account.valid = params.valid; |
| listed_account.signed_out = params.signed_out; |
| listed_account.verified = params.verified; |
| return listed_account; |
| } |
| |
| } // namespace |
| |
| class AccountReconcilorTest : public ::testing::Test { |
| protected: |
| AccountReconcilorTest(); |
| ~AccountReconcilorTest() override; |
| |
| signin::IdentityTestEnvironment* identity_test_env() { |
| return &identity_test_env_; |
| } |
| |
| base::test::SingleThreadTaskEnvironment* task_environment() { |
| return &task_environment_; |
| } |
| |
| TestSigninClient* test_signin_client() { return &test_signin_client_; } |
| base::HistogramTester* histogram_tester() { return &histogram_tester_; } |
| |
| MockAccountReconcilor* GetMockReconcilor(); |
| MockAccountReconcilor* CreateMockReconcilor( |
| std::unique_ptr<signin::AccountReconcilorDelegate> delegate); |
| |
| AccountInfo ConnectProfileToAccount(const std::string& email); |
| |
| CoreAccountId PickAccountIdForAccount(const std::string& gaia_id, |
| const std::string& username); |
| |
| void SimulateAddAccountToCookieCompleted(AccountReconcilor* reconcilor, |
| const CoreAccountId& account_id, |
| const GoogleServiceAuthError& error); |
| |
| void SimulateSetAccountsInCookieCompleted( |
| AccountReconcilor* reconcilor, |
| signin::SetAccountsInCookieResult result); |
| |
| void SimulateLogOutFromCookieCompleted(AccountReconcilor* reconcilor, |
| const GoogleServiceAuthError& error); |
| |
| void SimulateCookieContentSettingsChanged( |
| content_settings::Observer* observer, |
| const ContentSettingsPattern& primary_pattern); |
| |
| void SetAccountConsistency(signin::AccountConsistencyMethod method); |
| |
| // Should never be called before |SetAccountConsistency|. |
| void SetDiceMigrationCompleted(bool dice_migration_completed); |
| |
| PrefService* pref_service() { return &pref_service_; } |
| |
| void DeleteReconcilor() { |
| if (mock_reconcilor_) |
| mock_reconcilor_->Shutdown(); |
| mock_reconcilor_.reset(); |
| } |
| |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| |
| private: |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| signin::AccountConsistencyMethod account_consistency_; |
| bool dice_migration_completed_ = false; |
| sync_preferences::TestingPrefServiceSyncable pref_service_; |
| TestSigninClient test_signin_client_; |
| signin::IdentityTestEnvironment identity_test_env_; |
| std::unique_ptr<MockAccountReconcilor> mock_reconcilor_; |
| base::HistogramTester histogram_tester_; |
| |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTest); |
| }; |
| |
| class AccountReconcilorMirrorTest : public AccountReconcilorTest { |
| public: |
| AccountReconcilorMirrorTest() { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorMirrorTest); |
| }; |
| |
| // For tests that must be run with multiple account consistency methods. |
| class AccountReconcilorMethodParamTest |
| : public AccountReconcilorTest, |
| public ::testing::WithParamInterface<signin::AccountConsistencyMethod> { |
| public: |
| AccountReconcilorMethodParamTest() = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorMethodParamTest); |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(Dice_Mirror, |
| AccountReconcilorMethodParamTest, |
| ::testing::Values( |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| signin::AccountConsistencyMethod::kDice, |
| #endif |
| signin::AccountConsistencyMethod::kMirror)); |
| |
| AccountReconcilorTest::AccountReconcilorTest() |
| : task_environment_( |
| base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME), |
| account_consistency_(signin::AccountConsistencyMethod::kDisabled), |
| test_signin_client_(&pref_service_, &test_url_loader_factory_), |
| identity_test_env_(/*test_url_loader_factory=*/nullptr, |
| &pref_service_, |
| account_consistency_, |
| &test_signin_client_) { |
| signin::SetListAccountsResponseHttpNotFound(&test_url_loader_factory_); |
| |
| // The reconcilor should not be built before the test can set the account |
| // consistency method. |
| EXPECT_FALSE(mock_reconcilor_); |
| } |
| |
| MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor() { |
| if (!mock_reconcilor_) { |
| mock_reconcilor_ = std::make_unique<MockAccountReconcilor>( |
| identity_test_env_.identity_manager(), &test_signin_client_, |
| account_consistency_, dice_migration_completed_); |
| } |
| |
| return mock_reconcilor_.get(); |
| } |
| |
| MockAccountReconcilor* AccountReconcilorTest::CreateMockReconcilor( |
| std::unique_ptr<signin::AccountReconcilorDelegate> delegate) { |
| DCHECK(!mock_reconcilor_); |
| mock_reconcilor_ = std::make_unique<MockAccountReconcilor>( |
| identity_test_env_.identity_manager(), &test_signin_client_, |
| std::move(delegate)); |
| return mock_reconcilor_.get(); |
| } |
| |
| AccountReconcilorTest::~AccountReconcilorTest() { |
| if (mock_reconcilor_) |
| mock_reconcilor_->Shutdown(); |
| test_signin_client_.Shutdown(); |
| } |
| |
| AccountInfo AccountReconcilorTest::ConnectProfileToAccount( |
| const std::string& email) { |
| AccountInfo account_info = |
| identity_test_env()->MakePrimaryAccountAvailable(email); |
| return account_info; |
| } |
| |
| CoreAccountId AccountReconcilorTest::PickAccountIdForAccount( |
| const std::string& gaia_id, |
| const std::string& username) { |
| return identity_test_env()->identity_manager()->PickAccountIdForAccount( |
| gaia_id, username); |
| } |
| |
| void AccountReconcilorTest::SimulateAddAccountToCookieCompleted( |
| AccountReconcilor* reconcilor, |
| const CoreAccountId& account_id, |
| const GoogleServiceAuthError& error) { |
| reconcilor->OnAddAccountToCookieCompleted(account_id, error); |
| } |
| |
| void AccountReconcilorTest::SimulateSetAccountsInCookieCompleted( |
| AccountReconcilor* reconcilor, |
| signin::SetAccountsInCookieResult result) { |
| reconcilor->OnSetAccountsInCookieCompleted(result); |
| } |
| |
| void AccountReconcilorTest::SimulateLogOutFromCookieCompleted( |
| AccountReconcilor* reconcilor, |
| const GoogleServiceAuthError& error) { |
| reconcilor->OnLogOutFromCookieCompleted(error); |
| } |
| |
| void AccountReconcilorTest::SimulateCookieContentSettingsChanged( |
| content_settings::Observer* observer, |
| const ContentSettingsPattern& primary_pattern) { |
| observer->OnContentSettingChanged(primary_pattern, |
| ContentSettingsPattern::Wildcard(), |
| ContentSettingsType::COOKIES); |
| } |
| |
| void AccountReconcilorTest::SetAccountConsistency( |
| signin::AccountConsistencyMethod method) { |
| account_consistency_ = method; |
| dice_migration_completed_ = |
| account_consistency_ == signin::AccountConsistencyMethod::kDice; |
| } |
| |
| void AccountReconcilorTest::SetDiceMigrationCompleted( |
| bool dice_migration_completed) { |
| DCHECK_EQ(signin::AccountConsistencyMethod::kDice, account_consistency_); |
| dice_migration_completed_ = dice_migration_completed; |
| } |
| |
| TEST_F(AccountReconcilorTest, Basic) { |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| } |
| |
| enum class IsFirstReconcile { |
| kBoth = 0, |
| kFirst, |
| kNotFirst, |
| }; |
| |
| struct AccountReconcilorTestTableParam { |
| const char* tokens; |
| const char* cookies; |
| IsFirstReconcile is_first_reconcile; |
| const char* gaia_api_calls; |
| const char* tokens_after_reconcile; |
| const char* cookies_after_reconcile; |
| const char* gaia_api_calls_multilogin; |
| const char* tokens_after_reconcile_multilogin; |
| const char* cookies_after_reconcile_multilogin; |
| // Int represents AccountReconcilorDelegate::InconsistencyReason. |
| const int inconsistency_reason; |
| }; |
| |
| std::vector<AccountReconcilorTestTableParam> GenerateTestCasesFromParams( |
| const std::vector<AccountReconcilorTestTableParam>& params) { |
| std::vector<AccountReconcilorTestTableParam> return_params; |
| for (const AccountReconcilorTestTableParam& param : params) { |
| if (param.is_first_reconcile == IsFirstReconcile::kBoth) { |
| AccountReconcilorTestTableParam param_true = param; |
| param_true.is_first_reconcile = IsFirstReconcile::kFirst; |
| AccountReconcilorTestTableParam param_false = param; |
| param_false.is_first_reconcile = IsFirstReconcile::kNotFirst; |
| return_params.push_back(param_true); |
| return_params.push_back(param_false); |
| } else { |
| return_params.push_back(param); |
| } |
| } |
| return return_params; |
| } |
| |
| struct ForceDiceMigrationTestTableParam { |
| const char* tokens; |
| const char* cookies; |
| const char* gaia_api_calls; |
| const char* tokens_after_reconcile; |
| const char* cookies_after_reconcile; |
| }; |
| |
| // Pretty prints a AccountReconcilorTestTableParam. Used by gtest. |
| void PrintTo(const AccountReconcilorTestTableParam& param, ::std::ostream* os) { |
| *os << "Tokens: " << param.tokens << ". Cookies: " << param.cookies |
| << ". First reconcile: " |
| << (param.is_first_reconcile == IsFirstReconcile::kFirst ? "true" |
| : "false"); |
| } |
| |
| class BaseAccountReconcilorTestTable : public AccountReconcilorTest { |
| protected: |
| BaseAccountReconcilorTestTable(const AccountReconcilorTestTableParam& param) |
| : BaseAccountReconcilorTestTable(param.tokens, |
| param.cookies, |
| param.is_first_reconcile, |
| param.gaia_api_calls, |
| param.tokens_after_reconcile, |
| param.cookies_after_reconcile) {} |
| |
| BaseAccountReconcilorTestTable(const char* tokens, |
| const char* cookies, |
| IsFirstReconcile is_first_reconcile, |
| const char* gaia_api_calls, |
| const char* tokens_after_reconcile, |
| const char* cookies_after_reconcile) |
| : tokens_(tokens), |
| cookies_(cookies), |
| is_first_reconcile_(is_first_reconcile), |
| gaia_api_calls_(gaia_api_calls), |
| tokens_after_reconcile_(tokens_after_reconcile), |
| cookies_after_reconcile_(cookies_after_reconcile) { |
| accounts_['A'] = {"a@gmail.com", |
| signin::GetTestGaiaIdForEmail("a@gmail.com")}; |
| accounts_['B'] = {"b@gmail.com", |
| signin::GetTestGaiaIdForEmail("b@gmail.com")}; |
| accounts_['C'] = {"c@gmail.com", |
| signin::GetTestGaiaIdForEmail("c@gmail.com")}; |
| } |
| |
| struct Account { |
| std::string email; |
| std::string gaia_id; |
| }; |
| |
| struct Token { |
| std::string gaia_id; |
| std::string email; |
| bool is_authenticated; |
| bool has_error; |
| }; |
| |
| // Build Tokens from string. |
| std::vector<Token> ParseTokenString(const char* token_string) { |
| std::vector<Token> parsed_tokens; |
| bool is_authenticated = false; |
| bool has_error = false; |
| for (int i = 0; token_string[i] != '\0'; ++i) { |
| char token_code = token_string[i]; |
| if (token_code == '*') { |
| is_authenticated = true; |
| continue; |
| } |
| if (token_code == 'x') { |
| has_error = true; |
| continue; |
| } |
| parsed_tokens.push_back({accounts_[token_code].gaia_id, |
| accounts_[token_code].email, is_authenticated, |
| has_error}); |
| is_authenticated = false; |
| has_error = false; |
| } |
| return parsed_tokens; |
| } |
| |
| // Build Cookies from string. |
| std::vector<Cookie> ParseCookieString(const char* cookie_string) { |
| std::vector<Cookie> parsed_cookies; |
| bool valid = true; |
| for (int i = 0; cookie_string[i] != '\0'; ++i) { |
| char cookie_code = cookie_string[i]; |
| if (cookie_code == 'x') { |
| valid = false; |
| continue; |
| } |
| parsed_cookies.push_back({accounts_[cookie_code].gaia_id, valid}); |
| valid = true; |
| } |
| return parsed_cookies; |
| } |
| |
| // Checks that the tokens in the TokenService match the tokens. |
| void VerifyCurrentTokens(const std::vector<Token>& tokens) { |
| auto* identity_manager = identity_test_env()->identity_manager(); |
| EXPECT_EQ(identity_manager->GetAccountsWithRefreshTokens().size(), |
| tokens.size()); |
| |
| signin::ConsentLevel consent_level = |
| GetMockReconcilor()->delegate_->GetConsentLevelForPrimaryAccount(); |
| CoreAccountId primary_account_id = |
| identity_manager->GetPrimaryAccountId(consent_level); |
| bool authenticated_account_found = false; |
| for (const Token& token : tokens) { |
| CoreAccountId account_id = |
| PickAccountIdForAccount(token.gaia_id, token.email); |
| EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id)); |
| EXPECT_EQ( |
| token.has_error, |
| identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( |
| account_id)); |
| if (token.is_authenticated) { |
| EXPECT_EQ(account_id, primary_account_id); |
| authenticated_account_found = true; |
| } |
| } |
| if (!authenticated_account_found) |
| EXPECT_EQ(CoreAccountId(), primary_account_id); |
| } |
| |
| void SetupTokens(const char* tokens_string) { |
| std::vector<Token> tokens = ParseTokenString(tokens_string); |
| Token primary_account; |
| for (const Token& token : tokens) { |
| CoreAccountId account_id; |
| if (token.is_authenticated) { |
| account_id = ConnectProfileToAccount(token.email).account_id; |
| } else { |
| account_id = |
| identity_test_env()->MakeAccountAvailable(token.email).account_id; |
| } |
| if (token.has_error) { |
| signin::UpdatePersistentErrorOfRefreshTokenForAccount( |
| identity_test_env()->identity_manager(), account_id, |
| GoogleServiceAuthError( |
| GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); |
| } |
| } |
| VerifyCurrentTokens(tokens); |
| } |
| |
| void ConfigureCookieManagerService(const std::vector<Cookie>& cookies) { |
| std::vector<signin::CookieParams> cookie_params; |
| for (const auto& cookie : cookies) { |
| std::string gaia_id = cookie.gaia_id; |
| |
| // Figure the account token of this specific account id, |
| // ie 'A', 'B', or 'C'. |
| char account_key = '\0'; |
| for (const auto& account : accounts_) { |
| if (account.second.gaia_id == gaia_id) { |
| account_key = account.first; |
| break; |
| } |
| } |
| ASSERT_NE(account_key, '\0'); |
| |
| cookie_params.push_back({accounts_[account_key].email, gaia_id, |
| cookie.is_valid, false /* signed_out */, |
| true /* verified */}); |
| } |
| signin::SetListAccountsResponseWithParams(cookie_params, |
| &test_url_loader_factory_); |
| identity_test_env()->SetFreshnessOfAccountsInGaiaCookie(false); |
| } |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| void RunReconcile() { |
| // Setup cookies. |
| std::vector<Cookie> cookies = ParseCookieString(cookies_); |
| ConfigureCookieManagerService(cookies); |
| |
| // Call list accounts now so that the next call completes synchronously. |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Setup tokens. This triggers listing cookies so we need to setup cookies |
| // before that. |
| SetupTokens(tokens_); |
| |
| // Setup expectations. |
| testing::InSequence mock_sequence; |
| bool logout_action = false; |
| for (int i = 0; gaia_api_calls_[i] != '\0'; ++i) { |
| if (gaia_api_calls_[i] == 'X') { |
| logout_action = true; |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(1); |
| cookies.clear(); |
| continue; |
| } |
| std::string cookie(1, gaia_api_calls_[i]); |
| CoreAccountId account_id_for_cookie = PickAccountIdForAccount( |
| accounts_[cookie[0]].gaia_id, accounts_[cookie[0]].email); |
| EXPECT_CALL(*GetMockReconcilor(), |
| PerformMergeAction(account_id_for_cookie)) |
| .Times(1); |
| // MergeSession fixes an existing cookie or appends it at the end. |
| auto it = |
| std::find(cookies.begin(), cookies.end(), |
| Cookie{accounts_[cookie[0]].gaia_id, false /* is_valid */}); |
| if (it == cookies.end()) |
| cookies.push_back({accounts_[cookie[0]].gaia_id, true}); |
| else |
| it->is_valid = true; |
| } |
| if (!logout_action) { |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(0); |
| } |
| |
| // Check the expected cookies after reconcile. |
| std::vector<Cookie> expected_cookies = |
| ParseCookieString(cookies_after_reconcile_); |
| ASSERT_EQ(expected_cookies, cookies); |
| |
| // Reconcile. |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor->first_execution_); |
| reconcilor->first_execution_ = |
| is_first_reconcile_ == IsFirstReconcile::kFirst; |
| ASSERT_TRUE(reconcilor->delegate_->IsAccountConsistencyEnforced()); |
| reconcilor->StartReconcile(); |
| for (int i = 0; gaia_api_calls_[i] != '\0'; ++i) { |
| if (gaia_api_calls_[i] == 'X') { |
| SimulateLogOutFromCookieCompleted( |
| reconcilor, GoogleServiceAuthError::AuthErrorNone()); |
| continue; |
| } |
| CoreAccountId account_id = |
| PickAccountIdForAccount(accounts_[gaia_api_calls_[i]].gaia_id, |
| accounts_[gaia_api_calls_[i]].email); |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id, GoogleServiceAuthError::AuthErrorNone()); |
| } |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| if (tokens_ == tokens_after_reconcile_) { |
| EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } else { |
| // If the tokens were changed by the reconcile, a new reconcile should be |
| // scheduled. |
| EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED, |
| reconcilor->GetState()); |
| } |
| |
| VerifyCurrentTokens(ParseTokenString(tokens_after_reconcile_)); |
| |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| |
| // Another reconcile is sometimes triggered if Chrome accounts have changed. |
| // Allow it to finish. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)) |
| .WillRepeatedly(testing::Return()); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .WillRepeatedly(testing::Return()); |
| ConfigureCookieManagerService({}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| #endif // BUILDFLAG(ENABLE_DICE_SUPPORT) |
| |
| std::string GaiaIdForAccountKey(char account_key) { |
| return accounts_[account_key].gaia_id; |
| } |
| |
| std::map<char, Account> accounts_; |
| const char* tokens_; |
| const char* cookies_; |
| IsFirstReconcile is_first_reconcile_; |
| const char* gaia_api_calls_; |
| const char* tokens_after_reconcile_; |
| const char* cookies_after_reconcile_; |
| }; |
| |
| // Parameterized version of AccountReconcilorTest. |
| class AccountReconcilorTestTable |
| : public BaseAccountReconcilorTestTable, |
| public ::testing::WithParamInterface<AccountReconcilorTestTableParam> { |
| protected: |
| AccountReconcilorTestTable() : BaseAccountReconcilorTestTable(GetParam()) {} |
| |
| // Checks that reconcile is idempotent. |
| void CheckReconcileIdempotent( |
| const std::vector<AccountReconcilorTestTableParam>& params, |
| const AccountReconcilorTestTableParam& param, |
| bool multilogin) { |
| // Simulate another reconcile based on the results of this one: find the |
| // corresponding row in the table and check that it does nothing. |
| for (const AccountReconcilorTestTableParam& row : params) { |
| if (row.is_first_reconcile == IsFirstReconcile::kFirst) |
| continue; |
| |
| if (!((strcmp(row.tokens, param.tokens_after_reconcile) == 0 && |
| strcmp(row.cookies, param.cookies_after_reconcile) == 0 && |
| !multilogin) || |
| (strcmp(row.tokens, param.tokens_after_reconcile_multilogin) == 0 && |
| strcmp(row.cookies, param.cookies_after_reconcile_multilogin) == |
| 0 && |
| multilogin))) { |
| continue; |
| } |
| if (multilogin) { |
| EXPECT_STREQ(row.tokens, row.tokens_after_reconcile_multilogin); |
| EXPECT_STREQ(row.cookies, row.cookies_after_reconcile_multilogin); |
| } else { |
| EXPECT_STREQ(row.tokens, row.tokens_after_reconcile); |
| EXPECT_STREQ(row.cookies, row.cookies_after_reconcile); |
| } |
| return; |
| } |
| |
| ADD_FAILURE() << "Could not check that reconcile is idempotent."; |
| } |
| }; |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| TEST_F(AccountReconcilorMirrorTest, IdentityManagerRegistration) { |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_FALSE(reconcilor->IsRegisteredWithIdentityManager()); |
| |
| identity_test_env()->MakePrimaryAccountAvailable("user@gmail.com"); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| |
| identity_test_env()->ClearPrimaryAccount(); |
| ASSERT_FALSE(reconcilor->IsRegisteredWithIdentityManager()); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, Reauth) { |
| const std::string email = "user@gmail.com"; |
| AccountInfo account_info = ConnectProfileToAccount(email); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); |
| |
| // Simulate reauth. The state of the reconcilor should not change. |
| auto* account_mutator = |
| identity_test_env()->identity_manager()->GetPrimaryAccountMutator(); |
| DCHECK(account_mutator); |
| account_mutator->SetPrimaryAccount(account_info.account_id); |
| |
| ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); |
| } |
| |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| TEST_F(AccountReconcilorMirrorTest, ProfileAlreadyConnected) { |
| ConnectProfileToAccount("user@gmail.com"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); |
| } |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| |
| namespace { |
| std::vector<Cookie> FakeSetAccountsInCookie( |
| const signin::MultiloginParameters& parameters, |
| const std::vector<Cookie>& cookies_before_reconcile) { |
| std::vector<Cookie> cookies_after_reconcile; |
| if (parameters.mode == |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER) { |
| for (const CoreAccountId& account : parameters.accounts_to_send) { |
| cookies_after_reconcile.push_back({account.ToString(), true}); |
| } |
| } else { |
| std::vector<CoreAccountId> accounts(parameters.accounts_to_send.begin(), |
| parameters.accounts_to_send.end()); |
| cookies_after_reconcile = cookies_before_reconcile; |
| for (Cookie& param : cookies_after_reconcile) { |
| CoreAccountId account = CoreAccountId(param.gaia_id); |
| if (base::Contains(accounts, account)) { |
| param.is_valid = true; |
| accounts.erase(std::find(accounts.begin(), accounts.end(), account)); |
| } else { |
| DCHECK(!param.is_valid); |
| } |
| } |
| for (const CoreAccountId& account : accounts) { |
| cookies_after_reconcile.push_back({account.ToString(), true}); |
| } |
| } |
| return cookies_after_reconcile; |
| } |
| } // namespace |
| |
| // clang-format off |
| const std::vector<AccountReconcilorTestTableParam> kDiceParams = { |
| // This table encodes the initial state and expectations of a reconcile. |
| // The syntax is: |
| // - Tokens: |
| // A, B, C: Accounts for which we have a token in Chrome. |
| // *: The next account is the main Chrome account (i.e. in |
| // IdentityManager). |
| // x: The next account has a token error. |
| // - API calls: |
| // U: Multilogin with mode UPDATE |
| // P: Multilogin with mode PRESERVE |
| // X: Logout all accounts. |
| // A, B, C: Merge account. |
| |
| // - Cookies: |
| // A, B, C: Accounts in the Gaia cookie (returned by ListAccounts). |
| // x: The next cookie is marked "invalid". |
| // - First Run: true if this is the first reconcile (i.e. Chrome startup). |
| // ------------------------------------------------------------------------- |
| // Tokens|Cookies|First Run|Gaia calls|Tokens aft.|Cookies aft.|M.calls| M.Tokens aft.| M.Cookies aft.| AccountReconcilorDelegate::InconsistencyReason | |
| // ------------------------------------------------------------------------- |
| |
| // First reconcile (Chrome restart): Rebuild the Gaia cookie to match the |
| // tokens. Make the Sync account the default account in the Gaia cookie. |
| // Sync enabled. |
| { "", "A", IsFirstReconcile::kBoth, "X", "", "", "U", "", "", 3}, |
| { "*AB", "AB", IsFirstReconcile::kBoth, "", "*AB", "AB", "", "*AB", "AB", 0}, |
| { "*A", "A", IsFirstReconcile::kBoth, "", "*A", "A", "", "*A" , "A", 0}, |
| { "*A", "", IsFirstReconcile::kBoth, "A", "*A", "A", "PA", "*A" , "A", 1}, |
| { "*A", "B", IsFirstReconcile::kBoth, "XA", "*A", "A", "UA", "*A" , "A", 1}, |
| { "*A", "AB", IsFirstReconcile::kBoth, "XA", "*A", "A", "UA", "*A" , "A", 5}, |
| { "*AB", "BA", IsFirstReconcile::kFirst, "XAB", "*AB", "AB", "UAB", "*AB", "AB", 7}, |
| { "*AB", "BA", IsFirstReconcile::kNotFirst, "", "*AB", "BA", "", "*AB", "BA", 0}, |
| |
| { "*AB", "A", IsFirstReconcile::kBoth, "B", "*AB", "AB", "PAB", "*AB", "AB", 4}, |
| |
| { "*AB", "B", IsFirstReconcile::kFirst, "XAB", "*AB", "AB", "UAB", "*AB", "AB", 1}, |
| { "*AB", "B", IsFirstReconcile::kNotFirst, "A", "*AB", "BA", "PBA", "*AB", "BA", 1}, |
| |
| { "*AB", "", IsFirstReconcile::kBoth, "AB", "*AB", "AB", "PAB", "*AB", "AB", 1}, |
| // Sync enabled, token error on primary. |
| |
| { "*xAB", "AB", IsFirstReconcile::kBoth, "X", "*xA", "" , "U", "*xA", "", 2}, |
| { "*xAB", "BA", IsFirstReconcile::kBoth, "XB", "*xAB", "B", "UB", "*xAB", "B", 2}, |
| { "*xAB", "A", IsFirstReconcile::kBoth, "X", "*xA", "" , "U", "*xA", "", 2}, |
| { "*xAB", "B", IsFirstReconcile::kBoth, "", "*xAB", "B", "", "*xAB", "B", 0}, |
| { "*xAB", "", IsFirstReconcile::kBoth, "B", "*xAB", "B", "PB", "*xAB", "B", 0}, |
| // Sync enabled, token error on secondary. |
| { "*AxB", "AB", IsFirstReconcile::kBoth, "XA", "*A", "A", "UA", "*A", "A", 5}, |
| { "*AxB", "A", IsFirstReconcile::kBoth, "", "*A", "A", "", "*A", "A", 0}, |
| { "*AxB", "", IsFirstReconcile::kBoth, "A", "*A", "A", "PA", "*A", "A", 1}, |
| // The first account in cookies is swapped even when Chrome is running. |
| // The swap would happen at next startup anyway and doing it earlier avoids signing the user out. |
| { "*AxB", "BA", IsFirstReconcile::kBoth, "XA", "*A", "A", "UA", "*A", "A", 5}, |
| { "*AxB", "B", IsFirstReconcile::kBoth, "XA", "*A", "A", "UA", "*A", "A", 1}, |
| // Sync enabled, token error on both accounts. |
| { "*xAxB", "AB", IsFirstReconcile::kBoth, "X", "*xA", "", "U", "*xA", "", 2}, |
| { "*xAxB", "BA", IsFirstReconcile::kBoth, "X", "*xA", "", "U", "*xA", "", 2}, |
| { "*xAxB", "A", IsFirstReconcile::kBoth, "X", "*xA", "", "U", "*xA", "", 2}, |
| { "*xAxB", "B", IsFirstReconcile::kBoth, "X", "*xA", "", "U", "*xA", "", 5}, |
| { "*xAxB", "", IsFirstReconcile::kBoth, "", "*xA", "", "", "*xA", "", 0}, |
| // Sync disabled. |
| { "AB", "AB", IsFirstReconcile::kBoth, "", "AB", "AB", "", "AB", "AB", 0}, |
| { "AB", "BA", IsFirstReconcile::kBoth, "", "AB", "BA", "", "AB", "BA", 0}, |
| { "AB", "A", IsFirstReconcile::kBoth, "B", "AB", "AB", "PAB", "AB", "AB", 4}, |
| { "AB", "B", IsFirstReconcile::kBoth, "A", "AB", "BA", "PBA", "AB", "BA", 4}, |
| { "AB", "", IsFirstReconcile::kBoth, "AB", "AB", "AB", "PAB", "AB", "AB", 0}, |
| // Sync disabled, token error on first account. |
| { "xAB", "AB", IsFirstReconcile::kFirst, "XB", "B", "B", "UB", "B", "B", 3}, |
| { "xAB", "AB", IsFirstReconcile::kNotFirst, "X", "", "" , "U", "", "", 3}, |
| |
| { "xAB", "BA", IsFirstReconcile::kBoth, "XB", "B", "B", "UB", "B", "B", 5}, |
| |
| { "xAB", "A", IsFirstReconcile::kFirst, "XB", "B", "B", "UB", "B", "B", 3}, |
| { "xAB", "A", IsFirstReconcile::kNotFirst, "X", "", "" , "U", "", "", 3}, |
| |
| { "xAB", "B", IsFirstReconcile::kBoth, "", "B", "B", "", "B", "B", 0}, |
| |
| { "xAB", "", IsFirstReconcile::kBoth, "B", "B", "B", "PB", "B", "B", 0}, |
| // Sync disabled, token error on second account . |
| { "AxB", "AB", IsFirstReconcile::kBoth, "XA", "A", "A", "UA", "A", "A", 5}, |
| |
| { "AxB", "BA", IsFirstReconcile::kFirst, "XA", "A", "A", "UA", "A", "A", 3}, |
| { "AxB", "BA", IsFirstReconcile::kNotFirst, "X", "", "" , "U", "", "", 3}, |
| |
| { "AxB", "A", IsFirstReconcile::kBoth, "", "A", "A", "", "A", "A", 0}, |
| |
| { "AxB", "B", IsFirstReconcile::kFirst, "XA", "A", "A", "UA", "A", "A", 3}, |
| { "AxB", "B", IsFirstReconcile::kNotFirst, "X", "", "" , "U", "", "", 3}, |
| |
| { "AxB", "", IsFirstReconcile::kBoth, "A", "A", "A", "PA", "A", "A", 0}, |
| // Sync disabled, token error on both accounts. |
| { "xAxB", "AB", IsFirstReconcile::kBoth, "X", "", "", "U", "", "", 3}, |
| { "xAxB", "BA", IsFirstReconcile::kBoth, "X", "", "", "U", "", "", 3}, |
| { "xAxB", "A", IsFirstReconcile::kBoth, "X", "", "", "U", "", "", 3}, |
| { "xAxB", "B", IsFirstReconcile::kBoth, "X", "", "", "U", "", "", 3}, |
| { "xAxB", "", IsFirstReconcile::kBoth, "", "", "", "", "", "", 0}, |
| // Account marked as invalid in cookies. |
| // No difference between cookies and tokens, do not do do anything. |
| // Do not logout. Regression tests for http://crbug.com/854799 |
| { "", "xA", IsFirstReconcile::kBoth, "", "", "xA", "", "", "xA", 0}, |
| { "", "xAxB", IsFirstReconcile::kBoth, "", "", "xAxB", "", "", "xAxB", 0}, |
| { "xA", "xA", IsFirstReconcile::kBoth, "", "", "xA", "", "", "xA", 0}, |
| { "xAB", "xAB", IsFirstReconcile::kBoth, "", "B", "xAB", "", "B", "xAB", 0}, |
| { "AxB", "AxC", IsFirstReconcile::kBoth, "", "A", "AxC", "", "A", "AxC", 0}, |
| { "B", "xAB", IsFirstReconcile::kBoth, "", "B", "xAB", "", "B", "xAB", 0}, |
| { "*xA", "xA", IsFirstReconcile::kBoth, "", "*xA", "xA", "", "*xA", "xA", 0}, |
| { "*xA", "xB", IsFirstReconcile::kBoth, "", "*xA", "xB", "", "*xA", "xB", 0}, |
| { "*xAB", "xAB", IsFirstReconcile::kBoth, "", "*xAB", "xAB", "", "*xAB", "xAB", 0}, |
| { "*AxB", "xBA", IsFirstReconcile::kNotFirst, "", "*A", "xBA", "", "*A", "xBA", 0}, |
| // Appending a new cookie after the invalid one. |
| { "B", "xA", IsFirstReconcile::kBoth, "B", "B", "xAB", "PB", "B", "xAB", 4}, |
| { "xAB", "xA", IsFirstReconcile::kBoth, "B", "B", "xAB", "PB", "B", "xAB", 4}, |
| // Refresh existing cookies. |
| { "AB", "xAB", IsFirstReconcile::kBoth, "A", "AB", "AB", "PAB", "AB", "AB", 4}, |
| { "*AB", "xBxA", IsFirstReconcile::kNotFirst, "BA", "*AB", "BA", "PBA", "*AB", "BA", 1}, |
| // Appending and invalidating cookies at the same time. |
| { "xAB", "xAC", IsFirstReconcile::kFirst, "XB", "B", "B", "UB", "B", "B", 6}, |
| { "xAB", "xAC", IsFirstReconcile::kNotFirst, "X", "", "", "U", "", "", 6}, |
| |
| { "xAB", "AxC", IsFirstReconcile::kFirst, "XB", "B", "B", "UB", "B", "B", 3}, |
| { "xAB", "AxC", IsFirstReconcile::kNotFirst, "X", "", "", "U", "", "", 3}, |
| |
| { "*xAB", "xABC", IsFirstReconcile::kFirst, "XB", "*xAB", "B", "UB", "*xAB", "B", 5}, |
| { "*xAB", "xABC", IsFirstReconcile::kNotFirst, "X", "*xA", "", "U", "*xA", "", 5}, |
| |
| { "xAB", "xABC", IsFirstReconcile::kFirst, "XB", "B", "B", "UB", "B", "B", 5}, |
| { "xAB", "xABC", IsFirstReconcile::kNotFirst, "X", "", "", "U", "", "", 5}, |
| // Miscellaneous cases. |
| // Check that unknown Gaia accounts are signed out. |
| { "*A", "AB", IsFirstReconcile::kBoth, "XA", "*A", "A", "UA", "*A", "A", 5}, |
| // Check that Gaia default account is kept in first position. |
| { "AB", "BC", IsFirstReconcile::kBoth, "XBA", "AB", "BA", "UBA", "AB", "BA", 6}, |
| // Check that Gaia cookie order is preserved for B. |
| { "*ABC", "CB", IsFirstReconcile::kFirst, "XABC", "*ABC", "ABC", "UABC", "*ABC", "ABC", 1}, |
| // TODO(https://crbug.com/1129931): Merge session should do XCB instead. |
| { "xABC", "ABC", IsFirstReconcile::kFirst, "XBC", "BC", "BC", "UCB", "BC", "CB", 1}, |
| // Check that order in the chrome_accounts is not important. |
| { "A*B", "", IsFirstReconcile::kBoth, "BA", "A*B", "BA", "PBA", "A*B", "BA", 7}, |
| { "*xBA", "BA", IsFirstReconcile::kFirst, "X", "*xB", "" , "U", "*xB", "", 2}, |
| // Required for idempotency check. |
| { "", "", IsFirstReconcile::kNotFirst, "", "", "", "", "", "", 0}, |
| { "", "xA", IsFirstReconcile::kNotFirst, "", "", "xA", "", "", "xA", 0}, |
| { "", "xB", IsFirstReconcile::kNotFirst, "", "", "xB", "", "", "xB", 0}, |
| { "", "xAxB", IsFirstReconcile::kNotFirst, "", "", "xAxB", "", "", "xAxB", 0}, |
| { "", "xBxA", IsFirstReconcile::kNotFirst, "", "", "xBxA", "", "", "xBxA", 0}, |
| { "*A", "A", IsFirstReconcile::kNotFirst, "", "*A", "A", "", "*A", "A", 0}, |
| { "*A", "xBA", IsFirstReconcile::kNotFirst, "", "*A", "xBA", "", "*A", "xBA", 0}, |
| { "*A", "AxB", IsFirstReconcile::kNotFirst, "", "*A", "AxB", "", "*A", "AxB", 0}, |
| { "A", "A", IsFirstReconcile::kNotFirst, "", "A", "A", "", "A", "A", 0}, |
| { "A", "xBA", IsFirstReconcile::kNotFirst, "", "A", "xBA", "", "A", "xBA", 0}, |
| { "A", "AxB", IsFirstReconcile::kNotFirst, "", "A", "AxB", "", "A", "AxB", 0}, |
| { "B", "B", IsFirstReconcile::kNotFirst, "", "B", "B", "", "B", "B", 0}, |
| { "B", "xAB", IsFirstReconcile::kNotFirst, "", "B", "xAB", "", "B", "xAB", 0}, |
| { "B", "BxA", IsFirstReconcile::kNotFirst, "", "B", "BxA", "", "B", "BxA", 0}, |
| { "*xA", "", IsFirstReconcile::kNotFirst, "", "*xA", "", "", "*xA", "", 0}, |
| { "*xA", "xAxB", IsFirstReconcile::kNotFirst, "", "*xA", "xAxB", "", "*xA", "xAxB", 0}, |
| { "*xA", "xBxA", IsFirstReconcile::kNotFirst, "", "*xA", "xBxA", "", "*xA", "xBxA", 0}, |
| { "*xA", "xA", IsFirstReconcile::kNotFirst, "", "*xA", "xA", "", "*xA", "xA", 0}, |
| { "*xA", "xB", IsFirstReconcile::kNotFirst, "", "*xA", "xB", "", "*xA", "xB", 0}, |
| { "*xAB", "B", IsFirstReconcile::kNotFirst, "", "*xAB", "B", "", "*xAB", "B", 0}, |
| { "*xAB", "BxA", IsFirstReconcile::kNotFirst, "", "*xAB", "BxA", "", "*xAB", "BxA", 0}, |
| { "*xAB", "xAB", IsFirstReconcile::kNotFirst, "", "*xAB", "xAB", "", "*xAB", "xAB", 0}, |
| { "*xAB", "xABxC",IsFirstReconcile::kNotFirst, "", "*xAB", "xABxC", "", "*xAB", "xABxC", 0}, |
| { "*xB", "", IsFirstReconcile::kNotFirst, "", "*xB", "", "", "*xB", "", 0}, |
| { "A*B", "BA", IsFirstReconcile::kNotFirst, "", "A*B", "BA", "", "A*B", "BA", 0}, |
| { "A*B", "AB", IsFirstReconcile::kNotFirst, "", "A*B", "AB", "", "A*B", "AB", 0}, |
| { "A", "AxC", IsFirstReconcile::kNotFirst, "", "A", "AxC", "", "A", "AxC", 0}, |
| { "AB", "BxCA", IsFirstReconcile::kNotFirst, "", "AB", "BxCA", "", "AB", "BxCA", 0}, |
| { "B", "xABxC",IsFirstReconcile::kNotFirst, "", "B", "xABxC", "", "B", "xABxC", 0}, |
| { "B", "xAxCB",IsFirstReconcile::kNotFirst, "", "B", "xAxCB", "", "B", "xAxCB", 0}, |
| { "*ABC", "ACB", IsFirstReconcile::kNotFirst, "", "*ABC", "ACB", "", "*ABC", "ACB", 0}, |
| { "*ABC", "ABC", IsFirstReconcile::kNotFirst, "", "*ABC", "ABC", "", "*ABC", "ABC", 0}, |
| { "BC", "BC", IsFirstReconcile::kNotFirst, "", "BC", "BC", "", "BC", "BC", 0}, |
| { "BC", "CB", IsFirstReconcile::kNotFirst, "", "BC", "CB", "", "BC", "CB", 0}, |
| }; |
| // clang-format on |
| |
| // Checks one row of the kDiceParams table above. |
| TEST_P(AccountReconcilorTestTable, TableRowTest) { |
| // Enable Dice. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| // Check that reconcile is idempotent: when called twice in a row it should do |
| // nothing on the second call. |
| CheckReconcileIdempotent(kDiceParams, GetParam(), /*multilogin=*/false); |
| RunReconcile(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| DiceTable, |
| AccountReconcilorTestTable, |
| ::testing::ValuesIn(GenerateTestCasesFromParams(kDiceParams))); |
| |
| class AccountReconcilorTestForceDiceMigration |
| : public BaseAccountReconcilorTestTable, |
| public ::testing::WithParamInterface<ForceDiceMigrationTestTableParam> { |
| public: |
| AccountReconcilorTestForceDiceMigration() |
| : BaseAccountReconcilorTestTable(GetParam().tokens, |
| GetParam().cookies, |
| IsFirstReconcile::kFirst, |
| GetParam().gaia_api_calls, |
| GetParam().tokens_after_reconcile, |
| GetParam().cookies_after_reconcile) {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTestForceDiceMigration); |
| }; |
| |
| // clang-format off |
| const std::vector<ForceDiceMigrationTestTableParam> kForceDiceParams = { |
| {"*A", "AB", "XA", "*A", "A" }, |
| {"*AxB", "AB", "XA", "*A", "A" }, |
| {"AxB", "AB", "XA", "A", "A" }, |
| {"xAxB", "AB", "X", "", "" }, |
| {"*A", "", "", "*xA", "" }, |
| {"*A", "B", "X", "*xA", "" }, |
| {"*AB", "B", "", "*xAB", "B" }, |
| {"*AxB", "B", "X", "*xA", "" }, |
| {"*ABC", "CB", "", "*xABC", "CB" }, |
| {"*AB", "A", "", "*A", "A" }, |
| {"AB", "A", "", "A", "A" }, |
| {"AB", "", "", "", "" }, |
| {"xAB", "", "", "", "" }, |
| {"xAB", "A", "X", "", "" }, |
| {"xAB", "xA", "", "", "xA" }, |
| {"xAB", "B", "", "B", "B" }, |
| {"AxB", "B", "X", "", "" }, |
| {"AxB", "", "", "", "" }, |
| {"xAxB", "", "", "", "" }, |
| {"B", "xA", "", "", "xA" }, |
| {"AB", "xAB", "", "B", "xAB" }, |
| {"xAB", "xAC", "X", "", "" }, |
| {"xAB", "AxC", "X", "", "" }, |
| {"AB", "BC", "XB", "B", "B" }, |
| {"*AB", "", "", "*xA", "" }, |
| {"*xAB", "", "", "*xA", "" }, |
| {"*AxB", "", "", "*xA", "" }, |
| {"*AB", "xBxA", "", "*xA", "xBxA"} |
| }; |
| // clang-format on |
| |
| // Checks one row of the kForceDiceParams table above. |
| TEST_P(AccountReconcilorTestForceDiceMigration, TableRowTest) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| EXPECT_FALSE(test_signin_client()->is_dice_migration_completed()); |
| SetDiceMigrationCompleted(false); |
| RunReconcile(); |
| EXPECT_TRUE(test_signin_client()->is_dice_migration_completed()); |
| EXPECT_FALSE( |
| GetMockReconcilor()->delegate_->ShouldRevokeTokensNotInCookies()); |
| } |
| |
| // Check that the result state of the reconcile is in a final state (reconcile |
| // started from this state is a no-op). |
| TEST_P(AccountReconcilorTestForceDiceMigration, TableRowTestCheckNoOp) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| // Setup cookies. |
| std::vector<Cookie> cookies = ParseCookieString(cookies_after_reconcile_); |
| ConfigureCookieManagerService(cookies); |
| |
| // Call list accounts now so that the next call completes synchronously. |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Setup tokens. This triggers listing cookies so we need to setup cookies |
| // before that. |
| SetupTokens(tokens_after_reconcile_); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_)) |
| .Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| EXPECT_FALSE(reconcilor->delegate_->ShouldRevokeTokensNotInCookies()); |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(DiceMigrationTable, |
| AccountReconcilorTestForceDiceMigration, |
| ::testing::ValuesIn(kForceDiceParams)); |
| |
| // Parameterized version of AccountReconcilorTest that tests Dice |
| // implementation with Multilogin endpoint. |
| class AccountReconcilorTestDiceMultilogin : public AccountReconcilorTestTable { |
| public: |
| AccountReconcilorTestDiceMultilogin() = default; |
| |
| protected: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTestDiceMultilogin); |
| }; |
| |
| // Checks one row of the kDiceParams table above. |
| TEST_P(AccountReconcilorTestDiceMultilogin, TableRowTest) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| scoped_feature_list_.InitAndEnableFeature(kUseMultiloginEndpoint); |
| |
| CheckReconcileIdempotent(kDiceParams, GetParam(), /*multilogin=*/true); |
| |
| // Setup cookies. |
| std::vector<Cookie> cookies = ParseCookieString(GetParam().cookies); |
| ConfigureCookieManagerService(cookies); |
| std::vector<Cookie> cookies_after_reconcile = cookies; |
| |
| // Call list accounts now so that the next call completes synchronously. |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Setup tokens. This triggers listing cookies so we need to setup cookies |
| // before that. |
| SetupTokens(GetParam().tokens); |
| |
| // Setup expectations. |
| testing::InSequence mock_sequence; |
| if (GetParam().gaia_api_calls_multilogin[0] != '\0') { |
| gaia::MultiloginMode mode = |
| GetParam().gaia_api_calls_multilogin[0] == 'U' |
| ? gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER |
| : gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER; |
| // Generate expected array of accounts in cookies and set fake gaia |
| // response. |
| std::vector<CoreAccountId> accounts_to_send; |
| for (int i = 1; GetParam().gaia_api_calls_multilogin[i] != '\0'; ++i) { |
| accounts_to_send.push_back(CoreAccountId( |
| accounts_[GetParam().gaia_api_calls_multilogin[i]].gaia_id)); |
| } |
| const signin::MultiloginParameters params(mode, accounts_to_send); |
| cookies_after_reconcile = FakeSetAccountsInCookie(params, cookies); |
| if (accounts_to_send.empty() && |
| (mode == |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER)) { |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(1); |
| } else { |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)) |
| .Times(1); |
| } |
| } |
| // Reconcile. |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->first_execution_); |
| reconcilor->first_execution_ = |
| GetParam().is_first_reconcile == IsFirstReconcile::kFirst ? true : false; |
| reconcilor->StartReconcile(); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| if (GetParam().tokens == GetParam().tokens_after_reconcile_multilogin) { |
| EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } else { |
| // If the tokens were changed by the reconcile, a new reconcile should be |
| // scheduled. |
| EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED, |
| reconcilor->GetState()); |
| } |
| VerifyCurrentTokens( |
| ParseTokenString(GetParam().tokens_after_reconcile_multilogin)); |
| |
| std::vector<Cookie> cookies_after = |
| ParseCookieString(GetParam().cookies_after_reconcile_multilogin); |
| EXPECT_EQ(cookies_after, cookies_after_reconcile); |
| |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| |
| // Another reconcile is sometimes triggered if Chrome accounts have |
| // changed. Allow it to finish. |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_)) |
| .WillRepeatedly(testing::Return()); |
| ConfigureCookieManagerService({}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| DiceTableMultilogin, |
| AccountReconcilorTestDiceMultilogin, |
| ::testing::ValuesIn(GenerateTestCasesFromParams(kDiceParams))); |
| |
| class AccountReconcilorDiceEndpointParamTest |
| : public AccountReconcilorTest, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| AccountReconcilorDiceEndpointParamTest() { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| if (IsMultiloginEnabled()) |
| scoped_feature_list_.InitAndEnableFeature(kUseMultiloginEndpoint); |
| } |
| bool IsMultiloginEnabled() { return GetParam(); } |
| |
| protected: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorDiceEndpointParamTest); |
| }; |
| |
| // Tests that the AccountReconcilor is always registered. |
| TEST_P(AccountReconcilorDiceEndpointParamTest, DiceTokenServiceRegistration) { |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); |
| |
| identity_test_env()->MakePrimaryAccountAvailable("user@gmail.com"); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); |
| |
| // Reconcilor should not logout all accounts from the cookies when |
| // the primary account is cleared in IdentityManager. |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(::testing::_)) |
| .Times(0); |
| |
| identity_test_env()->ClearPrimaryAccount(); |
| ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); |
| } |
| |
| // Tests that reconcile starts even when Sync is not enabled. |
| TEST_P(AccountReconcilorDiceEndpointParamTest, DiceReconcileWithoutSignin) { |
| // Add a token in Chrome but do not sign in. Making account available (setting |
| // a refresh token) triggers listing cookies so we need to setup cookies |
| // before that. |
| signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_); |
| const CoreAccountId account_id = |
| identity_test_env()->MakeAccountAvailable("user@gmail.com").account_id; |
| |
| if (!IsMultiloginEnabled()) { |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| } else { |
| std::vector<CoreAccountId> accounts_to_send = {account_id}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| } |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| if (!IsMultiloginEnabled()) { |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id, GoogleServiceAuthError::AuthErrorNone()); |
| } else { |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| } |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Checks that nothing happens when there is no Chrome account and no Gaia |
| // cookie. |
| TEST_P(AccountReconcilorDiceEndpointParamTest, DiceReconcileNoop) { |
| // No Chrome account and no cookie. |
| signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_)) |
| .Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Tests that the first Gaia account is re-used when possible. |
| TEST_P(AccountReconcilorDiceEndpointParamTest, |
| DiceReconcileReuseGaiaFirstAccount) { |
| // Add account "other" to the Gaia cookie. |
| signin::SetListAccountsResponseTwoAccounts( |
| "other@gmail.com", signin::GetTestGaiaIdForEmail("other@gmail.com"), |
| "foo@gmail.com", "9999", &test_url_loader_factory_); |
| |
| // Add accounts "user" and "other" to the token service. |
| const AccountInfo account_info_1 = |
| identity_test_env()->MakeAccountAvailable("user@gmail.com"); |
| const CoreAccountId account_id_1 = account_info_1.account_id; |
| const AccountInfo account_info_2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com"); |
| const CoreAccountId account_id_2 = account_info_2.account_id; |
| |
| auto* identity_manager = identity_test_env()->identity_manager(); |
| std::vector<CoreAccountInfo> accounts = |
| identity_manager->GetAccountsWithRefreshTokens(); |
| ASSERT_EQ(2u, accounts.size()); |
| ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1)); |
| ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_2)); |
| |
| if (!IsMultiloginEnabled()) { |
| testing::InSequence mock_sequence; |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| // Account 2 is added first. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_2)); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_1)); |
| } else { |
| std::vector<CoreAccountId> accounts_to_send = {account_id_2, account_id_1}; |
| // Send accounts to Gaia in order of chrome accounts. Account 2 is added |
| // first. |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| } |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| if (!IsMultiloginEnabled()) { |
| SimulateLogOutFromCookieCompleted(reconcilor, |
| GoogleServiceAuthError::AuthErrorNone()); |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id_1, GoogleServiceAuthError::AuthErrorNone()); |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id_2, GoogleServiceAuthError::AuthErrorNone()); |
| } else { |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| } |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Tests that the first account is kept in cache and reused when cookies are |
| // lost. |
| TEST_P(AccountReconcilorDiceEndpointParamTest, DiceLastKnownFirstAccount) { |
| // Add accounts to the token service and the Gaia cookie in a different order. |
| // Making account available (setting a refresh token) triggers listing cookies |
| // so we need to setup cookies before that. |
| signin::SetListAccountsResponseTwoAccounts( |
| "other@gmail.com", signin::GetTestGaiaIdForEmail("other@gmail.com"), |
| "user@gmail.com", signin::GetTestGaiaIdForEmail("user@gmail.com"), |
| &test_url_loader_factory_); |
| |
| AccountInfo account_info_1 = |
| identity_test_env()->MakeAccountAvailable("user@gmail.com"); |
| const CoreAccountId account_id_1 = account_info_1.account_id; |
| AccountInfo account_info_2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com"); |
| const CoreAccountId account_id_2 = account_info_2.account_id; |
| |
| auto* identity_manager = identity_test_env()->identity_manager(); |
| std::vector<CoreAccountInfo> accounts = |
| identity_manager->GetAccountsWithRefreshTokens(); |
| ASSERT_EQ(2u, accounts.size()); |
| |
| ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1)); |
| ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_2)); |
| |
| // Do one reconcile. It should do nothing but to populating the last known |
| // account. |
| { |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_)) |
| .Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Delete the cookies. |
| signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_); |
| identity_test_env()->SetFreshnessOfAccountsInGaiaCookie(false); |
| |
| if (!IsMultiloginEnabled()) { |
| // Reconcile again and check that account_id_2 is added first. |
| testing::InSequence mock_sequence; |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_2)) |
| .Times(1); |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_1)) |
| .Times(1); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(0); |
| } else { |
| // Since Gaia can't know about cached account, make sure that we reorder |
| // chrome accounts accordingly even in PRESERVE mode. |
| std::vector<CoreAccountId> accounts_to_send = {account_id_2, account_id_1}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| } |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| if (!IsMultiloginEnabled()) { |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id_2, GoogleServiceAuthError::AuthErrorNone()); |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id_1, GoogleServiceAuthError::AuthErrorNone()); |
| } else { |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| } |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Checks that the reconcilor does not log out unverified accounts. |
| TEST_P(AccountReconcilorDiceEndpointParamTest, UnverifiedAccountNoop) { |
| // Add a unverified account to the Gaia cookie. |
| signin::SetListAccountsResponseOneAccountWithParams( |
| {"user@gmail.com", "12345", true /* valid */, false /* signed_out */, |
| false /* verified */}, |
| &test_url_loader_factory_); |
| |
| // Check that nothing happens. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(testing::_)).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()).Times(0); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_)) |
| .Times(0); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Checks that the reconcilor does not log out unverified accounts when adding |
| // a new account to the Gaia cookie. |
| TEST_P(AccountReconcilorDiceEndpointParamTest, UnverifiedAccountMerge) { |
| // Add a unverified account to the Gaia cookie. |
| signin::SetListAccountsResponseOneAccountWithParams( |
| {"user@gmail.com", "12345", true /* valid */, false /* signed_out */, |
| false /* verified */}, |
| &test_url_loader_factory_); |
| |
| // Add a token to Chrome. |
| const CoreAccountId chrome_account_id = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; |
| |
| if (!IsMultiloginEnabled()) { |
| // Check that the Chrome account is merged and the unverified account is not |
| // logged out. |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(chrome_account_id)) |
| .Times(1); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(0); |
| } else { |
| // In PRESERVE mode it is up to Gaia to not delete existing accounts in |
| // cookies and not sign out unveridied accounts. |
| std::vector<CoreAccountId> accounts_to_send = {chrome_account_id}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| } |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| if (!IsMultiloginEnabled()) { |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, chrome_account_id, GoogleServiceAuthError::AuthErrorNone()); |
| } else { |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| } |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(TestDiceEndpoint, |
| AccountReconcilorDiceEndpointParamTest, |
| ::testing::ValuesIn({false, true})); |
| |
| TEST_F(AccountReconcilorTest, DiceDeleteCookie) { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kDice); |
| |
| const CoreAccountId primary_account_id = |
| identity_test_env() |
| ->MakePrimaryAccountAvailable("user@gmail.com") |
| .account_id; |
| const CoreAccountId secondary_account_id = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; |
| |
| auto* identity_manager = identity_test_env()->identity_manager(); |
| ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(primary_account_id)); |
| ASSERT_FALSE( |
| identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( |
| primary_account_id)); |
| ASSERT_TRUE( |
| identity_manager->HasAccountWithRefreshToken(secondary_account_id)); |
| ASSERT_FALSE( |
| identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( |
| secondary_account_id)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| |
| // With scoped deletion, only secondary tokens are revoked. |
| { |
| std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> deletion = |
| reconcilor->GetScopedSyncDataDeletion(); |
| reconcilor->OnAccountsCookieDeletedByUserAction(); |
| EXPECT_TRUE( |
| identity_manager->HasAccountWithRefreshToken(primary_account_id)); |
| EXPECT_FALSE( |
| identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( |
| primary_account_id)); |
| EXPECT_FALSE( |
| identity_manager->HasAccountWithRefreshToken(secondary_account_id)); |
| } |
| |
| identity_test_env()->SetRefreshTokenForAccount(secondary_account_id); |
| reconcilor->OnAccountsCookieDeletedByUserAction(); |
| |
| // Without scoped deletion, the primary token is also invalidated. |
| EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(primary_account_id)); |
| EXPECT_TRUE( |
| identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( |
| primary_account_id)); |
| EXPECT_EQ(GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_CLIENT, |
| identity_manager |
| ->GetErrorStateOfRefreshTokenForAccount(primary_account_id) |
| .GetInvalidGaiaCredentialsReason()); |
| EXPECT_FALSE( |
| identity_manager->HasAccountWithRefreshToken(secondary_account_id)); |
| |
| // If the primary account has an error, always revoke it. |
| identity_test_env()->SetRefreshTokenForAccount(primary_account_id); |
| EXPECT_FALSE( |
| identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( |
| primary_account_id)); |
| signin::UpdatePersistentErrorOfRefreshTokenForAccount( |
| identity_test_env()->identity_manager(), primary_account_id, |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_SERVER)); |
| { |
| std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> deletion = |
| reconcilor->GetScopedSyncDataDeletion(); |
| reconcilor->OnAccountsCookieDeletedByUserAction(); |
| EXPECT_EQ(GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_CLIENT, |
| identity_manager |
| ->GetErrorStateOfRefreshTokenForAccount(primary_account_id) |
| .GetInvalidGaiaCredentialsReason()); |
| } |
| } |
| |
| #endif // BUILDFLAG(ENABLE_DICE_SUPPORT) |
| |
| // clang-format off |
| const std::vector<AccountReconcilorTestTableParam> kMirrorParams = { |
| // This table encodes the initial state and expectations of a reconcile. |
| // See kDiceParams for documentation of the syntax. |
| // ------------------------------------------------------------------------- |
| // Tokens | Cookies | First Run | Gaia calls | Tokens after | Cookies after |
| // ------------------------------------------------------------------------- |
| |
| // First reconcile (Chrome restart): Rebuild the Gaia cookie to match the |
| // tokens. Make the Sync account the default account in the Gaia cookie. |
| // Sync enabled. |
| { "*AB", "AB", IsFirstReconcile::kBoth, "", "*AB", "AB"}, |
| { "*AB", "BA", IsFirstReconcile::kBoth, "U", "*AB", "AB"}, |
| { "*AB", "A", IsFirstReconcile::kBoth, "U", "*AB", "AB"}, |
| { "*AB", "B", IsFirstReconcile::kBoth, "U", "*AB", "AB"}, |
| { "*AB", "", IsFirstReconcile::kBoth, "U", "*AB", "AB"}, |
| // Sync enabled, token error on primary. |
| // Sync enabled, token error on secondary. |
| { "*AxB", "AB", IsFirstReconcile::kBoth, "U", "*AxB", "A"}, |
| { "*AxB", "BA", IsFirstReconcile::kBoth, "U", "*AxB", "A"}, |
| { "*AxB", "A", IsFirstReconcile::kBoth, "", "*AxB", ""}, |
| { "*AxB", "B", IsFirstReconcile::kBoth, "U", "*AxB", "A"}, |
| { "*AxB", "", IsFirstReconcile::kBoth, "U", "*AxB", "A"}, |
| |
| // Cookies can be refreshed in pace, without logout. |
| { "*AB", "xBxA", IsFirstReconcile::kBoth, "U", "*AB", "AB"}, |
| |
| // Check that unknown Gaia accounts are signed out. |
| { "*A", "AB", IsFirstReconcile::kBoth, "U", "*A", "A"}, |
| // Check that the previous case is idempotent. |
| { "*A", "A", IsFirstReconcile::kBoth, "", "*A", ""}, |
| }; |
| // clang-format on |
| |
| // Parameterized version of AccountReconcilorTest that tests Mirror |
| // implementation with Multilogin endpoint. |
| class AccountReconcilorTestMirrorMultilogin |
| : public AccountReconcilorTestTable { |
| public: |
| AccountReconcilorTestMirrorMultilogin() = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTestMirrorMultilogin); |
| }; |
| |
| // Checks one row of the kDiceParams table above. |
| TEST_P(AccountReconcilorTestMirrorMultilogin, TableRowTest) { |
| // Enable Mirror. |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| |
| // Setup cookies. |
| std::vector<Cookie> cookies = ParseCookieString(GetParam().cookies); |
| ConfigureCookieManagerService(cookies); |
| |
| // Call list accounts now so that the next call completes synchronously. |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Setup tokens. |
| SetupTokens(GetParam().tokens); |
| |
| // Setup expectations. |
| testing::InSequence mock_sequence; |
| bool logout_action = false; |
| for (int i = 0; GetParam().gaia_api_calls[i] != '\0'; ++i) { |
| if (GetParam().gaia_api_calls[i] == 'X') { |
| logout_action = true; |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(1); |
| cookies.clear(); |
| continue; |
| } |
| if (GetParam().gaia_api_calls[i] == 'U') { |
| std::vector<CoreAccountId> accounts_to_send; |
| for (int i = 0; GetParam().cookies_after_reconcile[i] != '\0'; ++i) { |
| char cookie = GetParam().cookies_after_reconcile[i]; |
| std::string account_to_send = GaiaIdForAccountKey(cookie); |
| accounts_to_send.push_back(PickAccountIdForAccount( |
| account_to_send, |
| accounts_[GetParam().cookies_after_reconcile[i]].email)); |
| } |
| const signin::MultiloginParameters ml_params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(ml_params)) |
| .Times(1); |
| } |
| } |
| if (!logout_action) { |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(0); |
| } |
| |
| // Reconcile. |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->first_execution_); |
| reconcilor->first_execution_ = |
| GetParam().is_first_reconcile == IsFirstReconcile::kFirst ? true : false; |
| reconcilor->StartReconcile(); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| VerifyCurrentTokens(ParseTokenString(GetParam().tokens_after_reconcile)); |
| |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| |
| // Another reconcile is sometimes triggered if Chrome accounts have |
| // changed. Allow it to finish. |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(testing::_)) |
| .WillRepeatedly(testing::Return()); |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .WillRepeatedly(testing::Return()); |
| ConfigureCookieManagerService({}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| MirrorTableMultilogin, |
| AccountReconcilorTestMirrorMultilogin, |
| ::testing::ValuesIn(GenerateTestCasesFromParams(kMirrorParams))); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| class AccountReconcilorTestActiveDirectory : public AccountReconcilorTestTable { |
| public: |
| AccountReconcilorTestActiveDirectory() = default; |
| |
| void SetUp() override { |
| SetAccountConsistency(signin::AccountConsistencyMethod::kMirror); |
| } |
| |
| private: |
| chromeos::ScopedStubInstallAttributes install_attributes_{ |
| chromeos::StubInstallAttributes::CreateActiveDirectoryManaged( |
| "realm.com", |
| "device_id")}; |
| |
| DISALLOW_COPY_AND_ASSIGN(AccountReconcilorTestActiveDirectory); |
| }; |
| |
| // clang-format off |
| const std::vector<AccountReconcilorTestTableParam> kActiveDirectoryParams = { |
| // This table encodes the initial state and expectations of a reconcile. |
| // See kDiceParams for documentation of the syntax. |
| // ------------------------------------------------------------------------- |
| // Tokens |Cookies |First Run |Gaia calls|Tokens aft.|Cookies aft | |
| // ------------------------------------------------------------------------- |
| { "ABC", "ABC", IsFirstReconcile::kBoth, "" , "ABC", "ABC" }, |
| { "ABC", "", IsFirstReconcile::kBoth, "U", "ABC", "ABC" }, |
| { "", "ABC", IsFirstReconcile::kBoth, "X", "", "", }, |
| // Order of Gaia accounts can be different from chrome accounts. |
| { "ABC", "CBA", IsFirstReconcile::kBoth, "" , "ABC", "CBA" }, |
| { "ABC", "CB", IsFirstReconcile::kBoth, "U", "ABC", "CBA" }, |
| // Gaia accounts which are not present in chrome accounts should be removed. In |
| // this case Gaia accounts are going to be in the same order as chrome accounts. |
| // this case Gaia accounts are going to be in thcousame order as chromcnts. |
| { "A", "AB", IsFirstReconcile::kBoth, "U", "A", "A" }, |
| { "AB", "CBA", IsFirstReconcile::kBoth, "U", "AB", "AB" }, |
| { "AB", "C", IsFirstReconcile::kBoth, "U", "AB", "AB" }, |
| // Cookies can be refreshed in pace, without logout. |
| { "AB", "xAxB", IsFirstReconcile::kBoth, "U", "AB", "AB" }, |
| // Token error on the account - remove it from cookies |
| { "AxB", "AB", IsFirstReconcile::kBoth, "U", "AxB", "A" }, |
| { "xAxB", "AB", IsFirstReconcile::kBoth, "X", "xAxB", "" }, |
| }; |
| // clang-format on |
| |
| TEST_P(AccountReconcilorTestActiveDirectory, TableRowTestMultilogin) { |
| // Setup cookies. |
| std::vector<Cookie> cookies = ParseCookieString(GetParam().cookies); |
| ConfigureCookieManagerService(cookies); |
| |
| // Call list accounts now so that the next call completes synchronously. |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Setup tokens. |
| std::vector<Token> tokens = ParseTokenString(GetParam().tokens); |
| SetupTokens(GetParam().tokens); |
| |
| testing::InSequence mock_sequence; |
| DeleteReconcilor(); |
| MockAccountReconcilor* reconcilor = CreateMockReconcilor( |
| std::make_unique<signin::ActiveDirectoryAccountReconcilorDelegate>()); |
| |
| // Setup expectations. |
| bool logout_action = false; |
| for (int i = 0; GetParam().gaia_api_calls[i] != '\0'; ++i) { |
| if (GetParam().gaia_api_calls[i] == 'X') { |
| logout_action = true; |
| EXPECT_CALL(*reconcilor, PerformLogoutAllAccountsAction()).Times(1); |
| cookies.clear(); |
| continue; |
| } |
| if (GetParam().gaia_api_calls[i] == 'U') { |
| std::vector<CoreAccountId> accounts_to_send; |
| for (int i = 0; GetParam().cookies_after_reconcile[i] != '\0'; ++i) { |
| char cookie = GetParam().cookies_after_reconcile[i]; |
| std::string account_to_send = GaiaIdForAccountKey(cookie); |
| accounts_to_send.push_back(PickAccountIdForAccount( |
| account_to_send, |
| accounts_[GetParam().cookies_after_reconcile[i]].email)); |
| } |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*reconcilor, PerformSetCookiesAction(params)).Times(1); |
| } |
| } |
| if (!logout_action) { |
| EXPECT_CALL(*reconcilor, PerformLogoutAllAccountsAction()).Times(0); |
| } |
| |
| // Reconcile. |
| ASSERT_TRUE(reconcilor); |
| ASSERT_TRUE(reconcilor->first_execution_); |
| reconcilor->first_execution_ = |
| GetParam().is_first_reconcile == IsFirstReconcile::kFirst ? true : false; |
| reconcilor->StartReconcile(); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| VerifyCurrentTokens(ParseTokenString(GetParam().tokens_after_reconcile)); |
| |
| testing::Mock::VerifyAndClearExpectations(reconcilor); |
| |
| // Another reconcile is sometimes triggered if Chrome accounts have |
| // changed. Allow it to finish. |
| EXPECT_CALL(*reconcilor, PerformSetCookiesAction(testing::_)) |
| .WillRepeatedly(testing::Return()); |
| EXPECT_CALL(*reconcilor, PerformLogoutAllAccountsAction()) |
| .WillRepeatedly(testing::Return()); |
| ConfigureCookieManagerService({}); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| ActiveDirectoryTable, |
| AccountReconcilorTestActiveDirectory, |
| ::testing::ValuesIn(GenerateTestCasesFromParams(kActiveDirectoryParams))); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // Tests that reconcile cannot start before the tokens are loaded, and is |
| // automatically started when tokens are loaded. |
| TEST_F(AccountReconcilorMirrorTest, TokensNotLoaded) { |
| const CoreAccountId account_id = |
| ConnectProfileToAccount("user@gmail.com").account_id; |
| signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_); |
| identity_test_env()->ResetToAccountsNotYetLoadedFromDiskState(); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| // No reconcile when tokens are not loaded. |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| // When tokens are loaded, reconcile starts automatically. |
| identity_test_env()->ReloadAccountsFromDisk(); |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, GetAccountsFromCookieSuccess) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| signin::SetListAccountsResponseOneAccountWithParams( |
| {account_info.email, account_info.gaia, false /* valid */, |
| false /* signed_out */, true /* verified */}, |
| &test_url_loader_factory_); |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED, |
| reconcilor->GetState()); |
| reconcilor->StartReconcile(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| |
| signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info = |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| ASSERT_TRUE(accounts_in_cookie_jar_info.accounts_are_fresh); |
| ASSERT_EQ(1u, accounts_in_cookie_jar_info.signed_in_accounts.size()); |
| ASSERT_EQ(account_id, accounts_in_cookie_jar_info.signed_in_accounts[0].id); |
| ASSERT_EQ(0u, accounts_in_cookie_jar_info.signed_out_accounts.size()); |
| } |
| |
| // Checks that calling EnableReconcile() while the reconcilor is already running |
| // doesn't have any effect. Regression test for https://crbug.com/1043651 |
| TEST_F(AccountReconcilorMirrorTest, EnableReconcileWhileAlreadyRunning) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| signin::SetListAccountsResponseOneAccountWithParams( |
| {account_info.email, account_info.gaia, false /* valid */, |
| false /* signed_out */, true /* verified */}, |
| &test_url_loader_factory_); |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED, |
| reconcilor->GetState()); |
| reconcilor->StartReconcile(); |
| EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| reconcilor->EnableReconcile(); |
| EXPECT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| |
| signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info = |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| ASSERT_TRUE(accounts_in_cookie_jar_info.accounts_are_fresh); |
| ASSERT_EQ(1u, accounts_in_cookie_jar_info.signed_in_accounts.size()); |
| ASSERT_EQ(account_id, accounts_in_cookie_jar_info.signed_in_accounts[0].id); |
| ASSERT_EQ(0u, accounts_in_cookie_jar_info.signed_out_accounts.size()); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, GetAccountsFromCookieFailure) { |
| ConnectProfileToAccount("user@gmail.com"); |
| signin::SetListAccountsResponseWithUnexpectedServiceResponse( |
| &test_url_loader_factory_); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED, |
| reconcilor->GetState()); |
| reconcilor->StartReconcile(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| base::RunLoop().RunUntilIdle(); |
| |
| signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info = |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| ASSERT_FALSE(accounts_in_cookie_jar_info.accounts_are_fresh); |
| ASSERT_EQ(0u, accounts_in_cookie_jar_info.signed_in_accounts.size()); |
| ASSERT_EQ(0u, accounts_in_cookie_jar_info.signed_out_accounts.size()); |
| // List accounts retries once on |UNEXPECTED_SERVICE_RESPONSE| errors with |
| // backoff protection. |
| task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2)); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_ERROR, reconcilor->GetState()); |
| } |
| |
| // Regression test for https://crbug.com/923716 |
| TEST_F(AccountReconcilorMirrorTest, ExtraCookieChangeNotification) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| signin::CookieParams cookie_params = { |
| account_info.email, account_info.gaia, false /* valid */, |
| false /* signed_out */, true /* verified */}; |
| |
| signin::SetListAccountsResponseOneAccountWithParams( |
| cookie_params, &test_url_loader_factory_); |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_SCHEDULED, |
| reconcilor->GetState()); |
| reconcilor->StartReconcile(); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); |
| |
| // Add extra cookie change notification. Reconcilor should ignore it. |
| gaia::ListedAccount listed_account = |
| ListedAccountFromCookieParams(cookie_params, account_id); |
| signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info = { |
| /*accounts_are_fresh=*/true, {listed_account}, {}}; |
| reconcilor->OnAccountsInCookieUpdated( |
| accounts_in_cookie_jar_info, GoogleServiceAuthError::AuthErrorNone()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileNoop) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileCookiesDisabled) { |
| const CoreAccountId account_id = |
| ConnectProfileToAccount("user@gmail.com").account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| test_signin_client()->set_are_signin_cookies_allowed(false); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| std::vector<gaia::ListedAccount> accounts; |
| // This will be the first call to ListAccounts. |
| signin::AccountsInCookieJarInfo accounts_in_cookie_jar_info = |
| identity_test_env()->identity_manager()->GetAccountsInCookieJar(); |
| ASSERT_FALSE(accounts_in_cookie_jar_info.accounts_are_fresh); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileContentSettings) { |
| const CoreAccountId account_id = |
| ConnectProfileToAccount("user@gmail.com").account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| test_signin_client()->set_are_signin_cookies_allowed(false); |
| SimulateCookieContentSettingsChanged(reconcilor, |
| ContentSettingsPattern::Wildcard()); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| test_signin_client()->set_are_signin_cookies_allowed(true); |
| SimulateCookieContentSettingsChanged(reconcilor, |
| ContentSettingsPattern::Wildcard()); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileContentSettingsGaiaUrl) { |
| const CoreAccountId account_id = |
| ConnectProfileToAccount("user@gmail.com").account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| SimulateCookieContentSettingsChanged( |
| reconcilor, |
| ContentSettingsPattern::FromURL(GaiaUrls::GetInstance()->gaia_url())); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileContentSettingsNonGaiaUrl) { |
| const CoreAccountId account_id = |
| ConnectProfileToAccount("user@gmail.com").account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| SimulateCookieContentSettingsChanged( |
| reconcilor, |
| ContentSettingsPattern::FromURL(GURL("http://www.example.com"))); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, |
| StartReconcileContentSettingsInvalidPattern) { |
| const CoreAccountId account_id = |
| ConnectProfileToAccount("user@gmail.com").account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| std::unique_ptr<ContentSettingsPattern::BuilderInterface> builder = |
| ContentSettingsPattern::CreateBuilder(); |
| builder->Invalid(); |
| |
| SimulateCookieContentSettingsChanged(reconcilor, builder->Build()); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| } |
| |
| // This test is needed until chrome changes to use gaia obfuscated id. |
| // The primary account manager and token service use the gaia "email" property, |
| // which preserves dots in usernames and preserves case. |
| // gaia::ParseListAccountsData() however uses gaia "displayEmail" which does not |
| // preserve case, and then passes the string through gaia::CanonicalizeEmail() |
| // which removes dots. This tests makes sure that an email like |
| // "Dot.S@hmail.com", as seen by the token service, will be considered the same |
| // as "dots@gmail.com" as returned by gaia::ParseListAccountsData(). |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileNoopWithDots) { |
| if (identity_test_env()->identity_manager()->GetAccountIdMigrationState() != |
| signin::IdentityManager::AccountIdMigrationState::MIGRATION_NOT_STARTED) { |
| return; |
| } |
| |
| AccountInfo account_info = ConnectProfileToAccount("Dot.S@gmail.com"); |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileNoopMultiple) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| AccountInfo account_info_2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com"); |
| signin::SetListAccountsResponseTwoAccounts( |
| account_info.email, account_info.gaia, account_info_2.email, |
| account_info_2.gaia, &test_url_loader_factory_); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileAddToCookie) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| |
| const CoreAccountId account_id2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id, account_id2}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| base::HistogramTester::CountsMap expected_counts; |
| expected_counts["Signin.Reconciler.Duration.UpTo3mins.Success"] = 1; |
| EXPECT_THAT(histogram_tester()->GetTotalCountsForPrefix( |
| "Signin.Reconciler.Duration.UpTo3mins.Success"), |
| testing::ContainerEq(expected_counts)); |
| } |
| |
| TEST_F(AccountReconcilorTest, AuthErrorTriggersListAccount) { |
| class TestGaiaCookieObserver : public signin::IdentityManager::Observer { |
| public: |
| void OnAccountsInCookieUpdated( |
| const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, |
| const GoogleServiceAuthError& error) override { |
| cookies_updated_ = true; |
| } |
| |
| bool cookies_updated_ = false; |
| }; |
| |
| #if BUILDFLAG(ENABLE_DICE_SUPPORT) |
| signin::AccountConsistencyMethod account_consistency = |
| signin::AccountConsistencyMethod::kDice; |
| SetAccountConsistency(account_consistency); |
| #else |
| signin::AccountConsistencyMethod account_consistency = |
| signin::AccountConsistencyMethod::kMirror; |
| SetAccountConsistency(account_consistency); |
| #endif |
| |
| // Add one account to Chrome and instantiate the reconcilor. |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| TestGaiaCookieObserver observer; |
| identity_test_env()->identity_manager()->AddObserver(&observer); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| if (account_consistency == signin::AccountConsistencyMethod::kDice) { |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) |
| .Times(1); |
| } |
| |
| // Set an authentication error. |
| ASSERT_FALSE(observer.cookies_updated_); |
| signin::UpdatePersistentErrorOfRefreshTokenForAccount( |
| identity_test_env()->identity_manager(), account_id, |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_SERVER)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that a call to ListAccount was triggered. |
| EXPECT_TRUE(observer.cookies_updated_); |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| |
| identity_test_env()->identity_manager()->RemoveObserver(&observer); |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| // This test does not run on ChromeOS because it clears the primary account, |
| // which is not a flow that exists on ChromeOS. |
| |
| TEST_F(AccountReconcilorMirrorTest, SignoutAfterErrorDoesNotRecordUma) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| |
| const CoreAccountId account_id2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id, account_id2}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kPersistentError); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); |
| identity_test_env()->ClearPrimaryAccount(); |
| |
| base::HistogramTester::CountsMap expected_counts; |
| expected_counts["Signin.Reconciler.Duration.UpTo3mins.Failure"] = 1; |
| } |
| |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileRemoveFromCookie) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| identity_test_env()->SetRefreshTokenForAccount(account_id); |
| signin::SetListAccountsResponseTwoAccounts( |
| account_info.email, account_info.gaia, "other@gmail.com", "12345", |
| &test_url_loader_factory_); |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| // Check that reconcile is aborted if there is token error on primary account. |
| TEST_F(AccountReconcilorMirrorTest, TokenErrorOnPrimary) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| signin::UpdatePersistentErrorOfRefreshTokenForAccount( |
| identity_test_env()->identity_manager(), account_info.account_id, |
| GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); |
| |
| signin::SetListAccountsResponseTwoAccounts( |
| account_info.email, account_info.gaia, "other@gmail.com", "67890", |
| &test_url_loader_factory_); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileAddToCookieTwice) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| AccountInfo account_info2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com"); |
| const CoreAccountId account_id2 = account_info2.account_id; |
| |
| const std::string email3 = "third@gmail.com"; |
| const std::string gaia_id3 = signin::GetTestGaiaIdForEmail(email3); |
| const CoreAccountId account_id3 = PickAccountIdForAccount(gaia_id3, email3); |
| |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| |
| std::vector<CoreAccountId> accounts_to_send_1 = {account_id, account_id2}; |
| const signin::MultiloginParameters ml_params_1( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send_1); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(ml_params_1)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| |
| |
| // Do another pass after I've added a third account to the token service |
| signin::SetListAccountsResponseTwoAccounts( |
| account_info.email, account_info.gaia, account_info2.email, |
| account_info2.gaia, &test_url_loader_factory_); |
| identity_test_env()->SetFreshnessOfAccountsInGaiaCookie(false); |
| |
| // This will cause the reconcilor to fire. |
| identity_test_env()->MakeAccountAvailable(email3); |
| std::vector<CoreAccountId> accounts_to_send_2 = {account_id, account_id2, |
| account_id3}; |
| const signin::MultiloginParameters ml_params_2( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send_2); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(ml_params_2)); |
| base::RunLoop().RunUntilIdle(); |
| |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileBadPrimary) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| |
| AccountInfo account_info2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com"); |
| const CoreAccountId account_id2 = account_info2.account_id; |
| signin::SetListAccountsResponseTwoAccounts( |
| account_info2.email, account_info2.gaia, account_info.email, |
| account_info.gaia, &test_url_loader_factory_); |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id, account_id2}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| reconcilor->StartReconcile(); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, StartReconcileOnlyOnce) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, Lock) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_); |
| |
| class TestAccountReconcilorObserver : public AccountReconcilor::Observer { |
| public: |
| void OnStateChanged(AccountReconcilorState state) override { |
| if (state == AccountReconcilorState::ACCOUNT_RECONCILOR_RUNNING) { |
| ++started_count_; |
| } |
| } |
| void OnBlockReconcile() override { ++blocked_count_; } |
| void OnUnblockReconcile() override { ++unblocked_count_; } |
| |
| int started_count_ = 0; |
| int blocked_count_ = 0; |
| int unblocked_count_ = 0; |
| }; |
| |
| TestAccountReconcilorObserver observer; |
| base::ScopedObservation<AccountReconcilor, AccountReconcilor::Observer> |
| scoped_observation(&observer); |
| scoped_observation.Observe(reconcilor); |
| |
| // Lock prevents reconcile from starting, as long as one instance is alive. |
| std::unique_ptr<AccountReconcilor::Lock> lock_1 = |
| std::make_unique<AccountReconcilor::Lock>(reconcilor); |
| EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_); |
| reconcilor->StartReconcile(); |
| // lock_1 is blocking the reconcile. |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| { |
| AccountReconcilor::Lock lock_2(reconcilor); |
| EXPECT_EQ(2, reconcilor->account_reconcilor_lock_count_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| lock_1.reset(); |
| // lock_1 is no longer blocking, but lock_2 is still alive. |
| EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| EXPECT_EQ(0, observer.started_count_); |
| EXPECT_EQ(0, observer.unblocked_count_); |
| EXPECT_EQ(1, observer.blocked_count_); |
| } |
| |
| // All locks are deleted, reconcile starts. |
| EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| EXPECT_EQ(1, observer.started_count_); |
| EXPECT_EQ(1, observer.unblocked_count_); |
| EXPECT_EQ(1, observer.blocked_count_); |
| |
| // Lock aborts current reconcile, and restarts it later. |
| { |
| AccountReconcilor::Lock lock(reconcilor); |
| EXPECT_EQ(1, reconcilor->account_reconcilor_lock_count_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| EXPECT_EQ(0, reconcilor->account_reconcilor_lock_count_); |
| EXPECT_TRUE(reconcilor->is_reconcile_started_); |
| EXPECT_EQ(2, observer.started_count_); |
| EXPECT_EQ(2, observer.unblocked_count_); |
| EXPECT_EQ(2, observer.blocked_count_); |
| |
| // Reconcile can complete successfully after being restarted. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| // Checks that an "invalid" Gaia account can be refreshed in place, without |
| // performing a full logout. |
| TEST_P(AccountReconcilorMethodParamTest, |
| StartReconcileWithSessionInfoExpiredDefault) { |
| signin::AccountConsistencyMethod account_consistency = GetParam(); |
| SetAccountConsistency(account_consistency); |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| AccountInfo account_info2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com"); |
| const CoreAccountId account_id2 = account_info2.account_id; |
| signin::SetListAccountsResponseWithParams( |
| {{account_info.email, account_info.gaia, false /* valid */, |
| false /* signed_out */, true /* verified */}, |
| {account_info2.email, account_info2.gaia, true /* valid */, |
| false /* signed_out */, true /* verified */}}, |
| &test_url_loader_factory_); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| if (!reconcilor->IsMultiloginEndpointEnabled()) { |
| EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); |
| } else { |
| switch (account_consistency) { |
| case signin::AccountConsistencyMethod::kMirror: { |
| signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| {account_id, account_id2}); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| break; |
| } |
| case signin::AccountConsistencyMethod::kDice: { |
| signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER, |
| {account_id2, account_id}); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| break; |
| } |
| case signin::AccountConsistencyMethod::kDisabled: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| if (!reconcilor->IsMultiloginEndpointEnabled()) { |
| SimulateAddAccountToCookieCompleted( |
| reconcilor, account_id, GoogleServiceAuthError::AuthErrorNone()); |
| } else { |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| } |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, |
| AddAccountToCookieCompletedWithBogusAccount) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id = account_info.account_id; |
| signin::SetListAccountsResponseOneAccountWithParams( |
| {account_info.email, account_info.gaia, false /* valid */, |
| false /* signed_out */, true /* verified */}, |
| &test_url_loader_factory_); |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // If an unknown account id is sent, it should not upset the state. |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, NoLoopWithBadPrimary) { |
| // Connect profile to a primary account and then add a secondary account. |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| const CoreAccountId account_id1 = account_info.account_id; |
| AccountInfo account_info2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com"); |
| const CoreAccountId account_id2 = account_info2.account_id; |
| |
| std::vector<CoreAccountId> accounts_to_send = {account_id1, account_id2}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| // The primary account is in auth error, so it is not in the cookie. |
| signin::SetListAccountsResponseOneAccountWithParams( |
| {account_info2.email, account_info2.gaia, false /* valid */, |
| false /* signed_out */, true /* verified */}, |
| &test_url_loader_factory_); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| GoogleServiceAuthError error( |
| GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); |
| |
| // The primary cannot be added to cookie, so it fails. |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kPersistentError); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_NE(GoogleServiceAuthError::State::NONE, |
| reconcilor->error_during_last_reconcile_.state()); |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| |
| // Now that we've tried once, the token service knows that the primary |
| // account has an auth error. |
| signin::UpdatePersistentErrorOfRefreshTokenForAccount( |
| identity_test_env()->identity_manager(), account_id1, error); |
| |
| // A second attempt to reconcile should be a noop. |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| testing::Mock::VerifyAndClearExpectations(GetMockReconcilor()); |
| } |
| |
| TEST_F(AccountReconcilorMirrorTest, WontMergeAccountsWithError) { |
| // Connect profile to a primary account and then add a secondary account. |
| const CoreAccountId account_id1 = |
| ConnectProfileToAccount("user@gmail.com").account_id; |
| const CoreAccountId account_id2 = |
| identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; |
| |
| // Mark the secondary account in auth error state. |
| signin::UpdatePersistentErrorOfRefreshTokenForAccount( |
| identity_test_env()->identity_manager(), account_id2, |
| GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); |
| |
| // The cookie starts empty. |
| signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_); |
| |
| // Since the cookie jar starts empty, the reconcilor should attempt to merge |
| // accounts into it. However, it should only try accounts not in auth |
| // error state. |
| std::vector<CoreAccountId> accounts_to_send = {account_id1}; |
| const signin::MultiloginParameters params( |
| gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, |
| accounts_to_send); |
| EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); |
| |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| |
| reconcilor->StartReconcile(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| |
| SimulateSetAccountsInCookieCompleted( |
| reconcilor, signin::SetAccountsInCookieResult::kSuccess); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(GoogleServiceAuthError::State::NONE, |
| reconcilor->error_during_last_reconcile_.state()); |
| } |
| |
| // Test that delegate timeout is called when the delegate offers a valid |
| // timeout. |
| TEST_F(AccountReconcilorTest, DelegateTimeoutIsCalled) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>(); |
| SpyReconcilorDelegate* spy_delegate = spy_delegate0.get(); |
| AccountReconcilor* reconcilor = |
| CreateMockReconcilor(std::move(spy_delegate0)); |
| ASSERT_TRUE(reconcilor); |
| auto timer0 = std::make_unique<base::MockOneShotTimer>(); |
| base::MockOneShotTimer* timer = timer0.get(); |
| reconcilor->set_timer_for_testing(std::move(timer0)); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| ASSERT_TRUE(timer->IsRunning()); |
| |
| // Simulate a timeout |
| timer->Fire(); |
| EXPECT_EQ(1, spy_delegate->num_reconcile_timeout_calls_); |
| EXPECT_EQ(0, spy_delegate->num_reconcile_finished_calls_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| // Test that delegate timeout is not called when the delegate does not offer a |
| // valid timeout. |
| TEST_F(AccountReconcilorMirrorTest, DelegateTimeoutIsNotCalled) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| auto timer0 = std::make_unique<base::MockOneShotTimer>(); |
| base::MockOneShotTimer* timer = timer0.get(); |
| reconcilor->set_timer_for_testing(std::move(timer0)); |
| |
| reconcilor->StartReconcile(); |
| EXPECT_TRUE(reconcilor->is_reconcile_started_); |
| EXPECT_FALSE(timer->IsRunning()); |
| } |
| |
| TEST_F(AccountReconcilorTest, DelegateTimeoutIsNotCalledIfTimeoutIsNotReached) { |
| AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); |
| signin::SetListAccountsResponseOneAccount( |
| account_info.email, account_info.gaia, &test_url_loader_factory_); |
| auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>(); |
| SpyReconcilorDelegate* spy_delegate = spy_delegate0.get(); |
| AccountReconcilor* reconcilor = |
| CreateMockReconcilor(std::move(spy_delegate0)); |
| ASSERT_TRUE(reconcilor); |
| auto timer0 = std::make_unique<base::MockOneShotTimer>(); |
| base::MockOneShotTimer* timer = timer0.get(); |
| reconcilor->set_timer_for_testing(std::move(timer0)); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| ASSERT_TRUE(timer->IsRunning()); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(timer->IsRunning()); |
| EXPECT_EQ(0, spy_delegate->num_reconcile_timeout_calls_); |
| EXPECT_EQ(1, spy_delegate->num_reconcile_finished_calls_); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| TEST_F(AccountReconcilorTest, ScopedSyncedDataDeletionDestructionOrder) { |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> data_deletion = |
| reconcilor->GetScopedSyncDataDeletion(); |
| DeleteReconcilor(); |
| // data_deletion is destroyed after the reconcilor, this should not crash. |
| } |
| |
| TEST_F(AccountReconcilorTest, LockDestructionOrder) { |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| AccountReconcilor::Lock lock(reconcilor); |
| DeleteReconcilor(); |
| // |lock| is destroyed after the reconcilor, this should not crash. |
| } |
| |
| // Checks that multilogin with empty list of accounts in UPDATE mode is changed |
| // into a Logout call. |
| TEST_F(AccountReconcilorTest, MultiloginLogout) { |
| // Delegate implementation always returning UPDATE mode with no accounts. |
| class MultiloginLogoutDelegate : public signin::AccountReconcilorDelegate { |
| bool IsReconcileEnabled() const override { return true; } |
| bool IsAccountConsistencyEnforced() const override { return true; } |
| std::vector<CoreAccountId> GetChromeAccountsForReconcile( |
| const std::vector<CoreAccountId>& chrome_accounts, |
| const CoreAccountId& primary_account, |
| const std::vector<gaia::ListedAccount>& gaia_accounts, |
| bool first_execution, |
| bool primary_has_error, |
| const gaia::MultiloginMode mode) const override { |
| return {}; |
| } |
| gaia::MultiloginMode CalculateModeForReconcile( |
| const std::vector<CoreAccountId>& chrome_accounts, |
| const std::vector<gaia::ListedAccount>& gaia_accounts, |
| const CoreAccountId& primary_account, |
| bool first_execution, |
| bool primary_has_error) const override { |
| return gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER; |
| } |
| }; |
| |
| MockAccountReconcilor* reconcilor = |
| CreateMockReconcilor(std::make_unique<MultiloginLogoutDelegate>()); |
| signin::SetListAccountsResponseOneAccount("user@gmail.com", "123456", |
| &test_url_loader_factory_); |
| |
| // Logout call to Gaia. |
| EXPECT_CALL(*reconcilor, PerformLogoutAllAccountsAction()); |
| // No multilogin call. |
| EXPECT_CALL(*reconcilor, PerformSetCookiesAction(testing::_)).Times(0); |
| |
| reconcilor->StartReconcile(); |
| ASSERT_TRUE(reconcilor->is_reconcile_started_); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); |
| } |
| |
| // Reconcilor does not start after being shutdown. Regression test for |
| // https://crbug.com/923094 |
| TEST_F(AccountReconcilorTest, ReconcileAfterShutdown) { |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| EXPECT_FALSE(reconcilor->WasShutDown()); |
| reconcilor->Shutdown(); |
| EXPECT_TRUE(reconcilor->WasShutDown()); |
| reconcilor->StartReconcile(); // This should not crash. |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |
| |
| // Reconcilor does not unlock after being shutdown. Regression test for |
| // https://crbug.com/923094 |
| TEST_F(AccountReconcilorTest, UnlockAfterShutdown) { |
| AccountReconcilor* reconcilor = GetMockReconcilor(); |
| ASSERT_TRUE(reconcilor); |
| std::unique_ptr<AccountReconcilor::Lock> lock = |
| std::make_unique<AccountReconcilor::Lock>(reconcilor); |
| |
| // Reconcile does not start now because of the Lock, but is scheduled to start |
| // when the lock is released. |
| reconcilor->StartReconcile(); |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| |
| reconcilor->Shutdown(); |
| lock.reset(); // This should not crash. |
| EXPECT_FALSE(reconcilor->is_reconcile_started_); |
| } |