| // Copyright 2018 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 "components/sync/driver/sync_auth_manager.h" |
| |
| #include "base/callback_helpers.h" |
| #include "base/run_loop.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "components/signin/public/identity_manager/identity_test_environment.h" |
| #include "components/signin/public/identity_manager/primary_account_mutator.h" |
| #include "components/sync/driver/sync_driver_switches.h" |
| #include "components/sync/engine/connection_status.h" |
| #include "components/sync/engine/sync_credentials.h" |
| #include "net/base/net_errors.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace syncer { |
| |
| namespace { |
| |
| class SyncAuthManagerTest : public testing::Test { |
| protected: |
| using AccountStateChangedCallback = |
| SyncAuthManager::AccountStateChangedCallback; |
| using CredentialsChangedCallback = |
| SyncAuthManager::CredentialsChangedCallback; |
| |
| SyncAuthManagerTest() : identity_env_(&test_url_loader_factory_) {} |
| |
| ~SyncAuthManagerTest() override {} |
| |
| std::unique_ptr<SyncAuthManager> CreateAuthManager() { |
| return CreateAuthManager(base::DoNothing(), base::DoNothing()); |
| } |
| |
| std::unique_ptr<SyncAuthManager> CreateAuthManager( |
| const AccountStateChangedCallback& account_state_changed, |
| const CredentialsChangedCallback& credentials_changed) { |
| return std::make_unique<SyncAuthManager>(identity_env_.identity_manager(), |
| account_state_changed, |
| credentials_changed); |
| } |
| |
| std::unique_ptr<SyncAuthManager> CreateAuthManagerForLocalSync() { |
| return std::make_unique<SyncAuthManager>(nullptr, base::DoNothing(), |
| base::DoNothing()); |
| } |
| |
| signin::IdentityTestEnvironment* identity_env() { return &identity_env_; } |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| signin::IdentityTestEnvironment identity_env_; |
| }; |
| |
| TEST_F(SyncAuthManagerTest, ProvidesNothingInLocalSyncMode) { |
| auto auth_manager = CreateAuthManagerForLocalSync(); |
| EXPECT_TRUE(auth_manager->GetActiveAccountInfo().account_info.IsEmpty()); |
| syncer::SyncCredentials credentials = auth_manager->GetCredentials(); |
| EXPECT_TRUE(credentials.email.empty()); |
| EXPECT_TRUE(credentials.access_token.empty()); |
| EXPECT_TRUE(auth_manager->access_token().empty()); |
| // Note: Calling RegisterForAuthNotifications or any of the Connection*() |
| // methods is illegal in local Sync mode, so we don't test that. |
| } |
| |
| TEST_F(SyncAuthManagerTest, IgnoresEventsIfNotRegistered) { |
| base::MockCallback<AccountStateChangedCallback> account_state_changed; |
| base::MockCallback<CredentialsChangedCallback> credentials_changed; |
| EXPECT_CALL(account_state_changed, Run()).Times(0); |
| EXPECT_CALL(credentials_changed, Run()).Times(0); |
| auto auth_manager = |
| CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); |
| |
| // Fire some auth events. We haven't called RegisterForAuthNotifications, so |
| // none of this should result in any callback calls. |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| // Without RegisterForAuthNotifications, the active account should always be |
| // reported as empty. |
| EXPECT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| identity_env()->SetRefreshTokenForPrimaryAccount(); |
| EXPECT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| |
| // ChromeOS doesn't support sign-out. |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| identity_env()->ClearPrimaryAccount(); |
| EXPECT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| // ChromeOS doesn't support sign-out. |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| TEST_F(SyncAuthManagerTest, ForwardsPrimaryAccountEvents) { |
| // Start out already signed in before the SyncAuthManager is created. |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| |
| base::MockCallback<AccountStateChangedCallback> account_state_changed; |
| base::MockCallback<CredentialsChangedCallback> credentials_changed; |
| EXPECT_CALL(account_state_changed, Run()).Times(0); |
| EXPECT_CALL(credentials_changed, Run()).Times(0); |
| auto auth_manager = |
| CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); |
| |
| auth_manager->RegisterForAuthNotifications(); |
| |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| // Sign out of the account. |
| EXPECT_CALL(account_state_changed, Run()); |
| // Note: The ordering of removing the refresh token and the actual sign-out is |
| // undefined, see comment on IdentityManager::Observer. So we might or might |
| // not get a |credentials_changed| call here. |
| EXPECT_CALL(credentials_changed, Run()).Times(testing::AtMost(1)); |
| identity_env()->ClearPrimaryAccount(); |
| EXPECT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| |
| // Sign in to a different account. |
| EXPECT_CALL(account_state_changed, Run()); |
| CoreAccountId second_account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| second_account_id); |
| } |
| |
| TEST_F(SyncAuthManagerTest, NotifiesOfSignoutBeforeAccessTokenIsGone) { |
| // Start out already signed in before the SyncAuthManager is created. |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| |
| base::MockCallback<AccountStateChangedCallback> account_state_changed; |
| base::MockCallback<CredentialsChangedCallback> credentials_changed; |
| auto auth_manager = |
| CreateAuthManager(account_state_changed.Get(), base::DoNothing()); |
| |
| auth_manager->RegisterForAuthNotifications(); |
| |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| |
| // Make sure an access token is available. |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // Sign out of the account. |
| EXPECT_CALL(account_state_changed, Run()).WillOnce([&]() { |
| // At the time the callback gets run, the access token should still be here. |
| EXPECT_FALSE(auth_manager->GetCredentials().access_token.empty()); |
| }); |
| identity_env()->ClearPrimaryAccount(); |
| // After the signout is complete, the access token should be gone. |
| EXPECT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // Unconsented primary accounts (aka secondary accounts) are only supported on |
| // Win/Mac/Linux. |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID) && !defined(OS_IOS) |
| TEST_F(SyncAuthManagerTest, ForwardsSecondaryAccountEvents) { |
| base::MockCallback<AccountStateChangedCallback> account_state_changed; |
| base::MockCallback<CredentialsChangedCallback> credentials_changed; |
| EXPECT_CALL(account_state_changed, Run()).Times(0); |
| EXPECT_CALL(credentials_changed, Run()).Times(0); |
| auto auth_manager = |
| CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); |
| auth_manager->RegisterForAuthNotifications(); |
| |
| ASSERT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| |
| // Make a non-primary account available with both a refresh token and cookie. |
| EXPECT_CALL(account_state_changed, Run()); |
| AccountInfo account_info = |
| identity_env()->MakeUnconsentedPrimaryAccountAvailable("test@email.com"); |
| |
| EXPECT_FALSE(auth_manager->GetActiveAccountInfo().is_primary); |
| EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_info.account_id); |
| |
| // Make the account primary. |
| EXPECT_CALL(account_state_changed, Run()); |
| signin::PrimaryAccountMutator* primary_account_mutator = |
| identity_env()->identity_manager()->GetPrimaryAccountMutator(); |
| primary_account_mutator->SetPrimaryAccount(account_info.account_id, |
| signin::ConsentLevel::kSync); |
| |
| EXPECT_TRUE(auth_manager->GetActiveAccountInfo().is_primary); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID) && |
| // !defined(OS_IOS) |
| |
| // ChromeOS doesn't support sign-out. |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) |
| TEST_F(SyncAuthManagerTest, ClearsAuthErrorOnSignout) { |
| // Start out already signed in before the SyncAuthManager is created. |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| |
| auto auth_manager = CreateAuthManager(); |
| |
| auth_manager->RegisterForAuthNotifications(); |
| |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| ASSERT_EQ(auth_manager->GetLastAuthError().state(), |
| GoogleServiceAuthError::NONE); |
| |
| // Sign out of the account. |
| // The ordering of removing the refresh token and the actual sign-out is |
| // undefined, see comment on IdentityManager::Observer. Here, explicitly |
| // revoke the refresh token first to force an auth error. |
| identity_env()->RemoveRefreshTokenForPrimaryAccount(); |
| |
| ASSERT_NE(auth_manager->GetLastAuthError().state(), |
| GoogleServiceAuthError::NONE); |
| |
| // Now actually sign out, i.e. remove the primary account. This should clear |
| // the auth error, since it's now not meaningful anymore. |
| identity_env()->ClearPrimaryAccount(); |
| EXPECT_EQ(auth_manager->GetLastAuthError().state(), |
| GoogleServiceAuthError::NONE); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| TEST_F(SyncAuthManagerTest, DoesNotClearAuthErrorOnSyncDisable) { |
| // Start out already signed in before the SyncAuthManager is created. |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| |
| auto auth_manager = CreateAuthManager(); |
| |
| auth_manager->RegisterForAuthNotifications(); |
| |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| ASSERT_EQ(auth_manager->GetLastAuthError().state(), |
| GoogleServiceAuthError::NONE); |
| |
| auth_manager->ConnectionOpened(); |
| |
| // Force an auth error by revoking the refresh token. |
| identity_env()->RemoveRefreshTokenForPrimaryAccount(); |
| ASSERT_NE(auth_manager->GetLastAuthError().state(), |
| GoogleServiceAuthError::NONE); |
| |
| // Now Sync gets turned off, e.g. because the user disabled it. |
| auth_manager->ConnectionClosed(); |
| |
| // Since the user is still signed in, the auth error should have remained. |
| EXPECT_NE(auth_manager->GetLastAuthError().state(), |
| GoogleServiceAuthError::NONE); |
| } |
| |
| TEST_F(SyncAuthManagerTest, ForwardsCredentialsEvents) { |
| // Start out already signed in before the SyncAuthManager is created. |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| |
| base::MockCallback<AccountStateChangedCallback> account_state_changed; |
| base::MockCallback<CredentialsChangedCallback> credentials_changed; |
| EXPECT_CALL(account_state_changed, Run()).Times(0); |
| EXPECT_CALL(credentials_changed, Run()).Times(0); |
| auto auth_manager = |
| CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); |
| |
| auth_manager->RegisterForAuthNotifications(); |
| |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| |
| // Once an access token is available, the callback should get run. |
| EXPECT_CALL(credentials_changed, Run()); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // Now the refresh token gets updated. The access token will get dropped, so |
| // this should cause another notification. |
| EXPECT_CALL(credentials_changed, Run()); |
| identity_env()->SetRefreshTokenForPrimaryAccount(); |
| ASSERT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| |
| // Once a new token is available, there's another notification. |
| EXPECT_CALL(credentials_changed, Run()); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token_2", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token_2"); |
| |
| // Revoking the refresh token should also cause the access token to get |
| // dropped. |
| EXPECT_CALL(credentials_changed, Run()); |
| identity_env()->RemoveRefreshTokenForPrimaryAccount(); |
| EXPECT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| } |
| |
| TEST_F(SyncAuthManagerTest, RequestsAccessTokenOnSyncStartup) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| |
| EXPECT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| } |
| |
| TEST_F(SyncAuthManagerTest, |
| RetriesAccessTokenFetchWithBackoffOnTransientFailure) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( |
| GoogleServiceAuthError::FromConnectionError(net::ERR_TIMED_OUT)); |
| |
| // The access token fetch should get retried (with backoff, hence no actual |
| // request yet), without exposing an auth error. |
| EXPECT_TRUE(auth_manager->IsRetryingAccessTokenFetchForTest()); |
| EXPECT_EQ(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| } |
| |
| TEST_F( |
| SyncAuthManagerTest, |
| RetriesAccessTokenFetchWithBackoffOnFirstCancelTransientFailWhenDisabled) { |
| // Disable the first retry without backoff on cancellation. |
| base::test::ScopedFeatureList local_feature; |
| local_feature.InitAndDisableFeature(kSyncRetryFirstCanceledTokenFetch); |
| |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( |
| GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| |
| // Expect retry with backoff. |
| EXPECT_TRUE(auth_manager->IsRetryingAccessTokenFetchForTest()); |
| } |
| |
| TEST_F(SyncAuthManagerTest, |
| RetriesAccessTokenFetchWithoutBackoffOnceOnFirstCancelTransientFailure) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( |
| GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| |
| // Expect no backoff the first time the request is canceled. |
| EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest()); |
| |
| // Cancel the retry as well. |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( |
| GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| |
| // Expect retry with backoff when the first retry was also canceled. |
| EXPECT_TRUE(auth_manager->IsRetryingAccessTokenFetchForTest()); |
| } |
| |
| TEST_F(SyncAuthManagerTest, |
| RetriesAccessTokenFetchOnFirstCancelTransientFailure) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( |
| GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); |
| |
| // Expect no backoff the first time the request is canceled. |
| EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest()); |
| |
| // Retry is a success. |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| // Don't expect any backoff when the retry is a success. |
| EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest()); |
| } |
| |
| TEST_F(SyncAuthManagerTest, AbortsAccessTokenFetchOnPersistentFailure) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| |
| GoogleServiceAuthError auth_error = |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_SERVER); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithError( |
| auth_error); |
| |
| // Auth error should get exposed; no retry. |
| EXPECT_FALSE(auth_manager->IsRetryingAccessTokenFetchForTest()); |
| EXPECT_EQ(auth_manager->GetLastAuthError(), auth_error); |
| } |
| |
| TEST_F(SyncAuthManagerTest, FetchesNewAccessTokenWithBackoffOnServerError) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // The server is returning AUTH_ERROR - maybe something's wrong with the |
| // token we got. |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR); |
| |
| // The access token fetch should get retried (with backoff, hence no actual |
| // request yet), without exposing an auth error. |
| EXPECT_TRUE(auth_manager->IsRetryingAccessTokenFetchForTest()); |
| EXPECT_EQ(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| } |
| |
| TEST_F(SyncAuthManagerTest, ExposesServerError) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // Now a server error happens. |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_SERVER_ERROR); |
| |
| // The error should be reported. |
| EXPECT_NE(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| // But the access token should still be there - this might just be some |
| // non-auth-related problem with the server. |
| EXPECT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| } |
| |
| TEST_F(SyncAuthManagerTest, ClearsServerErrorOnSyncDisable) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // A server error happens. |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_SERVER_ERROR); |
| ASSERT_NE(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| |
| // Now Sync gets turned off, e.g. because the user disabled it. |
| auth_manager->ConnectionClosed(); |
| |
| // This should have cleared the auth error, because it was due to a server |
| // error which is now not meaningful anymore. |
| EXPECT_EQ(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| } |
| |
| TEST_F(SyncAuthManagerTest, RequestsNewAccessTokenOnExpiry) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // Now everything is okay for a while. |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| ASSERT_EQ(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| |
| // But then the token expires, resulting in an auth error from the server. |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_AUTH_ERROR); |
| |
| // Should immediately drop the access token and fetch a new one (no backoff). |
| EXPECT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token_2", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| EXPECT_EQ(auth_manager->GetCredentials().access_token, "access_token_2"); |
| } |
| |
| TEST_F(SyncAuthManagerTest, RequestsNewAccessTokenOnRefreshTokenUpdate) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // Now everything is okay for a while. |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| ASSERT_EQ(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| |
| // But then the refresh token changes. |
| identity_env()->SetRefreshTokenForPrimaryAccount(); |
| |
| // Should immediately drop the access token and fetch a new one (no backoff). |
| EXPECT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token_2", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| EXPECT_EQ(auth_manager->GetCredentials().access_token, "access_token_2"); |
| } |
| |
| TEST_F(SyncAuthManagerTest, DoesNotRequestAccessTokenAutonomously) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| // Do *not* call ConnectionStatusChanged here (which is what usually kicks off |
| // the token fetch). |
| |
| // Now the refresh token gets updated. If we already had an access token |
| // before, then this should trigger a new fetch. But since that initial fetch |
| // never happened (e.g. because Sync is turned off), this should do nothing. |
| base::MockCallback<base::OnceClosure> access_token_requested; |
| EXPECT_CALL(access_token_requested, Run()).Times(0); |
| identity_env()->SetCallbackForNextAccessTokenRequest( |
| access_token_requested.Get()); |
| identity_env()->SetRefreshTokenForPrimaryAccount(); |
| |
| // Make sure no access token request was sent. Since the request goes through |
| // posted tasks, we have to spin the message loop. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| } |
| |
| TEST_F(SyncAuthManagerTest, ClearsCredentialsOnRefreshTokenRemoval) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // Now everything is okay for a while. |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| ASSERT_EQ(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| |
| // But then the refresh token gets revoked. No new access token should get |
| // requested due to this. |
| base::MockCallback<base::OnceClosure> access_token_requested; |
| EXPECT_CALL(access_token_requested, Run()).Times(0); |
| identity_env()->SetCallbackForNextAccessTokenRequest( |
| access_token_requested.Get()); |
| identity_env()->RemoveRefreshTokenForPrimaryAccount(); |
| |
| // Should immediately drop the access token and expose an auth error. |
| EXPECT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| EXPECT_NE(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| |
| // No new access token should have been requested. Since the request goes |
| // through posted tasks, we have to spin the message loop. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(SyncAuthManagerTest, ClearsCredentialsOnInvalidRefreshToken) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| auth_manager->ConnectionOpened(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| |
| // Now everything is okay for a while. |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| ASSERT_EQ(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| |
| // But now an invalid refresh token gets set, i.e. we enter the "Sync paused" |
| // state. No new access token should get requested due to this. |
| base::MockCallback<base::OnceClosure> access_token_requested; |
| EXPECT_CALL(access_token_requested, Run()).Times(0); |
| identity_env()->SetCallbackForNextAccessTokenRequest( |
| access_token_requested.Get()); |
| identity_env()->SetInvalidRefreshTokenForPrimaryAccount(); |
| |
| // Should immediately drop the access token and expose a special auth error. |
| EXPECT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| GoogleServiceAuthError invalid_token_error = |
| GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( |
| GoogleServiceAuthError::InvalidGaiaCredentialsReason:: |
| CREDENTIALS_REJECTED_BY_CLIENT); |
| EXPECT_EQ(auth_manager->GetLastAuthError(), invalid_token_error); |
| EXPECT_TRUE(auth_manager->IsSyncPaused()); |
| |
| // No new access token should have been requested. Since the request goes |
| // through posted tasks, we have to spin the message loop. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(SyncAuthManagerTest, |
| RequestsAccessTokenWhenInvalidRefreshTokenResolved) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| // Sync starts up normally. |
| auth_manager->ConnectionOpened(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| auth_manager->ConnectionStatusChanged(syncer::CONNECTION_OK); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token"); |
| ASSERT_EQ(auth_manager->GetLastAuthError(), |
| GoogleServiceAuthError::AuthErrorNone()); |
| |
| // But now an invalid refresh token gets set, i.e. we enter the "Sync paused" |
| // state. |
| identity_env()->SetInvalidRefreshTokenForPrimaryAccount(); |
| ASSERT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| ASSERT_TRUE(auth_manager->IsSyncPaused()); |
| |
| // Once the user signs in again and we have a valid refresh token, we should |
| // also request a new access token. |
| identity_env()->SetRefreshTokenForPrimaryAccount(); |
| identity_env()->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken( |
| "access_token_2", base::Time::Now() + base::TimeDelta::FromHours(1)); |
| ASSERT_EQ(auth_manager->GetCredentials().access_token, "access_token_2"); |
| } |
| |
| TEST_F(SyncAuthManagerTest, DoesNotRequestAccessTokenIfSyncInactive) { |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| |
| base::MockCallback<AccountStateChangedCallback> account_state_changed; |
| base::MockCallback<CredentialsChangedCallback> credentials_changed; |
| EXPECT_CALL(account_state_changed, Run()).Times(0); |
| EXPECT_CALL(credentials_changed, Run()).Times(0); |
| auto auth_manager = |
| CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| // Sync is *not* enabled; in particular we don't call ConnectionOpened(). |
| |
| // An invalid refresh token gets set, i.e. we enter the "Sync paused" state |
| // (only from SyncAuthManager's point of view - Sync as a whole is still |
| // disabled). |
| EXPECT_CALL(credentials_changed, Run()); |
| identity_env()->SetInvalidRefreshTokenForPrimaryAccount(); |
| ASSERT_TRUE(auth_manager->GetCredentials().access_token.empty()); |
| ASSERT_TRUE(auth_manager->IsSyncPaused()); |
| |
| // Once the user signs in again and we have a valid refresh token, we should |
| // *not* request a new access token, since Sync isn't active. |
| base::MockCallback<base::OnceClosure> access_token_requested; |
| EXPECT_CALL(access_token_requested, Run()).Times(0); |
| identity_env()->SetCallbackForNextAccessTokenRequest( |
| access_token_requested.Get()); |
| // This *should* notify about changed credentials though, so that the |
| // SyncService can decide to start syncing. |
| EXPECT_CALL(credentials_changed, Run()); |
| identity_env()->SetRefreshTokenForPrimaryAccount(); |
| ASSERT_FALSE(auth_manager->IsSyncPaused()); |
| |
| // Since the access token request goes through posted tasks, we have to spin |
| // the message loop to make sure it didn't happen. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| #if !defined(OS_ANDROID) && !defined(OS_IOS) |
| // Primary account with no sync consent is not supported on Android and iOS. |
| TEST_F(SyncAuthManagerTest, PrimaryAccountWithNoSyncConsent) { |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| |
| ASSERT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| |
| // Make a primary account with no sync consent available. |
| AccountInfo account_info = |
| identity_env()->MakeUnconsentedPrimaryAccountAvailable("test@email.com"); |
| |
| // Since unconsented primary account support is enabled, SyncAuthManager |
| // should have picked up this account. |
| EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_info.account_id); |
| } |
| #endif // !defined(OS_ANDROID) && !defined(OS_IOS) |
| |
| #if !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID) && !defined(OS_IOS) |
| // Primary account with no sync consent is not supported on Android and iOS. |
| // On crOS the unconsented primary account can't be changed or removed, but can |
| // be granted sync consent. |
| TEST_F(SyncAuthManagerTest, PicksNewPrimaryAccountWithSyncConsent) { |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| |
| ASSERT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| |
| // Make a primary account with no sync consent available. |
| AccountInfo unconsented_primary_account_info = |
| identity_env()->MakeUnconsentedPrimaryAccountAvailable("test@email.com"); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| unconsented_primary_account_info.account_id); |
| |
| // Once a primary account with sync consent becomes available, the unconsented |
| // primary account should be overridden. |
| AccountInfo primary_account_info = |
| identity_env()->MakePrimaryAccountAvailable("primary@email.com"); |
| EXPECT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| primary_account_info.account_id); |
| } |
| |
| TEST_F(SyncAuthManagerTest, |
| DropsAccountWhenPrimaryAccountWithNoSyncConsentGoesAway) { |
| auto auth_manager = CreateAuthManager(); |
| auth_manager->RegisterForAuthNotifications(); |
| |
| // Make a primary account with no sync consent available. |
| AccountInfo account_info = |
| identity_env()->MakeUnconsentedPrimaryAccountAvailable("test@email.com"); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_info.account_id); |
| |
| identity_env()->ClearPrimaryAccount(); |
| EXPECT_TRUE( |
| auth_manager->GetActiveAccountInfo().account_info.account_id.empty()); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_ASH) && !defined(OS_ANDROID) && |
| // !defined(OS_IOS) |
| |
| TEST_F(SyncAuthManagerTest, DetectsInvalidRefreshTokenAtStartup) { |
| // There is a primary account, but it has an invalid refresh token (with a |
| // persistent auth error). |
| CoreAccountId account_id = |
| identity_env()->MakePrimaryAccountAvailable("test@email.com").account_id; |
| identity_env()->SetInvalidRefreshTokenForPrimaryAccount(); |
| |
| // On initialization, SyncAuthManager should pick up the auth error. This |
| // should not result in a notification. |
| base::MockCallback<AccountStateChangedCallback> account_state_changed; |
| base::MockCallback<CredentialsChangedCallback> credentials_changed; |
| EXPECT_CALL(account_state_changed, Run()).Times(0); |
| EXPECT_CALL(credentials_changed, Run()).Times(0); |
| |
| auto auth_manager = |
| CreateAuthManager(account_state_changed.Get(), credentials_changed.Get()); |
| auth_manager->RegisterForAuthNotifications(); |
| ASSERT_EQ(auth_manager->GetActiveAccountInfo().account_info.account_id, |
| account_id); |
| |
| EXPECT_TRUE(auth_manager->GetLastAuthError().IsPersistentError()); |
| } |
| |
| } // namespace |
| |
| } // namespace syncer |