blob: 74ebcdb59a976f3882056472c033c8a4cd7d7943 [file] [log] [blame]
// Copyright 2015 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 "components/user_manager/known_user.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/values.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/user_manager.h"
#include "google_apis/gaia/gaia_auth_util.h"
namespace user_manager {
namespace known_user {
namespace {
// A vector pref of preferences of known users. All new preferences should be
// placed in this list.
const char kKnownUsers[] = "KnownUsers";
// Known user preferences keys (stored in Local State). All keys should be
// listed in kReservedKeys below.
// Key of canonical e-mail value.
const char kCanonicalEmail[] = "email";
// Key of obfuscated GAIA id value.
const char kGAIAIdKey[] = "gaia_id";
// Key of obfuscated object guid value for Active Directory accounts.
const char kObjGuidKey[] = "obj_guid";
// Key of account type.
const char kAccountTypeKey[] = "account_type";
// Key of whether this user ID refers to a SAML user.
const char kUsingSAMLKey[] = "using_saml";
// Key of Device Id.
const char kDeviceId[] = "device_id";
// Key of GAPS cookie.
const char kGAPSCookie[] = "gaps_cookie";
// Key of the reason for re-auth.
const char kReauthReasonKey[] = "reauth_reason";
// Key for the GaiaId migration status.
const char kGaiaIdMigration[] = "gaia_id_migration";
// Key of the boolean flag telling if a minimal user home migration has been
// attempted.
const char kMinimalMigrationAttempted[] = "minimal_migration_attempted";
// Key of the boolean flag telling if user session requires policy.
const char kProfileRequiresPolicy[] = "profile_requires_policy";
// Key of the boolean flag telling if user is ephemeral and should be removed
// from the local state on logout.
const char kIsEphemeral[] = "is_ephemeral";
// List containing all the known user preferences keys.
const char* kReservedKeys[] = {kCanonicalEmail,
kGAIAIdKey,
kObjGuidKey,
kAccountTypeKey,
kUsingSAMLKey,
kDeviceId,
kGAPSCookie,
kReauthReasonKey,
kGaiaIdMigration,
kMinimalMigrationAttempted,
kProfileRequiresPolicy,
kIsEphemeral};
PrefService* GetLocalState() {
if (!UserManager::IsInitialized())
return nullptr;
return UserManager::Get()->GetLocalState();
}
// Checks if values in |dict| correspond with |account_id| identity.
bool UserMatches(const AccountId& account_id,
const base::DictionaryValue& dict) {
std::string value;
if (account_id.GetAccountType() != AccountType::UNKNOWN &&
dict.GetString(kAccountTypeKey, &value) &&
account_id.GetAccountType() != AccountId::StringToAccountType(value)) {
return false;
}
// TODO(alemate): update code once user id is really a struct.
switch (account_id.GetAccountType()) {
case AccountType::GOOGLE: {
bool has_gaia_id = dict.GetString(kGAIAIdKey, &value);
if (has_gaia_id && account_id.GetGaiaId() == value)
return true;
break;
}
case AccountType::ACTIVE_DIRECTORY: {
bool has_obj_guid = dict.GetString(kObjGuidKey, &value);
if (has_obj_guid && account_id.GetObjGuid() == value)
return true;
break;
}
case AccountType::UNKNOWN: {
}
}
bool has_email = dict.GetString(kCanonicalEmail, &value);
if (has_email && account_id.GetUserEmail() == value)
return true;
return false;
}
// Fills relevant |dict| values based on |account_id|.
void UpdateIdentity(const AccountId& account_id, base::DictionaryValue& dict) {
if (!account_id.GetUserEmail().empty())
dict.SetString(kCanonicalEmail, account_id.GetUserEmail());
switch (account_id.GetAccountType()) {
case AccountType::GOOGLE:
if (!account_id.GetGaiaId().empty())
dict.SetString(kGAIAIdKey, account_id.GetGaiaId());
break;
case AccountType::ACTIVE_DIRECTORY:
if (!account_id.GetObjGuid().empty())
dict.SetString(kObjGuidKey, account_id.GetObjGuid());
break;
case AccountType::UNKNOWN:
return;
}
dict.SetString(kAccountTypeKey,
AccountId::AccountTypeToString(account_id.GetAccountType()));
}
void ClearPref(const AccountId& account_id, const std::string& path) {
const base::DictionaryValue* user_pref_dict = nullptr;
if (!FindPrefs(account_id, &user_pref_dict))
return;
base::Value updated_user_pref = user_pref_dict->Clone();
base::DictionaryValue* updated_user_pref_dict;
updated_user_pref.GetAsDictionary(&updated_user_pref_dict);
updated_user_pref_dict->RemovePath(path);
UpdatePrefs(account_id, *updated_user_pref_dict, true);
}
} // namespace
bool FindPrefs(const AccountId& account_id,
const base::DictionaryValue** out_value) {
PrefService* local_state = GetLocalState();
// Local State may not be initialized in tests.
if (!local_state)
return false;
// UserManager is usually NULL in unit tests.
if (account_id.GetAccountType() != AccountType::ACTIVE_DIRECTORY &&
UserManager::IsInitialized() &&
UserManager::Get()->IsUserNonCryptohomeDataEphemeral(account_id)) {
return false;
}
const base::ListValue* known_users = local_state->GetList(kKnownUsers);
for (size_t i = 0; i < known_users->GetSize(); ++i) {
const base::DictionaryValue* element = nullptr;
if (known_users->GetDictionary(i, &element)) {
if (UserMatches(account_id, *element)) {
known_users->GetDictionary(i, out_value);
return true;
}
}
}
return false;
}
void UpdatePrefs(const AccountId& account_id,
const base::DictionaryValue& values,
bool clear) {
PrefService* local_state = GetLocalState();
// Local State may not be initialized in tests.
if (!local_state)
return;
// UserManager is usually NULL in unit tests.
if (account_id.GetAccountType() != AccountType::ACTIVE_DIRECTORY &&
UserManager::IsInitialized() &&
UserManager::Get()->IsUserNonCryptohomeDataEphemeral(account_id)) {
return;
}
ListPrefUpdate update(local_state, kKnownUsers);
for (size_t i = 0; i < update->GetSize(); ++i) {
base::DictionaryValue* element = nullptr;
if (update->GetDictionary(i, &element)) {
if (UserMatches(account_id, *element)) {
if (clear)
element->Clear();
element->MergeDictionary(&values);
UpdateIdentity(account_id, *element);
return;
}
}
}
std::unique_ptr<base::DictionaryValue> new_value(new base::DictionaryValue());
new_value->MergeDictionary(&values);
UpdateIdentity(account_id, *new_value);
update->Append(std::move(new_value));
}
bool GetStringPref(const AccountId& account_id,
const std::string& path,
std::string* out_value) {
const base::DictionaryValue* user_pref_dict = nullptr;
if (!FindPrefs(account_id, &user_pref_dict))
return false;
return user_pref_dict->GetString(path, out_value);
}
void SetStringPref(const AccountId& account_id,
const std::string& path,
const std::string& in_value) {
base::DictionaryValue dict;
dict.SetString(path, in_value);
UpdatePrefs(account_id, dict, false);
}
bool GetBooleanPref(const AccountId& account_id,
const std::string& path,
bool* out_value) {
const base::DictionaryValue* user_pref_dict = nullptr;
if (!FindPrefs(account_id, &user_pref_dict))
return false;
return user_pref_dict->GetBoolean(path, out_value);
}
void SetBooleanPref(const AccountId& account_id,
const std::string& path,
const bool in_value) {
base::DictionaryValue dict;
dict.SetBoolean(path, in_value);
UpdatePrefs(account_id, dict, false);
}
bool GetIntegerPref(const AccountId& account_id,
const std::string& path,
int* out_value) {
const base::DictionaryValue* user_pref_dict = nullptr;
if (!FindPrefs(account_id, &user_pref_dict))
return false;
return user_pref_dict->GetInteger(path, out_value);
}
void SetIntegerPref(const AccountId& account_id,
const std::string& path,
const int in_value) {
base::DictionaryValue dict;
dict.SetInteger(path, in_value);
UpdatePrefs(account_id, dict, false);
}
bool GetPref(const AccountId& account_id,
const std::string& path,
const base::Value** out_value) {
const base::DictionaryValue* user_pref_dict = nullptr;
if (!FindPrefs(account_id, &user_pref_dict))
return false;
*out_value = user_pref_dict->FindPath(path);
return *out_value != nullptr;
}
void SetPref(const AccountId& account_id,
const std::string& path,
base::Value in_value) {
base::DictionaryValue dict;
dict.SetPath(path, std::move(in_value));
UpdatePrefs(account_id, dict, false);
}
void RemovePref(const AccountId& account_id, const std::string& path) {
// Prevent removing keys that are used internally.
for (const std::string& key : kReservedKeys)
CHECK_NE(path, key);
ClearPref(account_id, path);
}
AccountId GetAccountId(const std::string& user_email,
const std::string& id,
const AccountType& account_type) {
DCHECK((id.empty() && account_type == AccountType::UNKNOWN) ||
(!id.empty() && account_type != AccountType::UNKNOWN));
// In tests empty accounts are possible.
if (user_email.empty() && id.empty() &&
account_type == AccountType::UNKNOWN) {
return EmptyAccountId();
}
AccountId result(EmptyAccountId());
// UserManager is usually NULL in unit tests.
if (account_type == AccountType::UNKNOWN && UserManager::IsInitialized() &&
UserManager::Get()->GetPlatformKnownUserId(user_email, id, &result)) {
return result;
}
std::string stored_gaia_id;
std::string stored_obj_guid;
const std::string sanitized_email =
user_email.empty()
? std::string()
: gaia::CanonicalizeEmail(gaia::SanitizeEmail(user_email));
if (!sanitized_email.empty()) {
if (GetStringPref(AccountId::FromUserEmail(sanitized_email), kGAIAIdKey,
&stored_gaia_id)) {
if (!id.empty()) {
DCHECK(account_type == AccountType::GOOGLE);
if (id != stored_gaia_id)
LOG(ERROR) << "User gaia id has changed. Sync will not work.";
}
// gaia_id is associated with cryptohome.
return AccountId::FromUserEmailGaiaId(sanitized_email, stored_gaia_id);
}
if (GetStringPref(AccountId::FromUserEmail(sanitized_email), kObjGuidKey,
&stored_obj_guid)) {
if (!id.empty()) {
DCHECK(account_type == AccountType::ACTIVE_DIRECTORY);
if (id != stored_obj_guid)
LOG(ERROR) << "User object guid has changed. Sync will not work.";
}
// obj_guid is associated with cryptohome.
return AccountId::AdFromUserEmailObjGuid(sanitized_email,
stored_obj_guid);
}
}
std::string stored_email;
switch (account_type) {
case AccountType::GOOGLE:
if (GetStringPref(AccountId::FromGaiaId(id), kCanonicalEmail,
&stored_email)) {
return AccountId::FromUserEmailGaiaId(stored_email, id);
}
return AccountId::FromUserEmailGaiaId(sanitized_email, id);
case AccountType::ACTIVE_DIRECTORY:
if (GetStringPref(AccountId::AdFromObjGuid(id), kCanonicalEmail,
&stored_email)) {
return AccountId::AdFromUserEmailObjGuid(stored_email, id);
}
return AccountId::AdFromUserEmailObjGuid(sanitized_email, id);
case AccountType::UNKNOWN:
return AccountId::FromUserEmail(sanitized_email);
}
NOTREACHED();
return EmptyAccountId();
}
std::vector<AccountId> GetKnownAccountIds() {
std::vector<AccountId> result;
PrefService* local_state = GetLocalState();
// Local State may not be initialized in tests.
if (!local_state)
return result;
const base::ListValue* known_users = local_state->GetList(kKnownUsers);
for (size_t i = 0; i < known_users->GetSize(); ++i) {
const base::DictionaryValue* element = nullptr;
if (known_users->GetDictionary(i, &element)) {
std::string email;
std::string gaia_id;
std::string obj_guid;
const bool has_email = element->GetString(kCanonicalEmail, &email);
const bool has_gaia_id = element->GetString(kGAIAIdKey, &gaia_id);
const bool has_obj_guid = element->GetString(kObjGuidKey, &obj_guid);
AccountType account_type = AccountType::GOOGLE;
std::string account_type_string;
if (element->GetString(kAccountTypeKey, &account_type_string)) {
account_type = AccountId::StringToAccountType(account_type_string);
}
switch (account_type) {
case AccountType::GOOGLE:
if (has_email || has_gaia_id) {
result.push_back(AccountId::FromUserEmailGaiaId(email, gaia_id));
}
break;
case AccountType::ACTIVE_DIRECTORY:
if (has_email && has_obj_guid) {
result.push_back(
AccountId::AdFromUserEmailObjGuid(email, obj_guid));
}
break;
default:
NOTREACHED() << "Unknown account type";
}
}
}
return result;
}
bool GetGaiaIdMigrationStatus(const AccountId& account_id,
const std::string& subsystem) {
bool migrated = false;
if (GetBooleanPref(account_id,
std::string(kGaiaIdMigration) + "." + subsystem,
&migrated)) {
return migrated;
}
return false;
}
void SetGaiaIdMigrationStatusDone(const AccountId& account_id,
const std::string& subsystem) {
SetBooleanPref(account_id, std::string(kGaiaIdMigration) + "." + subsystem,
true);
}
void SaveKnownUser(const AccountId& account_id) {
const bool is_ephemeral =
UserManager::IsInitialized() &&
UserManager::Get()->IsUserNonCryptohomeDataEphemeral(account_id);
if (is_ephemeral &&
account_id.GetAccountType() != AccountType::ACTIVE_DIRECTORY) {
return;
}
UpdateId(account_id);
GetLocalState()->CommitPendingWrite();
}
void SetIsEphemeralUser(const AccountId& account_id, bool is_ephemeral) {
if (account_id.GetAccountType() != AccountType::ACTIVE_DIRECTORY)
return;
SetBooleanPref(account_id, kIsEphemeral, is_ephemeral);
}
void UpdateGaiaID(const AccountId& account_id, const std::string& gaia_id) {
SetStringPref(account_id, kGAIAIdKey, gaia_id);
SetStringPref(account_id, kAccountTypeKey,
AccountId::AccountTypeToString(AccountType::GOOGLE));
}
void UpdateId(const AccountId& account_id) {
switch (account_id.GetAccountType()) {
case AccountType::GOOGLE:
SetStringPref(account_id, kGAIAIdKey, account_id.GetGaiaId());
break;
case AccountType::ACTIVE_DIRECTORY:
SetStringPref(account_id, kObjGuidKey, account_id.GetObjGuid());
break;
case AccountType::UNKNOWN:
return;
}
SetStringPref(account_id, kAccountTypeKey,
AccountId::AccountTypeToString(account_id.GetAccountType()));
}
bool FindGaiaID(const AccountId& account_id, std::string* out_value) {
return GetStringPref(account_id, kGAIAIdKey, out_value);
}
void SetDeviceId(const AccountId& account_id, const std::string& device_id) {
const std::string known_device_id = GetDeviceId(account_id);
if (!known_device_id.empty() && device_id != known_device_id) {
NOTREACHED() << "Trying to change device ID for known user.";
}
SetStringPref(account_id, kDeviceId, device_id);
}
std::string GetDeviceId(const AccountId& account_id) {
std::string device_id;
if (GetStringPref(account_id, kDeviceId, &device_id)) {
return device_id;
}
return std::string();
}
void SetGAPSCookie(const AccountId& account_id,
const std::string& gaps_cookie) {
SetStringPref(account_id, kGAPSCookie, gaps_cookie);
}
std::string GetGAPSCookie(const AccountId& account_id) {
std::string gaps_cookie;
if (GetStringPref(account_id, kGAPSCookie, &gaps_cookie)) {
return gaps_cookie;
}
return std::string();
}
void UpdateUsingSAML(const AccountId& account_id, const bool using_saml) {
SetBooleanPref(account_id, kUsingSAMLKey, using_saml);
}
bool IsUsingSAML(const AccountId& account_id) {
bool using_saml;
if (GetBooleanPref(account_id, kUsingSAMLKey, &using_saml))
return using_saml;
return false;
}
void SetProfileRequiresPolicy(const AccountId& account_id,
ProfileRequiresPolicy required) {
DCHECK_NE(required, ProfileRequiresPolicy::kUnknown);
SetBooleanPref(account_id, kProfileRequiresPolicy,
required == ProfileRequiresPolicy::kPolicyRequired);
}
ProfileRequiresPolicy GetProfileRequiresPolicy(const AccountId& account_id) {
bool requires_policy;
if (GetBooleanPref(account_id, kProfileRequiresPolicy, &requires_policy)) {
return requires_policy ? ProfileRequiresPolicy::kPolicyRequired
: ProfileRequiresPolicy::kNoPolicyRequired;
}
return ProfileRequiresPolicy::kUnknown;
}
void ClearProfileRequiresPolicy(const AccountId& account_id) {
ClearPref(account_id, kProfileRequiresPolicy);
}
void UpdateReauthReason(const AccountId& account_id, const int reauth_reason) {
SetIntegerPref(account_id, kReauthReasonKey, reauth_reason);
}
bool FindReauthReason(const AccountId& account_id, int* out_value) {
return GetIntegerPref(account_id, kReauthReasonKey, out_value);
}
bool WasUserHomeMinimalMigrationAttempted(const AccountId& account_id) {
bool minimal_migration_attempted;
const bool pref_set = GetBooleanPref(account_id, kMinimalMigrationAttempted,
&minimal_migration_attempted);
if (pref_set)
return minimal_migration_attempted;
// If we haven't recorded that a minimal migration has been attempted, assume
// no.
return false;
}
void SetUserHomeMinimalMigrationAttempted(const AccountId& account_id,
bool minimal_migration_attempted) {
SetBooleanPref(account_id, kMinimalMigrationAttempted,
minimal_migration_attempted);
}
void RemovePrefs(const AccountId& account_id) {
PrefService* local_state = GetLocalState();
// Local State may not be initialized in tests.
if (!local_state)
return;
ListPrefUpdate update(local_state, kKnownUsers);
for (size_t i = 0; i < update->GetSize(); ++i) {
base::DictionaryValue* element = nullptr;
if (update->GetDictionary(i, &element)) {
if (UserMatches(account_id, *element)) {
update->Remove(i, nullptr);
break;
}
}
}
}
void CleanEphemeralUsers() {
PrefService* local_state = GetLocalState();
// Local State may not be initialized in tests.
if (!local_state)
return;
ListPrefUpdate update(local_state, kKnownUsers);
auto& list_storage = update->GetList();
for (auto it = list_storage.begin(); it < list_storage.end();) {
bool remove = false;
base::DictionaryValue* element = nullptr;
if (update->GetDictionary(std::distance(list_storage.begin(), it),
&element)) {
base::Value* is_ephemeral = element->FindKey(kIsEphemeral);
if (is_ephemeral && is_ephemeral->GetBool())
remove = true;
}
if (remove)
it = list_storage.erase(it);
else
it++;
}
}
void RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterListPref(kKnownUsers);
}
} // namespace known_user
} // namespace user_manager