blob: 62573c80116e4aaa23be5316dbd26651d465a9af [file] [log] [blame]
// 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