Commit 33cfbf08 authored by Kush Sinha's avatar Kush Sinha Committed by Commit Bot

Add Chrome OS Account Manager

  - Loads Login Scoped Tokens (LSTs) from disk to memory
  - Persists updates to LSTs to disk

Design doc is linked in the bug id.
This CL creates the core of Account Manager and its ability to load
and persist its state.
Follow up CLs will:
- Link it with OAuth2TokenService via an OAuth2TokenServiceDelegate.
- Add UI components to modify Account Manager's state.

Bug: 820046
Change-Id: I1b43e11cfc7ed3592daf4a79ff664d377644f6a8
Test: chromeos_unittests --gtest_filter="*AccountManager*"
Reviewed-on: https://chromium-review.googlesource.com/955523
Commit-Queue: Kush Sinha <sinhak@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543944}
parent e182f94c
...@@ -19,6 +19,7 @@ component("chromeos") { ...@@ -19,6 +19,7 @@ component("chromeos") {
"//dbus", "//dbus",
] ]
deps = [ deps = [
":account_manager_proto",
":attestation_proto", ":attestation_proto",
":authpolicy_proto", ":authpolicy_proto",
":biod_proto", ":biod_proto",
...@@ -59,6 +60,8 @@ component("chromeos") { ...@@ -59,6 +60,8 @@ component("chromeos") {
"accelerometer/accelerometer_reader.h", "accelerometer/accelerometer_reader.h",
"accelerometer/accelerometer_types.cc", "accelerometer/accelerometer_types.cc",
"accelerometer/accelerometer_types.h", "accelerometer/accelerometer_types.h",
"account_manager/account_manager.cc",
"account_manager/account_manager.h",
"app_mode/kiosk_oem_manifest_parser.cc", "app_mode/kiosk_oem_manifest_parser.cc",
"app_mode/kiosk_oem_manifest_parser.h", "app_mode/kiosk_oem_manifest_parser.h",
"attestation/attestation_constants.cc", "attestation/attestation_constants.cc",
...@@ -664,6 +667,7 @@ test("chromeos_unittests") { ...@@ -664,6 +667,7 @@ test("chromeos_unittests") {
"//url", "//url",
] ]
sources = [ sources = [
"account_manager/account_manager_unittest.cc",
"app_mode/kiosk_oem_manifest_parser_unittest.cc", "app_mode/kiosk_oem_manifest_parser_unittest.cc",
"attestation/attestation_flow_unittest.cc", "attestation/attestation_flow_unittest.cc",
"audio/audio_devices_pref_handler_impl_unittest.cc", "audio/audio_devices_pref_handler_impl_unittest.cc",
...@@ -848,3 +852,11 @@ proto_library("smbprovider_proto") { ...@@ -848,3 +852,11 @@ proto_library("smbprovider_proto") {
proto_out_dir = "chromeos/dbus/smbprovider" proto_out_dir = "chromeos/dbus/smbprovider"
} }
proto_library("account_manager_proto") {
sources = [
"account_manager/tokens.proto",
]
proto_out_dir = "chromeos/account_manager"
}
// 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 "chromeos/account_manager/tokens.pb.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) {
LOG(ERROR) << "Failed to read tokens file";
return tokens;
}
chromeos::account_manager::Tokens tokens_proto;
success = tokens_proto.ParseFromString(token_file_data);
if (!success) {
LOG(ERROR) << "Failed to parse tokens from file";
return tokens;
}
tokens.insert(tokens_proto.login_scoped_tokens().begin(),
tokens_proto.login_scoped_tokens().end());
return tokens;
}
std::string GetSerializedTokens(const AccountManager::TokenMap& tokens) {
chromeos::account_manager::Tokens tokens_proto;
*tokens_proto.mutable_login_scoped_tokens() =
::google::protobuf::Map<std::string, std::string>(tokens.begin(),
tokens.end());
return tokens_proto.SerializeAsString();
}
} // namespace
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();
}
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(
base::OnceCallback<void(std::vector<std::string>)> 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(
base::OnceCallback<void(std::vector<std::string>)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(init_state_, InitializationState::kInitialized);
std::vector<std::string> accounts;
accounts.reserve(tokens_.size());
for (auto& key_val : tokens_) {
accounts.emplace_back(key_val.first);
}
std::move(callback).Run(std::move(accounts));
}
void AccountManager::UpsertToken(const std::string& account_id,
const std::string& login_scoped_token) {
DCHECK_NE(init_state_, InitializationState::kNotStarted);
base::OnceClosure closure = base::BindOnce(
&AccountManager::UpsertTokenInternal, weak_factory_.GetWeakPtr(),
account_id, login_scoped_token);
RunOnInitialization(std::move(closure));
}
void AccountManager::UpsertTokenInternal(
const std::string& account_id,
const std::string& login_scoped_token) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(init_state_, InitializationState::kInitialized);
tokens_[account_id] = login_scoped_token;
PersistTokensAsync();
}
void AccountManager::PersistTokensAsync() {
// Schedule (immediately) a non-blocking write.
writer_->WriteNow(
std::make_unique<std::string>(GetSerializedTokens(tokens_)));
}
} // 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 CHROMEOS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_H_
#define CHROMEOS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_H_
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
namespace base {
class SequencedTaskRunner;
class ImportantFileWriter;
} // namespace base
namespace chromeos {
class AccountManager {
public:
// A map of account identifiers to login scoped tokens.
using TokenMap = std::unordered_map<std::string, std::string>;
// Note: |Initialize| MUST be called at least once on this object.
AccountManager();
~AccountManager();
// |home_dir| is the path of the Device Account's home directory (root of the
// user's cryptohome). This method MUST be called at least once.
void Initialize(const base::FilePath& home_dir);
// Gets (async) a list of account identifiers known to |AccountManager|.
void GetAccounts(base::OnceCallback<void(std::vector<std::string>)> callback);
// Updates or inserts an LST (Login Scoped Token), for the account
// corresponding to the given account id.
void UpsertToken(const std::string& account_id,
const std::string& login_scoped_token);
private:
enum InitializationState {
kNotStarted, // Initialize has not been called
kInProgress, // Initialize has been called but not completed
kInitialized, // Initialization was successfully completed
};
friend class AccountManagerTest;
FRIEND_TEST_ALL_PREFIXES(AccountManagerTest, TestInitialization);
FRIEND_TEST_ALL_PREFIXES(AccountManagerTest, TestUpsert);
FRIEND_TEST_ALL_PREFIXES(AccountManagerTest, TestPersistence);
// Initializes |AccountManager| with the provided |task_runner| and location
// of the user's home directory.
void Initialize(const base::FilePath& home_dir,
scoped_refptr<base::SequencedTaskRunner> task_runner);
// Reads tokens from |tokens| and inserts them in |tokens_| and runs all
// callbacks waiting on |AccountManager| initialization.
void InsertTokensAndRunInitializationCallbacks(const TokenMap& tokens);
// Accepts a closure and runs it immediately if |AccountManager| has already
// been initialized, otherwise saves the |closure| for running later, when the
// class is initialized.
void RunOnInitialization(base::OnceClosure closure);
// Does the actual work of getting a list of accounts. Assumes that
// |AccountManager| initialization (|init_state_|) is complete.
void GetAccountsInternal(
base::OnceCallback<void(std::vector<std::string>)> callback);
// Does the actual work of updating or inserting tokens. Assumes that
// |AccountManager| initialization (|init_state_|) is complete.
void UpsertTokenInternal(const std::string& account_id,
const std::string& login_scoped_token);
// Persists (async) the current state of |tokens_| on disk.
void PersistTokensAsync();
// Status of this object's initialization.
InitializationState init_state_ = InitializationState::kNotStarted;
// A task runner for disk I/O.
scoped_refptr<base::SequencedTaskRunner> task_runner_;
std::unique_ptr<base::ImportantFileWriter> writer_;
// A map of account ids to login scoped tokens.
TokenMap tokens_;
// Callbacks waiting on class initialization (|init_state_|).
std::vector<base::OnceClosure> initialization_callbacks_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<AccountManager> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(AccountManager);
};
} // namespace chromeos
#endif // CHROMEOS_ACCOUNT_MANAGER_ACCOUNT_MANAGER_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 "chromeos/account_manager/account_manager.h"
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
class AccountManagerTest : public testing::Test {
public:
AccountManagerTest() = default;
~AccountManagerTest() override {}
protected:
void SetUp() override {
ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir());
account_manager_ = std::make_unique<AccountManager>();
account_manager_->Initialize(tmp_dir_.GetPath(),
base::SequencedTaskRunnerHandle::Get());
}
// 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_;
std::unique_ptr<AccountManager> account_manager_;
private:
DISALLOW_COPY_AND_ASSIGN(AccountManagerTest);
};
TEST_F(AccountManagerTest, TestInitialization) {
AccountManager account_manager;
EXPECT_EQ(account_manager.init_state_,
AccountManager::InitializationState::kNotStarted);
account_manager.Initialize(tmp_dir_.GetPath(),
base::SequencedTaskRunnerHandle::Get());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(account_manager.init_state_,
AccountManager::InitializationState::kInitialized);
}
TEST_F(AccountManagerTest, TestUpsert) {
account_manager_->UpsertToken("abc", "123");
std::vector<std::string> accounts;
base::RunLoop run_loop;
account_manager_->GetAccounts(base::BindOnce(
[](std::vector<std::string>* accounts, base::OnceClosure quit_closure,
std::vector<std::string> stored_accounts) -> void {
*accounts = stored_accounts;
std::move(quit_closure).Run();
},
base::Unretained(&accounts), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(1UL, accounts.size());
EXPECT_EQ("abc", accounts[0]);
}
TEST_F(AccountManagerTest, TestPersistence) {
account_manager_->UpsertToken("abc", "123");
base::RunLoop().RunUntilIdle();
account_manager_ = std::make_unique<AccountManager>();
account_manager_->Initialize(tmp_dir_.GetPath(),
base::SequencedTaskRunnerHandle::Get());
std::vector<std::string> accounts;
base::RunLoop run_loop;
account_manager_->GetAccounts(base::BindOnce(
[](std::vector<std::string>* accounts, base::OnceClosure quit_closure,
std::vector<std::string> stored_accounts) -> void {
*accounts = stored_accounts;
std::move(quit_closure).Run();
},
base::Unretained(&accounts), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(1UL, accounts.size());
EXPECT_EQ("abc", accounts[0]);
}
} // 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.
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
package chromeos.account_manager;
message Tokens {
// A mapping from GAIA id to a Login Scoped (Refresh) Token
map<string, string> login_scoped_tokens = 1;
}
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