blob: 9017f4ce5e8298a89b46507449b2dd83ea025bc7 [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 "chromeos/account_manager/account_manager.h"
#include <utility>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/sequenced_task_runner.h"
#include "base/task_runner_util.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"
namespace chromeos {
namespace {
constexpr base::FilePath::CharType kTokensFileName[] =
FILE_PATH_LITERAL("AccountManagerTokens.bin");
constexpr int kTokensFileMaxSizeInBytes = 100000; // ~100 KB
AccountManager::TokenMap LoadTokensFromDisk(
const base::FilePath& tokens_file_path) {
AccountManager::TokenMap tokens;
VLOG(1) << "AccountManager::LoadTokensFromDisk";
std::string token_file_data;
bool success = ReadFileToStringWithMaxSize(tokens_file_path, &token_file_data,
kTokensFileMaxSizeInBytes);
if (!success) {
// TODO(sinhak): Add an error log when AccountManager becomes the default
// Identity provider on Chrome OS.
return tokens;
}
chromeos::account_manager::Accounts accounts_proto;
success = accounts_proto.ParseFromString(token_file_data);
if (!success) {
LOG(ERROR) << "Failed to parse tokens from file";
return tokens;
}
for (const auto& account : accounts_proto.accounts()) {
AccountManager::AccountKey account_key{account.id(),
account.account_type()};
if (!account_key.IsValid()) {
LOG(WARNING) << "Ignoring invalid account_key load from disk: "
<< account_key;
continue;
}
tokens[account_key] = account.token();
}
return tokens;
}
std::string GetSerializedTokens(const AccountManager::TokenMap& tokens) {
chromeos::account_manager::Accounts accounts_proto;
for (const auto& token : tokens) {
account_manager::Account* account_proto = accounts_proto.add_accounts();
account_proto->set_id(token.first.id);
account_proto->set_account_type(token.first.account_type);
account_proto->set_token(token.second);
}
return accounts_proto.SerializeAsString();
}
std::vector<AccountManager::AccountKey> GetAccountKeys(
const AccountManager::TokenMap& tokens) {
std::vector<AccountManager::AccountKey> accounts;
accounts.reserve(tokens.size());
for (const auto& key_val : tokens) {
accounts.emplace_back(key_val.first);
}
return accounts;
}
} // namespace
bool AccountManager::AccountKey::IsValid() const {
return !id.empty() &&
account_type != account_manager::AccountType::ACCOUNT_TYPE_UNSPECIFIED;
}
bool AccountManager::AccountKey::operator<(const AccountKey& other) const {
if (id != other.id) {
return id < other.id;
}
return account_type < other.account_type;
}
bool AccountManager::AccountKey::operator==(const AccountKey& other) const {
return id == other.id && account_type == other.account_type;
}
AccountManager::Observer::Observer() = default;
AccountManager::Observer::~Observer() = default;
AccountManager::AccountManager() : weak_factory_(this) {}
void AccountManager::Initialize(const base::FilePath& home_dir) {
Initialize(home_dir, base::CreateSequencedTaskRunnerWithTraits(
{base::TaskShutdownBehavior::BLOCK_SHUTDOWN,
base::MayBlock()}));
}
void AccountManager::Initialize(
const base::FilePath& home_dir,
scoped_refptr<base::SequencedTaskRunner> task_runner) {
VLOG(1) << "AccountManager::Initialize";
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (init_state_ != InitializationState::kNotStarted) {
// |Initialize| has already been called once. To help diagnose possible race
// conditions, check whether the |home_dir| parameter provided by the first
// invocation of |Initialize| matches the one it is currently being called
// with.
DCHECK_EQ(home_dir, writer_->path().DirName());
return;
}
init_state_ = InitializationState::kInProgress;
task_runner_ = task_runner;
writer_ = std::make_unique<base::ImportantFileWriter>(
home_dir.Append(kTokensFileName), task_runner_);
PostTaskAndReplyWithResult(
task_runner_.get(), FROM_HERE,
base::BindOnce(&LoadTokensFromDisk, writer_->path()),
base::BindOnce(&AccountManager::InsertTokensAndRunInitializationCallbacks,
weak_factory_.GetWeakPtr()));
}
void AccountManager::InsertTokensAndRunInitializationCallbacks(
const AccountManager::TokenMap& tokens) {
VLOG(1) << "AccountManager::InsertTokensAndRunInitializationCallbacks";
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
tokens_.insert(tokens.begin(), tokens.end());
init_state_ = InitializationState::kInitialized;
for (auto& cb : initialization_callbacks_) {
std::move(cb).Run();
}
initialization_callbacks_.clear();
for (const auto& token : tokens_) {
NotifyTokenObservers(token.first);
}
}
AccountManager::~AccountManager() {
// AccountManager is supposed to be used as a leaky global.
}
void AccountManager::RunOnInitialization(base::OnceClosure closure) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (init_state_ != InitializationState::kInitialized) {
initialization_callbacks_.emplace_back(std::move(closure));
} else {
std::move(closure).Run();
}
}
void AccountManager::GetAccounts(AccountListCallback callback) {
DCHECK_NE(init_state_, InitializationState::kNotStarted);
base::OnceClosure closure =
base::BindOnce(&AccountManager::GetAccountsInternal,
weak_factory_.GetWeakPtr(), std::move(callback));
RunOnInitialization(std::move(closure));
}
void AccountManager::GetAccountsInternal(AccountListCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(init_state_, InitializationState::kInitialized);
std::vector<AccountKey> accounts = GetAccountKeys(tokens_);
std::move(callback).Run(std::move(accounts));
}
void AccountManager::UpsertToken(const AccountKey& account_key,
const std::string& token) {
DCHECK_NE(init_state_, InitializationState::kNotStarted);
base::OnceClosure closure =
base::BindOnce(&AccountManager::UpsertTokenInternal,
weak_factory_.GetWeakPtr(), account_key, token);
RunOnInitialization(std::move(closure));
}
void AccountManager::UpsertTokenInternal(const AccountKey& account_key,
const std::string& token) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(init_state_, InitializationState::kInitialized);
DCHECK(account_key.IsValid()) << "Invalid account_key: " << account_key;
auto it = tokens_.find(account_key);
if ((it == tokens_.end()) || (it->second != token)) {
tokens_[account_key] = token;
PersistTokensAsync();
NotifyTokenObservers(account_key);
}
}
void AccountManager::PersistTokensAsync() {
// Schedule (immediately) a non-blocking write.
writer_->WriteNow(
std::make_unique<std::string>(GetSerializedTokens(tokens_)));
}
void AccountManager::NotifyTokenObservers(const AccountKey& account_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers_) {
observer.OnTokenUpserted(account_key);
}
}
void AccountManager::AddObserver(AccountManager::Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.AddObserver(observer);
}
void AccountManager::RemoveObserver(AccountManager::Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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<<(
std::ostream& os,
const AccountManager::AccountKey& account_key) {
os << "{ id: " << account_key.id
<< ", account_type: " << account_key.account_type << " }";
return os;
}
} // namespace chromeos