| // 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 "chrome/browser/chromeos/oauth2_token_service_delegate.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/macros.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/stl_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "chromeos/account_manager/account_manager.h" |
| #include "components/signin/core/browser/account_info.h" |
| #include "components/signin/core/browser/account_tracker_service.h" |
| #include "components/signin/core/browser/signin_pref_names.h" |
| #include "components/signin/core/browser/test_signin_client.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "google_apis/gaia/oauth2_token_service.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| using account_manager::AccountType::ACCOUNT_TYPE_GAIA; |
| using account_manager::AccountType::ACCOUNT_TYPE_ACTIVE_DIRECTORY; |
| |
| class TokenServiceObserver : public OAuth2TokenService::Observer { |
| public: |
| void OnStartBatchChanges() override { |
| EXPECT_FALSE(is_inside_batch_); |
| is_inside_batch_ = true; |
| |
| // Start a new batch |
| batch_change_records_.emplace_back(std::vector<std::string>()); |
| } |
| |
| void OnEndBatchChanges() override { |
| EXPECT_TRUE(is_inside_batch_); |
| is_inside_batch_ = false; |
| } |
| |
| void OnRefreshTokenAvailable(const std::string& account_id) override { |
| EXPECT_TRUE(is_inside_batch_); |
| account_ids_.insert(account_id); |
| |
| // Record the |account_id| in the last batch. |
| batch_change_records_.rbegin()->emplace_back(account_id); |
| } |
| |
| void OnRefreshTokenRevoked(const std::string& account_id) override { |
| EXPECT_TRUE(is_inside_batch_); |
| account_ids_.erase(account_id); |
| |
| // Record the |account_id| in the last batch. |
| batch_change_records_.rbegin()->emplace_back(account_id); |
| } |
| |
| void OnAuthErrorChanged(const std::string& account_id, |
| const GoogleServiceAuthError& auth_error) override { |
| last_err_account_id_ = account_id; |
| last_err_ = auth_error; |
| } |
| |
| std::string last_err_account_id_; |
| GoogleServiceAuthError last_err_; |
| std::set<std::string> account_ids_; |
| bool is_inside_batch_ = false; |
| |
| // Records batch changes for later verification. Each index of this vector |
| // represents a batch change. Each batch change is a vector of account ids for |
| // which |OnRefreshTokenAvailable| is called. |
| std::vector<std::vector<std::string>> batch_change_records_; |
| }; |
| |
| } // namespace |
| |
| class CrOSOAuthDelegateTest : public testing::Test { |
| public: |
| CrOSOAuthDelegateTest() |
| : test_shared_loader_factory_( |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_)) {} |
| ~CrOSOAuthDelegateTest() override = default; |
| |
| protected: |
| void SetUp() override { |
| ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir()); |
| |
| account_manager_.Initialize(tmp_dir_.GetPath(), test_shared_loader_factory_, |
| immediate_callback_runner_); |
| scoped_task_environment_.RunUntilIdle(); |
| |
| pref_service_.registry()->RegisterListPref( |
| AccountTrackerService::kAccountInfoPref); |
| pref_service_.registry()->RegisterIntegerPref( |
| prefs::kAccountIdMigrationState, |
| AccountTrackerService::MIGRATION_NOT_STARTED); |
| client_ = std::make_unique<TestSigninClient>(&pref_service_); |
| |
| account_tracker_service_.Initialize(client_.get()); |
| |
| account_info_ = CreateAccountInfoTestFixture("111" /* gaia_id */, |
| "user@gmail.com" /* email */); |
| account_tracker_service_.SeedAccountInfo(account_info_); |
| |
| delegate_ = std::make_unique<ChromeOSOAuth2TokenServiceDelegate>( |
| &account_tracker_service_, &account_manager_); |
| delegate_->LoadCredentials( |
| account_info_.account_id /* primary_account_id */); |
| } |
| |
| void TearDown() override { test_shared_loader_factory_->Detach(); } |
| |
| AccountInfo CreateAccountInfoTestFixture(const std::string& gaia_id, |
| const std::string& email) { |
| AccountInfo account_info; |
| |
| account_info.gaia = gaia_id; |
| account_info.email = email; |
| account_info.full_name = "name"; |
| account_info.given_name = "name"; |
| account_info.hosted_domain = "example.com"; |
| account_info.locale = "en"; |
| account_info.picture_url = "https://example.com"; |
| account_info.is_child_account = false; |
| account_info.account_id = account_tracker_service_.PickAccountIdForAccount( |
| account_info.gaia, account_info.email); |
| |
| // Cannot use |ASSERT_TRUE| due to a |void| return type in an |ASSERT_TRUE| |
| // branch. |
| EXPECT_TRUE(account_info.IsValid()); |
| |
| return account_info; |
| } |
| |
| // Check base/test/scoped_task_environment.h. This must be the first member / |
| // declared before any member that cares about tasks. |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| |
| base::ScopedTempDir tmp_dir_; |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| scoped_refptr<network::WeakWrapperSharedURLLoaderFactory> |
| test_shared_loader_factory_; |
| AccountInfo account_info_; |
| AccountTrackerService account_tracker_service_; |
| AccountManager account_manager_; |
| std::unique_ptr<ChromeOSOAuth2TokenServiceDelegate> delegate_; |
| AccountManager::DelayNetworkCallRunner immediate_callback_runner_ = |
| base::BindRepeating( |
| [](const base::RepeatingClosure& closure) -> void { closure.Run(); }); |
| |
| private: |
| sync_preferences::TestingPrefServiceSyncable pref_service_; |
| std::unique_ptr<TestSigninClient> client_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrOSOAuthDelegateTest); |
| }; |
| |
| TEST_F(CrOSOAuthDelegateTest, RefreshTokenIsAvailableForGaiaAccounts) { |
| EXPECT_EQ(OAuth2TokenServiceDelegate::LoadCredentialsState:: |
| LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, |
| delegate_->GetLoadCredentialsState()); |
| |
| EXPECT_FALSE(delegate_->RefreshTokenIsAvailable(account_info_.account_id)); |
| |
| const AccountManager::AccountKey account_key{account_info_.gaia, |
| ACCOUNT_TYPE_GAIA}; |
| account_manager_.UpsertToken(account_key, "token"); |
| |
| EXPECT_TRUE(delegate_->RefreshTokenIsAvailable(account_info_.account_id)); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, ObserversAreNotifiedOnAuthErrorChange) { |
| TokenServiceObserver observer; |
| auto error = |
| GoogleServiceAuthError(GoogleServiceAuthError::State::SERVICE_ERROR); |
| delegate_->AddObserver(&observer); |
| |
| delegate_->UpdateAuthError(account_info_.account_id, error); |
| EXPECT_EQ(error, delegate_->GetAuthError(account_info_.account_id)); |
| EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_); |
| EXPECT_EQ(error, observer.last_err_); |
| |
| delegate_->RemoveObserver(&observer); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, ObserversAreNotifiedOnCredentialsInsertion) { |
| TokenServiceObserver observer; |
| delegate_->AddObserver(&observer); |
| delegate_->UpdateCredentials(account_info_.account_id, "123"); |
| |
| EXPECT_EQ(1UL, observer.account_ids_.size()); |
| EXPECT_EQ(account_info_.account_id, *observer.account_ids_.begin()); |
| EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_); |
| EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), observer.last_err_); |
| |
| delegate_->RemoveObserver(&observer); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, ObserversAreNotifiedOnCredentialsUpdate) { |
| TokenServiceObserver observer; |
| delegate_->AddObserver(&observer); |
| delegate_->UpdateCredentials(account_info_.account_id, "123"); |
| |
| EXPECT_EQ(1UL, observer.account_ids_.size()); |
| EXPECT_EQ(account_info_.account_id, *observer.account_ids_.begin()); |
| EXPECT_EQ(account_info_.account_id, observer.last_err_account_id_); |
| EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), observer.last_err_); |
| |
| delegate_->RemoveObserver(&observer); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, |
| ObserversAreNotNotifiedIfCredentialsAreNotUpdated) { |
| TokenServiceObserver observer; |
| const std::string kToken = "123"; |
| delegate_->AddObserver(&observer); |
| |
| delegate_->UpdateCredentials(account_info_.account_id, kToken); |
| observer.account_ids_.clear(); |
| observer.last_err_account_id_ = std::string(); |
| delegate_->UpdateCredentials(account_info_.account_id, kToken); |
| |
| EXPECT_TRUE(observer.account_ids_.empty()); |
| EXPECT_EQ(std::string(), observer.last_err_account_id_); |
| |
| delegate_->RemoveObserver(&observer); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, |
| BatchChangeObserversAreNotifiedOnCredentialsUpdate) { |
| TokenServiceObserver observer; |
| delegate_->AddObserver(&observer); |
| delegate_->UpdateCredentials(account_info_.account_id, "123"); |
| |
| EXPECT_EQ(1UL, observer.batch_change_records_.size()); |
| EXPECT_EQ(1UL, observer.batch_change_records_[0].size()); |
| EXPECT_EQ(account_info_.account_id, observer.batch_change_records_[0][0]); |
| |
| delegate_->RemoveObserver(&observer); |
| } |
| |
| // If observers register themselves with |OAuth2TokenServiceDelegate| before |
| // |AccountManager| has been initialized, they should receive all the accounts |
| // stored in |AccountManager| in a single batch. |
| TEST_F(CrOSOAuthDelegateTest, BatchChangeObserversAreNotifiedOncePerBatch) { |
| // Setup |
| AccountInfo account1 = CreateAccountInfoTestFixture( |
| "1" /* gaia_id */, "test1@gmail.com" /* email */); |
| AccountInfo account2 = CreateAccountInfoTestFixture( |
| "2" /* gaia_id */, "test2@gmail.com" /* email */); |
| |
| account_tracker_service_.SeedAccountInfo(account1); |
| account_tracker_service_.SeedAccountInfo(account2); |
| account_manager_.UpsertToken( |
| AccountManager::AccountKey{account1.gaia, ACCOUNT_TYPE_GAIA}, "token1"); |
| account_manager_.UpsertToken( |
| AccountManager::AccountKey{account2.gaia, ACCOUNT_TYPE_GAIA}, "token2"); |
| scoped_task_environment_.RunUntilIdle(); |
| |
| AccountManager account_manager; |
| // AccountManager will not be fully initialized until |
| // |scoped_task_environment_.RunUntilIdle()| is called. |
| account_manager.Initialize(tmp_dir_.GetPath(), test_shared_loader_factory_, |
| immediate_callback_runner_); |
| |
| // Register callbacks before AccountManager has been fully initialized. |
| auto delegate = std::make_unique<ChromeOSOAuth2TokenServiceDelegate>( |
| &account_tracker_service_, &account_manager); |
| delegate->LoadCredentials(account1.account_id /* primary_account_id */); |
| TokenServiceObserver observer; |
| delegate->AddObserver(&observer); |
| // Wait until AccountManager is fully initialized. |
| scoped_task_environment_.RunUntilIdle(); |
| |
| // Tests |
| |
| // The observer should receive 3 batch change callbacks: |
| // First - A batch of all accounts stored in AccountManager: because of the |
| // delegate's invocation of |AccountManager::GetAccounts| in its constructor. |
| // Followed by 2 updates for the individual accounts (|account1| and |
| // |account2|): because of the delegate's registration as an |
| // |AccountManager::Observer| before |AccountManager| has been fully |
| // initialized. |
| EXPECT_EQ(3UL, observer.batch_change_records_.size()); |
| |
| const std::vector<std::string>& first_batch = |
| observer.batch_change_records_[0]; |
| EXPECT_EQ(2UL, first_batch.size()); |
| EXPECT_TRUE(base::ContainsValue(first_batch, account1.account_id)); |
| EXPECT_TRUE(base::ContainsValue(first_batch, account2.account_id)); |
| |
| delegate->RemoveObserver(&observer); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, GetAccountsShouldNotReturnAdAccounts) { |
| EXPECT_TRUE(delegate_->GetAccounts().empty()); |
| |
| // Insert an Active Directory account into AccountManager. |
| AccountManager::AccountKey ad_account_key{"111", |
| ACCOUNT_TYPE_ACTIVE_DIRECTORY}; |
| account_manager_.UpsertToken(ad_account_key, "" /* token */); |
| |
| // OAuth delegate should not return Active Directory accounts. |
| EXPECT_TRUE(delegate_->GetAccounts().empty()); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, GetAccountsReturnsGaiaAccounts) { |
| EXPECT_TRUE(delegate_->GetAccounts().empty()); |
| |
| AccountManager::AccountKey gaia_account_key{"111", ACCOUNT_TYPE_GAIA}; |
| account_manager_.UpsertToken(gaia_account_key, "token"); |
| |
| std::vector<std::string> accounts = delegate_->GetAccounts(); |
| EXPECT_EQ(1UL, accounts.size()); |
| EXPECT_EQ(account_info_.account_id, accounts[0]); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, UpdateCredentialsSucceeds) { |
| EXPECT_TRUE(delegate_->GetAccounts().empty()); |
| |
| delegate_->UpdateCredentials(account_info_.account_id, "token"); |
| |
| std::vector<std::string> accounts = delegate_->GetAccounts(); |
| EXPECT_EQ(1UL, accounts.size()); |
| EXPECT_EQ(account_info_.account_id, accounts[0]); |
| } |
| |
| TEST_F(CrOSOAuthDelegateTest, ObserversAreNotifiedOnAccountRemoval) { |
| delegate_->UpdateCredentials(account_info_.account_id, "token"); |
| |
| TokenServiceObserver observer; |
| delegate_->AddObserver(&observer); |
| const AccountManager::AccountKey account_key{account_info_.gaia, |
| ACCOUNT_TYPE_GAIA}; |
| account_manager_.RemoveAccount(account_key); |
| |
| EXPECT_EQ(1UL, observer.batch_change_records_.size()); |
| EXPECT_EQ(1UL, observer.batch_change_records_[0].size()); |
| EXPECT_EQ(account_info_.account_id, observer.batch_change_records_[0][0]); |
| EXPECT_TRUE(observer.account_ids_.empty()); |
| |
| delegate_->RemoveObserver(&observer); |
| } |
| |
| } // namespace chromeos |