Commit 9d3867da authored by Kush Sinha's avatar Kush Sinha Committed by Commit Bot

Add Chrome OS OAuth2 Token Service Delegate

Implement a new OAuth2 delegate for Chrome OS which uses Chrome OS
Account Manager as the source of truth for OAuth credentials.

Bug: 820046
Test: unit_tests --gtest_filter="*CrOSOAuthDelegateTest*"
Change-Id: I5b9dde8bc988f3aa1edf27b06691b07d2a7d5939
Reviewed-on: https://chromium-review.googlesource.com/1014041Reviewed-by: default avatarMihai Sardarescu <msarda@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarLutz Justen <ljusten@chromium.org>
Reviewed-by: default avatarDavid Roger <droger@chromium.org>
Commit-Queue: Kush Sinha <sinhak@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555335}
parent 1dc33316
...@@ -1237,6 +1237,8 @@ source_set("chromeos") { ...@@ -1237,6 +1237,8 @@ source_set("chromeos") {
"note_taking_controller_client.h", "note_taking_controller_client.h",
"note_taking_helper.cc", "note_taking_helper.cc",
"note_taking_helper.h", "note_taking_helper.h",
"oauth2_token_service_delegate.cc",
"oauth2_token_service_delegate.h",
"options/cert_library.cc", "options/cert_library.cc",
"options/cert_library.h", "options/cert_library.h",
"options/network_config_view.cc", "options/network_config_view.cc",
...@@ -2003,6 +2005,7 @@ source_set("unit_tests") { ...@@ -2003,6 +2005,7 @@ source_set("unit_tests") {
"net/wake_on_wifi_manager_unittest.cc", "net/wake_on_wifi_manager_unittest.cc",
"night_light/night_light_client_unittest.cc", "night_light/night_light_client_unittest.cc",
"note_taking_helper_unittest.cc", "note_taking_helper_unittest.cc",
"oauth2_token_service_delegate_unittest.cc",
"options/network_property_ui_data_unittest.cc", "options/network_property_ui_data_unittest.cc",
"ownership/fake_owner_settings_service.cc", "ownership/fake_owner_settings_service.cc",
"ownership/fake_owner_settings_service.h", "ownership/fake_owner_settings_service.h",
......
// 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 <string>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "chrome/browser/browser_process.h"
#include "chromeos/account_manager/account_manager.h"
#include "components/signin/core/browser/account_tracker_service.h"
namespace chromeos {
ChromeOSOAuth2TokenServiceDelegate::ChromeOSOAuth2TokenServiceDelegate(
AccountTrackerService* account_tracker_service,
chromeos::AccountManager* account_manager)
: account_tracker_service_(account_tracker_service),
account_manager_(account_manager),
weak_factory_(this) {
DCHECK(account_manager_);
load_credentials_state_ = LOAD_CREDENTIALS_IN_PROGRESS;
account_manager_->AddObserver(this);
account_manager_->GetAccounts(
base::BindOnce(&ChromeOSOAuth2TokenServiceDelegate::GetAccountsCallback,
weak_factory_.GetWeakPtr()));
}
ChromeOSOAuth2TokenServiceDelegate::~ChromeOSOAuth2TokenServiceDelegate() {
account_manager_->RemoveObserver(this);
}
OAuth2AccessTokenFetcher*
ChromeOSOAuth2TokenServiceDelegate::CreateAccessTokenFetcher(
const std::string& account_id,
net::URLRequestContextGetter* getter,
OAuth2AccessTokenConsumer* consumer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, load_credentials_state_);
ValidateAccountId(account_id);
const AccountManager::AccountKey& account_key =
MapAccountIdToAccountKey(account_id);
// |OAuth2TokenService| will manage the lifetime of the released pointer.
return account_manager_
->CreateAccessTokenFetcher(account_key, getter, consumer)
.release();
}
bool ChromeOSOAuth2TokenServiceDelegate::RefreshTokenIsAvailable(
const std::string& account_id) const {
if (load_credentials_state_ != LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS) {
return false;
}
return account_manager_->IsTokenAvailable(
MapAccountIdToAccountKey(account_id));
}
void ChromeOSOAuth2TokenServiceDelegate::UpdateAuthError(
const std::string& account_id,
const GoogleServiceAuthError& error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(sinhak): Implement a backoff policy.
if (error.IsTransientError()) {
return;
}
auto it = errors_.find(account_id);
if (error.state() == GoogleServiceAuthError::NONE) {
if (it != errors_.end()) {
errors_.erase(it);
FireAuthErrorChanged(account_id, error);
}
} else if ((it == errors_.end()) || (it->second != error)) {
errors_[account_id] = error;
FireAuthErrorChanged(account_id, error);
}
}
GoogleServiceAuthError ChromeOSOAuth2TokenServiceDelegate::GetAuthError(
const std::string& account_id) const {
auto it = errors_.find(account_id);
if (it != errors_.end()) {
return it->second;
}
return GoogleServiceAuthError::AuthErrorNone();
}
std::vector<std::string> ChromeOSOAuth2TokenServiceDelegate::GetAccounts() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, load_credentials_state_);
std::vector<std::string> accounts;
for (auto& account_key : account_keys_) {
std::string account_id = MapAccountKeyToAccountId(account_key);
if (!account_id.empty()) {
accounts.emplace_back(account_id);
}
}
return accounts;
}
void ChromeOSOAuth2TokenServiceDelegate::UpdateCredentials(
const std::string& account_id,
const std::string& refresh_token) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!account_id.empty());
DCHECK(!refresh_token.empty());
ValidateAccountId(account_id);
const AccountManager::AccountKey& account_key =
MapAccountIdToAccountKey(account_id);
account_keys_.insert(account_key);
account_manager_->UpsertToken(account_key, refresh_token);
FireRefreshTokenAvailable(account_id);
}
net::URLRequestContextGetter*
ChromeOSOAuth2TokenServiceDelegate::GetRequestContext() const {
// LSTs on Chrome are not channel/token bound for now and hence we can use
// the system request context.
// Note that we cannot use the Profile's request context since
// |AccountManager| acts outside the scope of Profiles.
// TODO(sinhak): Create a new |URLRequestContext| for |AccountManager| which
// conforms to token binding when those details are finalized.
return g_browser_process->system_request_context();
}
OAuth2TokenServiceDelegate::LoadCredentialsState
ChromeOSOAuth2TokenServiceDelegate::GetLoadCredentialsState() const {
return load_credentials_state_;
}
void ChromeOSOAuth2TokenServiceDelegate::GetAccountsCallback(
std::vector<AccountManager::AccountKey> account_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
OnAccountListUpdated(account_keys);
load_credentials_state_ = LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS;
FireRefreshTokensLoaded();
for (const auto& account_key : account_keys) {
std::string account_id = MapAccountKeyToAccountId(account_key);
if (!account_id.empty()) {
FireRefreshTokenAvailable(account_id);
}
}
}
std::string ChromeOSOAuth2TokenServiceDelegate::MapAccountKeyToAccountId(
const AccountManager::AccountKey& account_key) const {
DCHECK(account_key.IsValid());
if (account_key.account_type !=
account_manager::AccountType::ACCOUNT_TYPE_GAIA) {
return std::string();
}
const std::string& account_id =
account_tracker_service_->FindAccountInfoByGaiaId(account_key.id)
.account_id;
DCHECK(!account_id.empty()) << "Can't find account id";
return account_id;
}
AccountManager::AccountKey
ChromeOSOAuth2TokenServiceDelegate::MapAccountIdToAccountKey(
const std::string& account_id) const {
DCHECK(!account_id.empty());
const AccountInfo& account_info =
account_tracker_service_->GetAccountInfo(account_id);
DCHECK(!account_info.gaia.empty()) << "Can't find account info";
return AccountManager::AccountKey{
account_info.gaia, account_manager::AccountType::ACCOUNT_TYPE_GAIA};
}
void ChromeOSOAuth2TokenServiceDelegate::OnAccountListUpdated(
const std::vector<AccountManager::AccountKey>& account_keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
account_keys_.clear();
account_keys_.insert(account_keys.begin(), account_keys.end());
}
} // namespace chromeos
// 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.
#ifndef CHROME_BROWSER_CHROMEOS_OAUTH2_TOKEN_SERVICE_DELEGATE_H_
#define CHROME_BROWSER_CHROMEOS_OAUTH2_TOKEN_SERVICE_DELEGATE_H_
#include <map>
#include <set>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "chromeos/account_manager/account_manager.h"
#include "google_apis/gaia/oauth2_token_service_delegate.h"
class AccountTrackerService;
namespace chromeos {
class ChromeOSOAuth2TokenServiceDelegate : public OAuth2TokenServiceDelegate,
public AccountManager::Observer {
public:
// Accepts non-owning pointers to |AccountTrackerService| and
// |AccountManager|. |AccountTrackerService| is a |KeyedService| and
// |AccountManager| transitively belongs to |g_browser_process| and they
// outlive (as they must) |this| delegate.
ChromeOSOAuth2TokenServiceDelegate(
AccountTrackerService* account_tracker_service,
AccountManager* account_manager);
~ChromeOSOAuth2TokenServiceDelegate() override;
// OAuth2TokenServiceDelegate overrides
OAuth2AccessTokenFetcher* CreateAccessTokenFetcher(
const std::string& account_id,
net::URLRequestContextGetter* getter,
OAuth2AccessTokenConsumer* consumer) override;
bool RefreshTokenIsAvailable(const std::string& account_id) const override;
void UpdateAuthError(const std::string& account_id,
const GoogleServiceAuthError& error) override;
GoogleServiceAuthError GetAuthError(
const std::string& account_id) const override;
std::vector<std::string> GetAccounts() override;
void UpdateCredentials(const std::string& account_id,
const std::string& refresh_token) override;
net::URLRequestContextGetter* GetRequestContext() const override;
LoadCredentialsState GetLoadCredentialsState() const override;
// |AccountManager::Observer| overrides
void OnAccountListUpdated(
const std::vector<AccountManager::AccountKey>& account_keys) override;
// TODO(sinhak): Implement server token revocation.
// TODO(sinhak): Implement scoped batch changes.
private:
// Callback handler for |AccountManager::GetAccounts|.
void GetAccountsCallback(
std::vector<AccountManager::AccountKey> account_keys);
// A utility method to map an |account_key| to the account id used by the
// OAuth2TokenService chain (see |AccountInfo|). Returns an empty string for
// non-Gaia accounts.
std::string MapAccountKeyToAccountId(
const AccountManager::AccountKey& account_key) const;
// A utility method to map the |account_id| used by the OAuth2TokenService
// chain (see |AccountInfo|) to an |AccountManager::AccountKey|.
AccountManager::AccountKey MapAccountIdToAccountKey(
const std::string& account_id) const;
LoadCredentialsState load_credentials_state_ =
LoadCredentialsState::LOAD_CREDENTIALS_NOT_STARTED;
// A non-owning pointer to |AccountTrackerService|, which itself is a
// |KeyedService|.
AccountTrackerService* account_tracker_service_;
// A non-owning pointer to |AccountManager|. |AccountManager| is available
// throughout the lifetime of a user session.
AccountManager* account_manager_;
// A cache of AccountKeys.
std::set<AccountManager::AccountKey> account_keys_;
// A map from account id to the last seen error for that account.
std::map<std::string, GoogleServiceAuthError> errors_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<ChromeOSOAuth2TokenServiceDelegate> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ChromeOSOAuth2TokenServiceDelegate);
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_OAUTH2_TOKEN_SERVICE_DELEGATE_H_
// 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 <string>
#include <utility>
#include "base/files/scoped_temp_dir.h"
#include "base/macros.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 "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 AuthErrorObserver : public OAuth2TokenService::Observer {
public:
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_;
};
} // namespace
class CrOSOAuthDelegateTest : public testing::Test {
public:
CrOSOAuthDelegateTest() = default;
~CrOSOAuthDelegateTest() override = default;
protected:
void SetUp() override {
ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
account_manager_.Initialize(tmp_dir_.GetPath());
scoped_task_environment_.RunUntilIdle();
pref_service_.registry()->RegisterListPref(
AccountTrackerService::kAccountInfoPref);
pref_service_.registry()->RegisterIntegerPref(
prefs::kAccountIdMigrationState,
AccountTrackerService::MIGRATION_NOT_STARTED);
client_.reset(new TestSigninClient(&pref_service_));
client_->SetURLRequestContext(new net::TestURLRequestContextGetter(
base::ThreadTaskRunnerHandle::Get()));
account_tracker_service_.Initialize(client_.get());
account_info_.email = "user@gmail.com";
account_info_.gaia = "111";
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);
ASSERT_TRUE(account_info_.IsValid());
account_tracker_service_.SeedAccountInfo(account_info_);
delegate_ = std::make_unique<ChromeOSOAuth2TokenServiceDelegate>(
&account_tracker_service_, &account_manager_);
}
// 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_;
AccountInfo account_info_;
AccountManager account_manager_;
std::unique_ptr<ChromeOSOAuth2TokenServiceDelegate> delegate_;
private:
base::ScopedTempDir tmp_dir_;
sync_preferences::TestingPrefServiceSyncable pref_service_;
std::unique_ptr<TestSigninClient> client_;
AccountTrackerService account_tracker_service_;
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) {
AuthErrorObserver 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, 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]);
}
} // namespace chromeos
...@@ -9,6 +9,7 @@ include_rules = [ ...@@ -9,6 +9,7 @@ include_rules = [
"+components/signin/core/account_id/account_id.h", "+components/signin/core/account_id/account_id.h",
"+components/user_manager/known_user.h", "+components/user_manager/known_user.h",
"+crypto", "+crypto",
"+google_apis/gaia",
"+media/base/video_facing.h", "+media/base/video_facing.h",
"+mojo/edk/embedder/embedder.h", "+mojo/edk/embedder/embedder.h",
"+net", "+net",
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/sequenced_task_runner.h" #include "base/sequenced_task_runner.h"
#include "base/task_runner_util.h" #include "base/task_runner_util.h"
#include "base/task_scheduler/post_task.h" #include "base/task_scheduler/post_task.h"
#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
#include "third_party/protobuf/src/google/protobuf/message_lite.h" #include "third_party/protobuf/src/google/protobuf/message_lite.h"
namespace chromeos { namespace chromeos {
...@@ -242,6 +243,29 @@ void AccountManager::RemoveObserver(AccountManager::Observer* observer) { ...@@ -242,6 +243,29 @@ void AccountManager::RemoveObserver(AccountManager::Observer* observer) {
observers_.RemoveObserver(observer); observers_.RemoveObserver(observer);
} }
std::unique_ptr<OAuth2AccessTokenFetcher>
AccountManager::CreateAccessTokenFetcher(
const AccountKey& account_key,
net::URLRequestContextGetter* getter,
OAuth2AccessTokenConsumer* consumer) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = tokens_.find(account_key);
if (it == tokens_.end() || it->second.empty()) {
return nullptr;
}
return std::make_unique<OAuth2AccessTokenFetcherImpl>(consumer, getter,
it->second);
}
bool AccountManager::IsTokenAvailable(const AccountKey& account_key) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = tokens_.find(account_key);
return it != tokens_.end() && !it->second.empty();
}
CHROMEOS_EXPORT std::ostream& operator<<( CHROMEOS_EXPORT std::ostream& operator<<(
std::ostream& os, std::ostream& os,
const AccountManager::AccountKey& account_key) { const AccountManager::AccountKey& account_key) {
......
...@@ -23,11 +23,18 @@ ...@@ -23,11 +23,18 @@
#include "chromeos/account_manager/tokens.pb.h" #include "chromeos/account_manager/tokens.pb.h"
#include "chromeos/chromeos_export.h" #include "chromeos/chromeos_export.h"
class OAuth2AccessTokenFetcher;
class OAuth2AccessTokenConsumer;
namespace base { namespace base {
class SequencedTaskRunner; class SequencedTaskRunner;
class ImportantFileWriter; class ImportantFileWriter;
} // namespace base } // namespace base
namespace net {
class URLRequestContextGetter;
} // namespace net
namespace chromeos { namespace chromeos {
class CHROMEOS_EXPORT AccountManager { class CHROMEOS_EXPORT AccountManager {
...@@ -92,6 +99,21 @@ class CHROMEOS_EXPORT AccountManager { ...@@ -92,6 +99,21 @@ class CHROMEOS_EXPORT AccountManager {
// not in the list of known observers. // not in the list of known observers.
void RemoveObserver(Observer* observer); void RemoveObserver(Observer* observer);
// Creates and returns an |OAuth2AccessTokenFetcher| using the refresh token
// stored for |account_key|. |IsTokenAvailable| should be |true| for
// |account_key|, otherwise a |nullptr| is returned.
std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
const AccountKey& account_key,
net::URLRequestContextGetter* getter,
OAuth2AccessTokenConsumer* consumer) const;
// Returns |true| if an LST is available for |account_key|.
// Note: An LST will not be available for |account_key| if it is an Active
// Directory account.
// Note: This method will return |false| if |AccountManager| has not been
// initialized yet.
bool IsTokenAvailable(const AccountKey& account_key) const;
private: private:
enum InitializationState { enum InitializationState {
kNotStarted, // Initialize has not been called kNotStarted, // Initialize has not been called
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment