0
Files
src/components/user_manager/user_manager_impl.cc
Xiyuan Xia 3067d3b1ad ash: Fix kiosk launch crash with misconfigured user
The crash happens when the previous kiosk launch fails at
cryptohome mount stage. That would leave the kiosk user as
misconfigured and will be removed on the next chrome run.
However, kiosk users are only added when loading user list.
Once removed, the user type could not be determined correctly.
The code would fallback to regular user login code path and
crash. The CL skips removing kiosk users from user list even
if they are in marked as misconfigured.

Bug: 403354377
Change-Id: I7c19228c53d6685a34abaa4c3a88e81636b9cf06
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6357166
Reviewed-by: Edman Anjos <edman@chromium.org>
Reviewed-by: Hidehiko Abe <hidehiko@chromium.org>
Commit-Queue: Xiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1435088}
2025-03-19 14:13:44 -07:00

1839 lines
62 KiB
C++

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/user_manager/user_manager_impl.h"
#include <stddef.h>
#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include <vector>
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "base/check_deref.h"
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/settings/user_login_permission_tracker.h"
#include "components/crash/core/common/crash_key.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/multi_user/multi_user_sign_in_policy.h"
#include "components/user_manager/user_directory_integrity_manager.h"
#include "components/user_manager/user_manager_pref_names.h"
#include "components/user_manager/user_names.h"
#include "components/user_manager/user_type.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image_skia.h"
namespace user_manager {
namespace {
// Upper bound for a histogram metric reporting the amount of time between
// one regular user logging out and a different regular user logging in.
const int kLogoutToLoginDelayMaxSec = 1800;
// Used for serializing information about the owner user. The existing entries
// should never be deleted / renumbered.
enum class OwnerAccountType { kGoogleEmail = 1 };
// This reads integer value from kUserType Local State preference and
// interprets it as UserType. It is used in initial users load.
UserType GetStoredUserType(const base::Value::Dict& prefs_user_types,
const AccountId& account_id) {
const base::Value* stored_user_type = prefs_user_types.Find(
account_id.HasAccountIdKey() ? account_id.GetAccountIdKey()
: account_id.GetUserEmail());
if (!stored_user_type || !stored_user_type->is_int()) {
return UserType::kRegular;
}
int int_user_type = stored_user_type->GetInt();
if (int_user_type < 0 ||
int_user_type > static_cast<int>(UserType::kMaxValue) ||
int_user_type == 2) {
LOG(ERROR) << "Bad user type " << int_user_type;
return UserType::kRegular;
}
return static_cast<UserType>(int_user_type);
}
bool IsDeviceLocalAccountChanged(
const UserList& users,
const base::span<UserManager::DeviceLocalAccountInfo>&
device_local_accounts) {
size_t i = 0;
for (const user_manager::User* user : users) {
if (!user->IsDeviceLocalAccount()) {
continue;
}
if (i >= device_local_accounts.size()) {
// `users` has device local users more than the updated one.
return true;
}
if (user->GetAccountId().GetUserEmail() !=
device_local_accounts[i].user_id ||
user->GetType() != device_local_accounts[i].type) {
// The device local user at the position is different from the new
// corresponding entry.
return true;
}
++i;
}
// If there're still new entries, the `users` needs to be updated.
return i != device_local_accounts.size();
}
} // namespace
// static
const char UserManagerImpl::kLegacySupervisedUsersHistogramName[] =
"ChromeOS.LegacySupervisedUsers.HiddenFromLoginScreen";
// static
const char UserManagerImpl::kDeprecatedArcKioskUsersHistogramName[] =
"Kiosk.DeprecatedArcKioskUsers";
// static
BASE_FEATURE(kRemoveDeprecatedArcKioskUsersOnStartup,
"RemoveDeprecatedArcKioskUsersOnStartup",
base::FEATURE_ENABLED_BY_DEFAULT);
UserManagerImpl::UserManagerImpl(std::unique_ptr<Delegate> delegate,
PrefService* local_state,
ash::CrosSettings* cros_settings)
: delegate_(std::move(delegate)),
local_state_(local_state),
cros_settings_(cros_settings) {
// |local_state| can be nullptr only for testing.
if (!local_state) {
CHECK_IS_TEST();
}
UpdateNumLoggedInUsersCrashKey(0);
}
UserManagerImpl::~UserManagerImpl() = default;
void UserManagerImpl::Shutdown() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
const UserList& UserManagerImpl::GetPersistedUsers() const {
return persisted_users_;
}
UserList UserManagerImpl::GetUsersAllowedForMultiUserSignIn() const {
// Supervised users are not allowed to use multi-user sign-in.
if (logged_in_users_.size() == 1 &&
primary_user_->GetType() != UserType::kRegular) {
return {};
}
// No user is allowed if the primary user policy forbids it.
if (GetMultiUserSignInPolicy(primary_user_) ==
MultiUserSignInPolicy::kNotAllowed) {
return {};
}
user_manager::UserList result;
for (user_manager::User* user : persisted_users_) {
if (user->GetType() == UserType::kRegular && !user->is_logged_in()) {
// Users with a policy that prevents them being added to a session will be
// shown in login UI but will be grayed out.
// Same applies to owner account (see http://crbug.com/385034).
result.push_back(user);
}
}
// Extract out users that are allowed on login screen.
return FindLoginAllowedUsersFrom(result);
}
UserList UserManagerImpl::FindLoginAllowedUsersFrom(
const UserList& users) const {
bool show_users_on_signin;
cros_settings_->GetBoolean(ash::kAccountsPrefShowUserNamesOnSignIn,
&show_users_on_signin);
UserList found_users;
for (User* user : users) {
// Skip kiosk apps for login screen user list. Kiosk apps as pods (aka new
// kiosk UI) is currently disabled and it gets the apps directly from
// KioskChromeAppManager and WebKioskAppManager.
if (user->IsKioskType()) {
continue;
}
const bool meets_allowlist_requirements =
!user->HasGaiaAccount() || IsGaiaUserAllowed(*user);
// Public session accounts are always shown on login screen.
const bool meets_show_users_requirements =
show_users_on_signin || user->GetType() == UserType::kPublicAccount;
if (meets_allowlist_requirements && meets_show_users_requirements) {
found_users.push_back(user);
}
}
return found_users;
}
const UserList& UserManagerImpl::GetLoggedInUsers() const {
return logged_in_users_;
}
const UserList& UserManagerImpl::GetLRULoggedInUsers() const {
return lru_logged_in_users_;
}
UserList UserManagerImpl::GetUnlockUsers() const {
std::optional<MultiUserSignInPolicy> primary_policy =
GetMultiUserSignInPolicy(primary_user_);
if (!primary_policy.has_value()) {
// Locking is not allowed until the primary user profile is created.
return {};
}
// Specific case: only one logged in user or
// primary user has primary-only multi-user policy.
if (logged_in_users_.size() == 1 ||
primary_policy == MultiUserSignInPolicy::kPrimaryOnly) {
return primary_user_->CanLock() ? UserList{{primary_user_.get()}}
: UserList{};
}
// Fill list of potential unlock users based on multi-user policy state.
UserList unlock_users;
for (User* user : logged_in_users_) {
std::optional<MultiUserSignInPolicy> policy =
GetMultiUserSignInPolicy(user);
if (!policy.has_value()) {
continue;
}
if (policy == MultiUserSignInPolicy::kUnrestricted && user->CanLock()) {
unlock_users.push_back(user);
} else if (policy == MultiUserSignInPolicy::kPrimaryOnly) {
NOTREACHED()
<< "Spotted primary-only multi-user policy for non-primary user";
}
}
return unlock_users;
}
const AccountId& UserManagerImpl::GetOwnerAccountId() const {
if (!owner_account_id_.has_value()) {
return EmptyAccountId();
}
return *owner_account_id_;
}
void UserManagerImpl::GetOwnerAccountIdAsync(
base::OnceCallback<void(const AccountId&)> callback) const {
if (owner_account_id_.has_value()) {
std::move(callback).Run(*owner_account_id_);
return;
}
pending_owner_callbacks_.AddUnsafe(std::move(callback));
}
const AccountId& UserManagerImpl::GetLastSessionActiveAccountId() const {
return last_session_active_account_id_;
}
void UserManagerImpl::UserLoggedIn(const AccountId& account_id,
const std::string& username_hash) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!last_session_active_account_id_initialized_) {
last_session_active_account_id_ = AccountId::FromUserEmail(
local_state_->GetString(prefs::kLastActiveUser));
last_session_active_account_id_initialized_ = true;
}
bool is_primary_login = logged_in_users_.empty();
// For primary login case, i.e., there was no previous login, so there should
// be no active user. Otherwise, there was some previous login, so there
// should be an active user.
CHECK_EQ(!active_user_, is_primary_login);
User* user = FindUserAndModify(account_id);
CHECK(user);
OnUserLoggedIn(*user, username_hash);
if (is_primary_login) {
OnPrimaryUserLoggedIn(*user);
OnActiveUserSwitched(*user);
local_state_->CommitPendingWrite();
NotifyOnLogin();
} else {
SendMultiUserSignInMetrics();
NotifyUserAddedToSession(user);
}
}
bool UserManagerImpl::EnsureUser(const AccountId& account_id,
UserType user_type,
bool is_ephemeral) {
User* user = FindUserInListAndModify(account_id);
switch (user_type) {
case UserType::kRegular:
case UserType::kChild:
if (user) {
KnownUser known_user(local_state_.get());
// There already is a registered User, update the type as needed.
if (user->GetType() != user_type) {
user->SetType(user_type);
SaveUserType(user);
// Clear information about profile policy requirements to enforce
// setting it again for the new account type.
known_user.ClearProfileRequiresPolicy(account_id);
}
break;
}
// Ensure User is created.
if (is_ephemeral) {
user = AddEphemeralUser(account_id, user_type);
} else {
user = AddGaiaUser(account_id, user_type);
}
return true;
case UserType::kGuest:
CHECK(!user);
user = AddGuestUser();
return true;
case UserType::kPublicAccount:
if (!user) {
user = AddPublicAccountUser(account_id);
return true;
}
break;
case UserType::kKioskApp:
case UserType::kWebKioskApp:
case UserType::kKioskIWA:
// Do nothing. User should be already there.
break;
default:
NOTREACHED() << "Unhandled usert type " << user_type;
}
CHECK(user);
return false;
}
void UserManagerImpl::OnUserLoggedIn(User& user,
std::string_view username_hash) {
user.set_is_logged_in(true);
user.set_username_hash(username_hash);
logged_in_users_.push_back(&user);
UpdateNumLoggedInUsersCrashKey(logged_in_users_.size());
lru_logged_in_users_.push_back(&user);
}
void UserManagerImpl::OnPrimaryUserLoggedIn(User& user) {
CHECK(!primary_user_);
primary_user_ = &user;
const auto& account_id = user.GetAccountId();
delegate_->OverrideDirHome(user);
if (user.HasGaiaAccount()) {
// Move the user to the front of the list.
auto it = std::ranges::find(persisted_users_, account_id,
[](auto& ptr) { return ptr->GetAccountId(); });
if (it != persisted_users_.end()) {
std::rotate(persisted_users_.begin(), it, it + 1);
ScopedListPrefUpdate prefs_users_update(local_state_.get(),
prefs::kRegularUsersPref);
prefs_users_update->clear();
for (const User* u : persisted_users_) {
if (u->HasGaiaAccount()) {
prefs_users_update->Append(u->GetAccountId().GetUserEmail());
}
}
}
SendGaiaUserLoginMetrics(account_id);
}
base::UmaHistogramEnumeration("UserManager.LoginUserType", user.GetType());
UpdateSessionTypeCrashKey(user.GetType());
local_state_->SetString(
prefs::kLastLoggedInGaiaUser,
user.HasGaiaAccount() ? account_id.GetUserEmail() : "");
delegate_->CheckProfileOnLogin(user);
}
void UserManagerImpl::SwitchActiveUser(const AccountId& account_id) {
User* user = FindUserAndModify(account_id);
if (!user) {
DUMP_WILL_BE_NOTREACHED() << "Switching to a non-existing user";
return;
}
if (user == active_user_) {
DUMP_WILL_BE_NOTREACHED() << "Switching to a user who is already active";
return;
}
if (!user->is_logged_in()) {
DUMP_WILL_BE_NOTREACHED() << "Switching to a user that is not logged in";
return;
}
if (!user->HasGaiaAccount()) {
DUMP_WILL_BE_NOTREACHED()
<< "Switching to a user without gaia account (non-regular one)";
return;
}
if (user->username_hash().empty()) {
DUMP_WILL_BE_NOTREACHED()
<< "Switching to a user that doesn't have username_hash set";
return;
}
DCHECK(active_user_);
OnActiveUserSwitched(*user);
NotifyActiveUserChanged(active_user_);
NotifyLoginStateUpdated();
}
void UserManagerImpl::SwitchToLastActiveUser() {
if (!last_session_active_account_id_.is_valid()) {
return;
}
if (AccountId::FromUserEmail(
GetActiveUser()->GetAccountId().GetUserEmail()) !=
last_session_active_account_id_) {
SwitchActiveUser(last_session_active_account_id_);
}
// Make sure that this function gets run only once.
last_session_active_account_id_.clear();
}
void UserManagerImpl::OnSessionStarted() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NotifyLoginStateUpdated();
local_state_->CommitPendingWrite();
}
bool UserManagerImpl::UpdateDeviceLocalAccountUser(
const base::span<DeviceLocalAccountInfo>& device_local_accounts) {
// Try to remove any device local account data marked as pending removal.
RemovePendingDeviceLocalAccount();
// Persist the new list of device local accounts in a pref. These accounts
// will be loaded in LoadDeviceLocalAccounts() on the next reboot regardless
// of whether they still exist in kAccountsPrefDeviceLocalAccounts, allowing
// us to clean up associated data if they disappear from policy.
ScopedListPrefUpdate prefs_device_local_accounts_update(
GetLocalState(), prefs::kDeviceLocalAccountsWithSavedData);
prefs_device_local_accounts_update->clear();
for (const auto& account : device_local_accounts) {
prefs_device_local_accounts_update->Append(account.user_id);
}
// If the list of device local accounts has not changed, return.
if (!IsDeviceLocalAccountChanged(persisted_users_, device_local_accounts)) {
return false;
}
// Remove the old device local accounts from the user list.
// Take snapshot because RemoveUserFromListImpl will update |user_|.
std::vector<User*> users(persisted_users_.begin(), persisted_users_.end());
for (User* user : users) {
if (!user->IsDeviceLocalAccount()) {
// Non device local account is not a target to be removed.
continue;
}
if (std::ranges::any_of(
device_local_accounts, [user](const DeviceLocalAccountInfo& info) {
return info.user_id == user->GetAccountId().GetUserEmail() &&
info.type == user->GetType();
})) {
// The account exists in new device local accounts. Do not remove.
continue;
}
if (user == GetActiveUser()) {
// This user is active, so keep the instance. Instead, mark it as
// pending removal, so it will be removed in the next turn.
GetLocalState()->SetString(prefs::kDeviceLocalAccountPendingDataRemoval,
user->GetAccountId().GetUserEmail());
std::erase(persisted_users_, user);
continue;
}
// Remove the instance.
RemoveUserFromListImpl(user->GetAccountId(),
UserRemovalReason::DEVICE_LOCAL_ACCOUNT_UPDATED,
/*trigger_cryptohome_removal=*/false);
}
// Add the new device local accounts to the front of the user list.
for (size_t i = 0; i < device_local_accounts.size(); ++i) {
const DeviceLocalAccountInfo& account = device_local_accounts[i];
auto iter = std::find_if(
persisted_users_.begin() + i, persisted_users_.end(),
[&account](const User* user) {
return user->GetAccountId().GetUserEmail() == account.user_id &&
user->GetType() == account.type;
});
if (iter != persisted_users_.end()) {
// Found the instance. Rotate the `persisted_users_` to place the found
// user at the i-th position.
std::rotate(persisted_users_.begin() + i, iter, iter + 1);
} else {
// Not found so create an instance.
// Using `new` to access a non-public constructor.
user_storage_.push_back(base::WrapUnique(
new User(AccountId::FromUserEmail(account.user_id), account.type)));
persisted_users_.insert(persisted_users_.begin() + i,
user_storage_.back().get());
}
if (account.display_name) {
SaveUserDisplayName(AccountId::FromUserEmail(account.user_id),
*account.display_name);
}
}
for (auto& observer : observer_list_) {
observer.OnDeviceLocalUserListUpdated();
}
return true;
}
void UserManagerImpl::RemoveUser(const AccountId& account_id,
UserRemovalReason reason) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const User* const user = FindUser(account_id);
// Misconfigured user would not be included in GetPersistedUsers(),
// account for them separately.
// TODO(crbug.com/404898436): Find a better way for the special handling.
if (reason == UserRemovalReason::MISCONFIGURED_USER) {
if (user && user->IsDeviceLocalAccount()) {
// Device local account users are created from policy and should only be
// remove from the user list on policy change. So just remove crypothome
// for them instead of a full removal.
delegate_->RemoveCryptohomeAsync(account_id);
} else {
RemoveUserInternal(account_id, reason);
}
return;
}
if (!CanUserBeRemoved(user)) {
return;
}
RemoveUserInternal(account_id, reason);
}
void UserManagerImpl::RemoveUserInternal(const AccountId& account_id,
UserRemovalReason reason) {
auto callback =
base::BindOnce(&UserManagerImpl::RemoveUserInternal,
weak_factory_.GetWeakPtr(), account_id, reason);
// Ensure the value of owner email has been fetched.
if (cros_settings()->PrepareTrustedValues(std::move(callback)) !=
ash::CrosSettingsProvider::TRUSTED) {
// Value of owner email is not fetched yet. RemoveUserInternal will be
// called again after fetch completion.
return;
}
std::string owner;
cros_settings()->GetString(ash::kDeviceOwner, &owner);
if (account_id == AccountId::FromUserEmail(owner)) {
// Owner is not allowed to be removed from the device.
return;
}
delegate_->RemoveProfileByAccountId(account_id);
RemoveUserFromListImpl(account_id, reason,
/*trigger_cryptohome_removal=*/true);
}
void UserManagerImpl::RemoveUserFromList(const AccountId& account_id) {
RemoveUserFromListImpl(account_id, UserRemovalReason::UNKNOWN,
/*trigger_cryptohome_removal=*/false);
}
void UserManagerImpl::RemoveUserFromListForRecreation(
const AccountId& account_id) {
RemoveUserFromListImpl(account_id, /*reason=*/std::nullopt,
/*trigger_cryptohome_removal=*/false);
}
bool UserManagerImpl::RemoveStaleEphemeralUsers() {
CHECK(!IsUserLoggedIn());
bool changed = false;
const auto owner_id = GetOwnerAccountId();
// Take snapshot because DeleteUser called in the loop will update it.
std::vector<User*> users(persisted_users_.begin(), persisted_users_.end());
for (user_manager::User* user : users) {
const AccountId account_id = user->GetAccountId();
if (user->HasGaiaAccount() && account_id != owner_id &&
IsEphemeralAccountId(account_id)) {
RemoveUserFromListImpl(
account_id,
/*reason=*/UserRemovalReason::DEVICE_EPHEMERAL_USERS_ENABLED,
/*trigger_cryptohome_removal=*/false);
changed = true;
}
}
return changed;
}
void UserManagerImpl::CleanStaleUserInformationFor(
const AccountId& account_id) {
KnownUser known_user(local_state_);
if (known_user.UserExists(account_id)) {
known_user.RemovePrefs(account_id);
known_user.SaveKnownUser(account_id);
}
// For users with actual online identity we need
// to remove them from the user list as well, otherwise
// they would be incorrectly detected as "existing" later.
// For Kiosk/MGS users it is actually expected for User object
// to exist even if no cryptohome is present for the user.
const User* user = FindUserInList(account_id);
if (!user) {
return;
}
if (!User::TypeHasGaiaAccount(user->GetType())) {
return;
}
RemoveUserFromList(account_id);
}
// Use AccountId instead of const AccountId& here, since the account_id maybe
// originated from the one stored in the User being removed, and the removed ID
// will be kept using after the actual deletion for observer call.
void UserManagerImpl::RemoveUserFromListImpl(
AccountId account_id,
std::optional<UserRemovalReason> reason,
bool trigger_cryptohome_removal) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (reason.has_value()) {
NotifyUserToBeRemoved(account_id);
}
if (trigger_cryptohome_removal) {
delegate_->RemoveCryptohomeAsync(account_id);
}
RemoveNonCryptohomeData(account_id);
KnownUser(local_state_.get()).RemovePrefs(account_id);
// After the User object is deleted from memory in DeleteUser() here,
// the account_id reference will be invalid if the reference points
// to the account_id in the User object.
DeleteUser(
RemoveRegularOrSupervisedUserFromList(account_id, reason.has_value()));
if (reason.has_value()) {
NotifyUserRemoved(account_id, reason.value());
}
// Make sure that new data is persisted to Local State.
local_state_->CommitPendingWrite();
}
bool UserManagerImpl::IsKnownUser(const AccountId& account_id) const {
// We check for the presence of a misconfigured user as well. This is because
// `WallpaperControllerClientImpl::RemoveUserWallpaper` would not remove
// the wallpaper prefs if we return false here, thus leaving behind
// orphan prefs for the misconfigured users.
UserDirectoryIntegrityManager integrity_manager(local_state_.get());
return FindUser(account_id) != nullptr ||
integrity_manager.IsUserMisconfigured(account_id);
}
const User* UserManagerImpl::FindUser(const AccountId& account_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& user : user_storage_) {
if (user->GetAccountId() == account_id) {
return user.get();
}
}
return nullptr;
}
User* UserManagerImpl::FindUserAndModify(const AccountId& account_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& user : user_storage_) {
if (user->GetAccountId() == account_id) {
return user.get();
}
}
return nullptr;
}
const User* UserManagerImpl::GetActiveUser() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return active_user_;
}
User* UserManagerImpl::GetActiveUser() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return active_user_;
}
const User* UserManagerImpl::GetPrimaryUser() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return primary_user_;
}
void UserManagerImpl::SaveUserOAuthStatus(
const AccountId& account_id,
User::OAuthTokenStatus oauth_token_status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(1) << "Saving user OAuth token status in Local State";
User* user = FindUserAndModify(account_id);
if (user) {
user->set_oauth_token_status(oauth_token_status);
}
// Do not update local state if data stored or cached outside the user's
// cryptohome is to be treated as ephemeral.
if (IsUserNonCryptohomeDataEphemeral(account_id)) {
return;
}
{
ScopedDictPrefUpdate oauth_status_update(local_state_.get(),
prefs::kUserOAuthTokenStatus);
oauth_status_update->Set(account_id.GetUserEmail(),
static_cast<int>(oauth_token_status));
}
local_state_->CommitPendingWrite();
}
void UserManagerImpl::SaveForceOnlineSignin(const AccountId& account_id,
bool force_online_signin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
User* const user = FindUserAndModify(account_id);
if (user) {
user->set_force_online_signin(force_online_signin);
}
// Do not update local state if data stored or cached outside the user's
// cryptohome is to be treated as ephemeral.
if (IsUserNonCryptohomeDataEphemeral(account_id)) {
return;
}
{
ScopedDictPrefUpdate force_online_update(local_state_.get(),
prefs::kUserForceOnlineSignin);
force_online_update->Set(account_id.GetUserEmail(), force_online_signin);
}
local_state_->CommitPendingWrite();
}
void UserManagerImpl::SaveUserDisplayName(const AccountId& account_id,
const std::u16string& display_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (User* user = FindUserAndModify(account_id)) {
user->set_display_name(display_name);
// Do not update local state if data stored or cached outside the user's
// cryptohome is to be treated as ephemeral.
if (!IsUserNonCryptohomeDataEphemeral(account_id)) {
ScopedDictPrefUpdate display_name_update(local_state_.get(),
prefs::kUserDisplayName);
display_name_update->Set(account_id.GetUserEmail(), display_name);
}
}
}
void UserManagerImpl::SaveUserDisplayEmail(const AccountId& account_id,
const std::string& display_email) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
User* user = FindUserAndModify(account_id);
if (!user) {
LOG(ERROR) << "User not found: " << account_id.GetUserEmail();
return; // Ignore if there is no such user.
}
user->set_display_email(display_email);
// Do not update local state if data stored or cached outside the user's
// cryptohome is to be treated as ephemeral.
if (IsUserNonCryptohomeDataEphemeral(account_id)) {
return;
}
ScopedDictPrefUpdate display_email_update(local_state_.get(),
prefs::kUserDisplayEmail);
display_email_update->Set(account_id.GetUserEmail(), display_email);
}
UserType UserManagerImpl::GetUserType(const AccountId& account_id) {
const base::Value::Dict& prefs_user_types =
local_state_->GetDict(prefs::kUserType);
return GetStoredUserType(prefs_user_types, account_id);
}
void UserManagerImpl::SaveUserType(const User* user) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(user);
// Do not update local state if data stored or cached outside the user's
// cryptohome is to be treated as ephemeral.
if (IsUserNonCryptohomeDataEphemeral(user->GetAccountId())) {
return;
}
ScopedDictPrefUpdate user_type_update(local_state_.get(), prefs::kUserType);
user_type_update->Set(user->GetAccountId().GetAccountIdKey(),
static_cast<int>(user->GetType()));
local_state_->CommitPendingWrite();
}
void UserManagerImpl::SetUserUsingSaml(const AccountId& account_id,
bool using_saml,
bool using_saml_principals_api) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto& user = CHECK_DEREF(FindUserAndModify(account_id));
user.set_using_saml(using_saml);
user_manager::KnownUser known_user(local_state_);
known_user.UpdateUsingSAML(account_id, using_saml);
known_user.UpdateIsUsingSAMLPrincipalsAPI(
account_id, using_saml && using_saml_principals_api);
if (!using_saml) {
known_user.ClearPasswordSyncToken(account_id);
}
}
std::optional<std::string> UserManagerImpl::GetOwnerEmail() {
const base::Value::Dict& owner = local_state_->GetDict(prefs::kOwnerAccount);
std::optional<int> type = owner.FindInt(prefs::kOwnerAccountType);
if (!type.has_value() || (static_cast<OwnerAccountType>(type.value())) !=
OwnerAccountType::kGoogleEmail) {
return std::nullopt;
}
const std::string* email = owner.FindString(prefs::kOwnerAccountIdentity);
// A valid email should not be empty, so return a nullopt if Chrome
// accidentally saved an empty string.
if (!email || email->empty()) {
return std::nullopt;
}
return *email;
}
void UserManagerImpl::RecordOwner(const AccountId& owner) {
base::Value::Dict owner_dict;
owner_dict.Set(prefs::kOwnerAccountType,
static_cast<int>(OwnerAccountType::kGoogleEmail));
owner_dict.Set(prefs::kOwnerAccountIdentity, owner.GetUserEmail());
local_state_->SetDict(prefs::kOwnerAccount, std::move(owner_dict));
// The information about the owner might be needed for recovery if Chrome
// crashes before establishing ownership, so it needs to be written on disk as
// soon as possible.
local_state_->CommitPendingWrite();
}
void UserManagerImpl::UpdateUserAccountData(
const AccountId& account_id,
const UserAccountData& account_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SaveUserDisplayName(account_id, account_data.display_name());
if (User* user = FindUserAndModify(account_id)) {
std::u16string given_name = account_data.given_name();
user->set_given_name(given_name);
if (!IsUserNonCryptohomeDataEphemeral(account_id)) {
ScopedDictPrefUpdate given_name_update(local_state_.get(),
prefs::kUserGivenName);
given_name_update->Set(account_id.GetUserEmail(), given_name);
}
}
UpdateUserAccountLocale(account_id, account_data.locale());
}
void UserManagerImpl::ParseUserList(const base::Value::List& users_list,
const std::set<AccountId>& existing_users,
std::vector<AccountId>* users_vector,
std::set<AccountId>* users_set) {
users_vector->clear();
users_set->clear();
for (size_t i = 0; i < users_list.size(); ++i) {
const std::string* email = users_list[i].GetIfString();
if (!email || email->empty()) {
LOG(ERROR) << "Corrupt entry in user list at index " << i << ".";
continue;
}
const AccountId account_id =
KnownUser(local_state_.get())
.GetAccountId(*email, std::string() /* id */, AccountType::UNKNOWN);
if (existing_users.find(account_id) != existing_users.end() ||
!users_set->insert(account_id).second) {
LOG(ERROR) << "Duplicate user: " << *email;
continue;
}
users_vector->push_back(account_id);
}
}
bool UserManagerImpl::IsOwnerUser(const User* user) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return user && owner_account_id_.has_value() &&
user->GetAccountId() == *owner_account_id_;
}
bool UserManagerImpl::IsPrimaryUser(const User* user) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return user && user == primary_user_;
}
bool UserManagerImpl::IsEphemeralUser(const User* user) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!user) {
return false;
}
return IsEphemeralAccountId(user->GetAccountId());
}
bool UserManagerImpl::IsCurrentUserOwner() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsOwnerUser(active_user_);
}
bool UserManagerImpl::IsCurrentUserNew() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kForceFirstRunUI)) {
return true;
}
return is_current_user_new_;
}
bool UserManagerImpl::IsCurrentUserNonCryptohomeDataEphemeral() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() &&
IsUserNonCryptohomeDataEphemeral(GetActiveUser()->GetAccountId());
}
bool UserManagerImpl::IsCurrentUserCryptohomeDataEphemeral() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() &&
IsUserCryptohomeDataEphemeral(GetActiveUser()->GetAccountId());
}
bool UserManagerImpl::IsUserLoggedIn() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return active_user_;
}
bool UserManagerImpl::IsLoggedInAsUserWithGaiaAccount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() && active_user_->HasGaiaAccount();
}
bool UserManagerImpl::IsLoggedInAsChildUser() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() && active_user_->GetType() == UserType::kChild;
}
bool UserManagerImpl::IsLoggedInAsManagedGuestSession() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() &&
active_user_->GetType() == UserType::kPublicAccount;
}
bool UserManagerImpl::IsLoggedInAsGuest() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() && active_user_->GetType() == UserType::kGuest;
}
bool UserManagerImpl::IsLoggedInAsKioskApp() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() && active_user_->GetType() == UserType::kKioskApp;
}
bool UserManagerImpl::IsLoggedInAsWebKioskApp() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() && active_user_->GetType() == UserType::kWebKioskApp;
}
bool UserManagerImpl::IsLoggedInAsKioskIWA() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() && active_user_->GetType() == UserType::kKioskIWA;
}
bool UserManagerImpl::IsLoggedInAsAnyKioskApp() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() && active_user_->IsKioskType();
}
bool UserManagerImpl::IsLoggedInAsStub() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return IsUserLoggedIn() && active_user_->GetAccountId() == StubAccountId();
}
bool UserManagerImpl::IsUserNonCryptohomeDataEphemeral(
const AccountId& account_id) const {
// Data belonging to the guest and stub users is always ephemeral.
if (account_id == GuestAccountId() || account_id == StubAccountId()) {
return true;
}
// Data belonging to the owner, anyone found on the user list and obsolete
// device local accounts whose data has not been removed yet is not ephemeral.
if (account_id == GetOwnerAccountId() || UserExistsInList(account_id) ||
IsDeviceLocalAccountMarkedForRemoval(account_id)) {
return false;
}
// Even though device-local accounts might be ephemeral (e.g. kiosk accounts),
// non-cryptohome data of device-local accounts should be non-ephemeral.
const User* user = FindUser(account_id);
if (user && user->IsDeviceLocalAccount()) {
return false;
}
// Data belonging to the currently logged-in user is ephemeral when:
// a) The user logged into a regular gaia account while the ephemeral users
// policy was enabled.
// - or -
// b) The user logged into any other account type.
// TODO(crbug.com/278643115): The first condition may be redundant, because
// we already check it in UserExistsInList above.
if (user && user == active_user_ &&
((user->GetType() == UserType::kRegular &&
std::ranges::find(persisted_users_, user) == persisted_users_.end()) ||
!user->HasGaiaAccount())) {
return true;
}
// Data belonging to any other user is ephemeral when:
// a) Going through the regular login flow and the ephemeral users policy is
// enabled.
// - or -
// b) The browser is restarting after a crash.
return IsEphemeralAccountId(account_id) || HasBrowserRestarted();
}
bool UserManagerImpl::IsUserCryptohomeDataEphemeral(
const AccountId& account_id) const {
return IsEphemeralAccountId(account_id);
}
bool UserManagerImpl::IsEphemeralAccountId(const AccountId& account_id) const {
// Data belonging to the device owner is never ephemeral.
if (account_id == GetOwnerAccountId()) {
return false;
}
// Data belonging to the stub users is never ephemeral.
if (account_id == StubAccountId()) {
return false;
}
// Data belonging to the guest user is always ephemeral.
if (account_id == GuestAccountId()) {
return true;
}
// Data belonging to the public accounts (e.g. managed guest sessions) is
// always ephemeral.
if (const User* user = FindUser(account_id);
user && user->GetType() == UserType::kPublicAccount) {
return true;
}
const bool device_is_owned =
IsEnterpriseManaged() || GetOwnerAccountId().is_valid();
return device_is_owned &&
GetEphemeralModeConfig().IsAccountIdIncluded(account_id);
}
void UserManagerImpl::AddObserver(UserManager::Observer* obs) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observer_list_.AddObserver(obs);
}
void UserManagerImpl::RemoveObserver(UserManager::Observer* obs) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observer_list_.RemoveObserver(obs);
}
void UserManagerImpl::AddSessionStateObserver(
UserManager::UserSessionStateObserver* obs) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
session_state_observer_list_.AddObserver(obs);
}
void UserManagerImpl::RemoveSessionStateObserver(
UserManager::UserSessionStateObserver* obs) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
session_state_observer_list_.RemoveObserver(obs);
}
void UserManagerImpl::NotifyLocalStateChanged() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.LocalStateChanged(this);
}
}
void UserManagerImpl::NotifyUserImageChanged(const User& user) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUserImageChanged(user);
}
}
void UserManagerImpl::NotifyUserImageIsEnterpriseManagedChanged(
const User& user,
bool is_enterprise_managed) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUserImageIsEnterpriseManagedChanged(user, is_enterprise_managed);
}
}
void UserManagerImpl::NotifyUserProfileImageUpdateFailed(const User& user) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUserProfileImageUpdateFailed(user);
}
}
void UserManagerImpl::NotifyUserProfileImageUpdated(
const User& user,
const gfx::ImageSkia& profile_image) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUserProfileImageUpdated(user, profile_image);
}
}
void UserManagerImpl::NotifyUsersSignInConstraintsChanged() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUsersSignInConstraintsChanged();
}
}
void UserManagerImpl::NotifyUserAffiliationUpdated(const User& user) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUserAffiliationUpdated(user);
}
}
void UserManagerImpl::NotifyUserToBeRemoved(const AccountId& account_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUserToBeRemoved(account_id);
}
}
void UserManagerImpl::NotifyUserRemoved(const AccountId& account_id,
UserRemovalReason reason) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUserRemoved(account_id, reason);
}
}
void UserManagerImpl::NotifyUserNotAllowed(const std::string& user_email) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observer_list_) {
observer.OnUserNotAllowed(user_email);
}
}
bool UserManagerImpl::IsGuestSessionAllowed() const {
// In tests CrosSettings might not be initialized.
if (!cros_settings()) {
return false;
}
bool is_guest_allowed = false;
cros_settings()->GetBoolean(ash::kAccountsPrefAllowGuest, &is_guest_allowed);
return is_guest_allowed;
}
bool UserManagerImpl::IsGaiaUserAllowed(const User& user) const {
DCHECK(user.HasGaiaAccount());
return ash::UserLoginPermissionTracker::Get()->IsUserAllowlisted(
user.GetAccountId().GetUserEmail(), nullptr, user.GetType());
}
bool UserManagerImpl::IsUserAllowed(const User& user) const {
DCHECK(user.GetType() == UserType::kRegular ||
user.GetType() == UserType::kGuest ||
user.GetType() == UserType::kChild);
return UserManager::IsUserAllowed(
user, IsGuestSessionAllowed(),
user.HasGaiaAccount() && IsGaiaUserAllowed(user));
}
bool UserManagerImpl::IsDeprecatedSupervisedAccountId(
const AccountId& account_id) const {
return gaia::ExtractDomainName(account_id.GetUserEmail()) ==
kSupervisedUserDomain;
}
bool UserManagerImpl::IsDeviceLocalAccountMarkedForRemoval(
const AccountId& account_id) const {
return account_id == AccountId::FromUserEmail(GetLocalState()->GetString(
prefs::kDeviceLocalAccountPendingDataRemoval));
}
bool UserManagerImpl::CanUserBeRemoved(const User* user) const {
// Only regular users are allowed to be manually removed.
if (!user || !user->HasGaiaAccount()) {
return false;
}
// Sanity check: we must not remove single user unless it's an enterprise
// device. This check may seem redundant at a first sight because
// this single user must be an owner and we perform special check later
// in order not to remove an owner. However due to non-instant nature of
// ownership assignment this later check may sometimes fail.
// See http://crosbug.com/12723
if (persisted_users_.size() < 2 && !IsEnterpriseManaged()) {
return false;
}
// Sanity check: do not allow any of the the logged in users to be removed.
for (UserList::const_iterator it = logged_in_users_.begin();
it != logged_in_users_.end(); ++it) {
if ((*it)->GetAccountId() == user->GetAccountId()) {
return false;
}
}
return true;
}
const UserManagerImpl::EphemeralModeConfig&
UserManagerImpl::GetEphemeralModeConfig() const {
return ephemeral_mode_config_;
}
void UserManagerImpl::SetEphemeralModeConfig(
EphemeralModeConfig ephemeral_mode_config) {
ephemeral_mode_config_ = std::move(ephemeral_mode_config);
}
void UserManagerImpl::SetIsCurrentUserNew(bool is_new) {
is_current_user_new_ = is_new;
}
void UserManagerImpl::ResetOwnerId() {
owner_account_id_ = std::nullopt;
}
void UserManagerImpl::SetOwnerId(const AccountId& owner_account_id) {
owner_account_id_ = owner_account_id;
pending_owner_callbacks_.Notify(owner_account_id);
NotifyLoginStateUpdated();
}
void UserManagerImpl::EnsureUsersLoaded() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!local_state_) {
return;
}
const base::Value::List& prefs_regular_users =
local_state_->GetList(prefs::kRegularUsersPref);
const base::Value::Dict& prefs_display_names =
local_state_->GetDict(prefs::kUserDisplayName);
const base::Value::Dict& prefs_given_names =
local_state_->GetDict(prefs::kUserGivenName);
const base::Value::Dict& prefs_display_emails =
local_state_->GetDict(prefs::kUserDisplayEmail);
const base::Value::Dict& prefs_user_types =
local_state_->GetDict(prefs::kUserType);
// Load public sessions first.
std::set<AccountId> device_local_accounts_set;
LoadDeviceLocalAccounts(&device_local_accounts_set);
// Load regular users and supervised users.
std::vector<AccountId> regular_users;
std::set<AccountId> regular_users_set;
ParseUserList(prefs_regular_users, device_local_accounts_set, &regular_users,
&regular_users_set);
for (std::vector<AccountId>::const_iterator it = regular_users.begin();
it != regular_users.end(); ++it) {
if (IsDeprecatedSupervisedAccountId(*it)) {
RemoveLegacySupervisedUser(*it);
// Hide legacy supervised users from the login screen if not removed.
continue;
}
UserDirectoryIntegrityManager integrity_manager(local_state_.get());
if (integrity_manager.IsUserMisconfigured(*it)) {
// Skip misconfigured user.
VLOG(1) << "Encountered misconfigured user while loading list of "
"users, skipping";
continue;
}
base::UmaHistogramEnumeration(
kLegacySupervisedUsersHistogramName,
LegacySupervisedUserStatus::kGaiaUserDisplayed);
User* user =
User::CreateRegularUser(*it, GetStoredUserType(prefs_user_types, *it));
user->set_oauth_token_status(LoadUserOAuthStatus(*it));
user->set_force_online_signin(LoadForceOnlineSignin(*it));
KnownUser known_user(local_state_.get());
user->set_using_saml(known_user.IsUsingSAML(*it));
user_storage_.emplace_back(user);
persisted_users_.push_back(user);
}
for (user_manager::User* user : persisted_users_) {
auto& account_id = user->GetAccountId();
const std::string* display_name =
prefs_display_names.FindString(account_id.GetUserEmail());
if (display_name) {
user->set_display_name(base::UTF8ToUTF16(*display_name));
}
const std::string* given_name =
prefs_given_names.FindString(account_id.GetUserEmail());
if (given_name) {
user->set_given_name(base::UTF8ToUTF16(*given_name));
}
const std::string* display_email =
prefs_display_emails.FindString(account_id.GetUserEmail());
if (display_email) {
user->set_display_email(*display_email);
}
}
for (auto& observer : observer_list_) {
observer.OnUserListLoaded();
}
}
void UserManagerImpl::LoadDeviceLocalAccounts(
std::set<AccountId>* device_local_accounts_set) {
const base::Value::List& prefs_device_local_accounts =
GetLocalState()->GetList(prefs::kDeviceLocalAccountsWithSavedData);
std::vector<AccountId> device_local_accounts;
ParseUserList(prefs_device_local_accounts, std::set<AccountId>(),
&device_local_accounts, device_local_accounts_set);
for (const AccountId& account_id : device_local_accounts) {
if (IsDeprecatedArcKioskAccountId(account_id)) {
RemoveDeprecatedArcKioskUser(account_id);
// Remove or hide deprecated ARC kiosk users from the login screen.
continue;
}
auto type =
delegate_->GetDeviceLocalAccountUserType(account_id.GetUserEmail());
if (!type.has_value()) {
NOTREACHED();
}
// Using `new` to access a non-public constructor.
user_storage_.push_back(base::WrapUnique(new User(account_id, *type)));
persisted_users_.push_back(user_storage_.back().get());
}
}
void UserManagerImpl::RemovePendingDeviceLocalAccount() {
PrefService* local_state = GetLocalState();
const std::string device_local_account_pending_data_removal =
local_state->GetString(prefs::kDeviceLocalAccountPendingDataRemoval);
if (device_local_account_pending_data_removal.empty() ||
(IsUserLoggedIn() &&
device_local_account_pending_data_removal ==
GetActiveUser()->GetAccountId().GetUserEmail())) {
return;
}
RemoveUserFromListImpl(
AccountId::FromUserEmail(device_local_account_pending_data_removal),
user_manager::UserRemovalReason::DEVICE_LOCAL_ACCOUNT_UPDATED,
/*trigger_cryptohome_removal=*/false);
local_state->ClearPref(prefs::kDeviceLocalAccountPendingDataRemoval);
}
UserList& UserManagerImpl::GetUsersAndModify() {
return persisted_users_;
}
const User* UserManagerImpl::FindUserInList(const AccountId& account_id) const {
for (const User* user : persisted_users_) {
if (user->GetAccountId() == account_id) {
return user;
}
}
return nullptr;
}
bool UserManagerImpl::UserExistsInList(const AccountId& account_id) const {
const base::Value::List& user_list =
local_state_->GetList(prefs::kRegularUsersPref);
for (const base::Value& i : user_list) {
const std::string* email = i.GetIfString();
if (email && (account_id.GetUserEmail() == *email)) {
return true;
}
}
return false;
}
User* UserManagerImpl::FindUserInListAndModify(const AccountId& account_id) {
UserList& users = GetUsersAndModify();
for (UserList::iterator it = users.begin(); it != users.end(); ++it) {
if ((*it)->GetAccountId() == account_id) {
return *it;
}
}
return nullptr;
}
// TODO(crbug.com/278643115): Verify AccountId uniqueness in the following
// User creation methods.
User* UserManagerImpl::AddGaiaUser(const AccountId& account_id,
UserType user_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* user = User::CreateRegularUser(account_id, user_type);
user_storage_.emplace_back(user);
SaveUserType(user);
user->set_oauth_token_status(LoadUserOAuthStatus(account_id));
SaveUserDisplayName(account_id,
base::UTF8ToUTF16(user->GetAccountName(
/*use_display_email=*/true)));
// Add to the persisted list.
persisted_users_.push_back(user);
ScopedListPrefUpdate prefs_users_update(local_state_.get(),
prefs::kRegularUsersPref);
prefs_users_update->Append(base::Value(account_id.GetUserEmail()));
return user;
}
User* UserManagerImpl::AddEphemeralUser(const AccountId& account_id,
UserType user_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* user = User::CreateRegularUser(account_id, user_type);
user_storage_.emplace_back(user);
return user;
}
User* UserManagerImpl::AddGuestUser() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* user = User::CreateGuestUser(GuestAccountId());
user_storage_.emplace_back(user);
return user;
}
User* UserManagerImpl::AddPublicAccountUser(const AccountId& account_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* user = User::CreatePublicAccountUser(account_id);
user_storage_.emplace_back(user);
return user;
}
bool UserManagerImpl::OnUserProfileCreated(const AccountId& account_id,
PrefService* prefs) {
// Find a User from `user_storage_`.
// FindUserAndModify may overlook some existing User instance, because
// the list may not contain ephemeral users that are getting stale.
auto it = std::ranges::find(user_storage_, account_id,
[](auto& ptr) { return ptr->GetAccountId(); });
auto* user = it == user_storage_.end() ? nullptr : it->get();
CHECK(user);
if (user->is_profile_created()) {
// This happens sometimes in browser_tests.
// See also kIgnoreUserProfileMappingForTests and its uses.
// TODO(b/294452567): Consider how to remove this workaround for testing.
LOG(ERROR) << "user profile duplicated";
CHECK_IS_TEST();
return false;
}
// Update `user` profile profs before profile created notification so that
// observers could get profile prefs via `user`.
CHECK(!user->GetProfilePrefs());
user->SetProfilePrefs(prefs);
user->SetProfileIsCreated();
observer_list_.Notify(&UserManager::Observer::OnUserProfileCreated, *user);
return true;
}
void UserManagerImpl::OnUserProfileWillBeDestroyed(
const AccountId& account_id) {
// Find from user_stroage_. See OnUserProfileCreated for the reason why not
// using FindUserAndModify.
auto it = std::ranges::find(user_storage_, account_id,
[](auto& ptr) { return ptr->GetAccountId(); });
auto* user = it == user_storage_.end() ? nullptr : it->get();
CHECK(user);
observer_list_.Notify(&UserManager::Observer::OnUserProfileWillBeDestroyed,
*user);
user->SetProfilePrefs(nullptr);
}
void UserManagerImpl::NotifyActiveUserChanged(User* active_user) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : session_state_observer_list_) {
observer.ActiveUserChanged(active_user);
}
}
void UserManagerImpl::NotifyLoginStateUpdated() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : session_state_observer_list_) {
observer.OnLoginStateUpdated(active_user_);
}
}
void UserManagerImpl::NotifyOnLogin() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(active_user_);
// TODO(crbug.com/278643115): Migrate into ActiveUserChanged
// and UserAddedToSession calls.
for (auto& observer : observer_list_) {
observer.OnUserLoggedIn(*active_user_);
}
NotifyActiveUserChanged(active_user_);
NotifyLoginStateUpdated();
}
User::OAuthTokenStatus UserManagerImpl::LoadUserOAuthStatus(
const AccountId& account_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const base::Value::Dict& prefs_oauth_status =
local_state_->GetDict(prefs::kUserOAuthTokenStatus);
std::optional<int> oauth_token_status =
prefs_oauth_status.FindInt(account_id.GetUserEmail());
if (!oauth_token_status.has_value()) {
return User::OAUTH_TOKEN_STATUS_UNKNOWN;
}
return static_cast<User::OAuthTokenStatus>(oauth_token_status.value());
}
bool UserManagerImpl::LoadForceOnlineSignin(const AccountId& account_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const base::Value::Dict& prefs_force_online =
local_state_->GetDict(prefs::kUserForceOnlineSignin);
return prefs_force_online.FindBool(account_id.GetUserEmail()).value_or(false);
}
void UserManagerImpl::RemoveNonCryptohomeData(const AccountId& account_id) {
ScopedDictPrefUpdate(local_state_.get(), prefs::kUserDisplayName)
->Remove(account_id.GetUserEmail());
ScopedDictPrefUpdate(local_state_.get(), prefs::kUserGivenName)
->Remove(account_id.GetUserEmail());
ScopedDictPrefUpdate(local_state_.get(), prefs::kUserDisplayEmail)
->Remove(account_id.GetUserEmail());
ScopedDictPrefUpdate(local_state_.get(), prefs::kUserOAuthTokenStatus)
->Remove(account_id.GetUserEmail());
ScopedDictPrefUpdate(local_state_.get(), prefs::kUserForceOnlineSignin)
->Remove(account_id.GetUserEmail());
KnownUser(local_state_.get()).RemovePrefs(account_id);
const AccountId last_active_user =
AccountId::FromUserEmail(local_state_->GetString(prefs::kLastActiveUser));
if (account_id == last_active_user) {
local_state_->SetString(prefs::kLastActiveUser, std::string());
}
}
User* UserManagerImpl::RemoveRegularOrSupervisedUserFromList(
const AccountId& account_id,
bool notify) {
ScopedListPrefUpdate prefs_users_update(local_state_.get(),
prefs::kRegularUsersPref);
prefs_users_update->clear();
User* user = nullptr;
for (UserList::iterator it = persisted_users_.begin();
it != persisted_users_.end();) {
if ((*it)->GetAccountId() == account_id) {
user = *it;
it = persisted_users_.erase(it);
} else {
if ((*it)->HasGaiaAccount()) {
const std::string user_email = (*it)->GetAccountId().GetUserEmail();
prefs_users_update->Append(user_email);
}
++it;
}
}
if (notify) {
NotifyLocalStateChanged();
}
return user;
}
void UserManagerImpl::NotifyUserAddedToSession(const User* added_user) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : session_state_observer_list_) {
observer.UserAddedToSession(added_user);
}
}
PrefService* UserManagerImpl::GetLocalState() const {
return local_state_.get();
}
bool UserManagerImpl::IsFirstExecAfterBoot() const {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kFirstExecAfterBoot);
}
void UserManagerImpl::SetUserPolicyStatus(const AccountId& account_id,
bool is_managed,
bool is_affiliated) {
User* user = FindUserAndModify(account_id);
if (!user) {
return;
}
user->SetUserPolicyStatus(is_managed, is_affiliated);
NotifyUserAffiliationUpdated(*user);
}
bool UserManagerImpl::HasBrowserRestarted() const {
return base::SysInfo::IsRunningOnChromeOS() &&
base::CommandLine::ForCurrentProcess()->HasSwitch(
ash::switches::kLoginUser);
}
void UserManagerImpl::Initialize() {
UserManager::Initialize();
if (!HasBrowserRestarted()) {
// local_state may be null in unit tests.
if (local_state_) {
KnownUser known_user(local_state_.get());
known_user.CleanObsoletePrefs();
}
}
EnsureUsersLoaded();
NotifyLoginStateUpdated();
}
void UserManagerImpl::OnActiveUserSwitched(User& new_active_user) {
if (active_user_) {
active_user_->set_is_active(false);
}
active_user_ = &new_active_user;
active_user_->set_is_active(true);
local_state_->SetString(prefs::kLastActiveUser,
active_user_->GetAccountId().GetUserEmail());
local_state_->CommitPendingWrite();
// Move the user to the front of the list.
UserList::iterator it = std::ranges::find(lru_logged_in_users_, active_user_);
CHECK(it != lru_logged_in_users_.end());
std::rotate(lru_logged_in_users_.begin(), it, it + 1);
}
// static
void UserManagerImpl::UpdateNumLoggedInUsersCrashKey(size_t num_users) {
static crash_reporter::CrashKeyString<64> crash_key("num-users");
crash_key.Set(base::NumberToString(num_users));
}
// static
void UserManagerImpl::UpdateSessionTypeCrashKey(UserType active_user_type) {
static crash_reporter::CrashKeyString<32> session_type("session-type");
session_type.Set(UserTypeToString(active_user_type));
}
void UserManagerImpl::SendGaiaUserLoginMetrics(const AccountId& account_id) {
// If this isn't the first time Chrome was run after the system booted,
// assume that Chrome was restarted because a previous session ended.
if (IsFirstExecAfterBoot()) {
return;
}
const std::string last_email =
local_state_->GetString(prefs::kLastLoggedInGaiaUser);
const base::TimeDelta time_to_login =
base::TimeTicks::Now() - manager_creation_time_;
if (!last_email.empty() &&
account_id != AccountId::FromUserEmail(last_email) &&
time_to_login.InSeconds() <= kLogoutToLoginDelayMaxSec) {
UMA_HISTOGRAM_CUSTOM_COUNTS("UserManager.LogoutToLoginDelay",
time_to_login.InSeconds(), 1,
kLogoutToLoginDelayMaxSec, 50);
}
}
void UserManagerImpl::SendMultiUserSignInMetrics() {
size_t users = logged_in_users_.size();
if (!users) {
return;
}
// Write the user number as UMA stat when a multi user session is possible.
if (users + GetUsersAllowedForMultiUserSignIn().size() > 1) {
// Keep MultiProfile name here for compatibility of historical reason.
// It is for multi-user sign-in.
UMA_HISTOGRAM_COUNTS_100("MultiProfile.UsersPerSessionIncremental", users);
}
}
void UserManagerImpl::UpdateUserAccountLocale(const AccountId& account_id,
const std::string& locale) {
if (!locale.empty() && locale != delegate_->GetApplicationLocale()) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(
[](const std::string& locale) {
std::string resolved_locale;
std::ignore =
l10n_util::CheckAndResolveLocale(locale, &resolved_locale);
return resolved_locale;
},
locale),
base::BindOnce(&UserManagerImpl::DoUpdateAccountLocale,
weak_factory_.GetWeakPtr(), account_id));
} else {
DoUpdateAccountLocale(account_id, locale);
}
}
void UserManagerImpl::DoUpdateAccountLocale(
const AccountId& account_id,
const std::string& resolved_locale) {
User* user = FindUserAndModify(account_id);
if (user) {
user->SetAccountLocale(resolved_locale);
}
}
void UserManagerImpl::DeleteUser(User* user) {
if (active_user_ == user) {
active_user_ = nullptr;
}
if (primary_user_ == user) {
primary_user_ = nullptr;
}
std::erase(persisted_users_, user);
std::erase(logged_in_users_, user);
std::erase(lru_logged_in_users_, user);
std::erase_if(user_storage_, [user](auto& ptr) { return ptr.get() == user; });
}
// TODO(crbug.com/40755604): Remove dormant legacy supervised user cryptohomes.
// After we have enough confidence that there are no more supervised users on
// devices in the wild, remove this.
void UserManagerImpl::RemoveLegacySupervisedUser(const AccountId& account_id) {
DCHECK(IsDeprecatedSupervisedAccountId(account_id));
// Since we skip adding legacy supervised users to the users list,
// FindUser(account_id) returns nullptr and CanUserBeRemoved() returns
// false. This is why we call RemoveUserInternal() directly instead of
// RemoveUser().
RemoveUserInternal(account_id, UserRemovalReason::UNKNOWN);
base::UmaHistogramEnumeration(kLegacySupervisedUsersHistogramName,
LegacySupervisedUserStatus::kLSUDeleted);
}
bool UserManagerImpl::IsDeprecatedArcKioskAccountId(
const AccountId& account_id) const {
return gaia::ExtractDomainName(account_id.GetUserEmail()) == kArcKioskDomain;
}
// TODO(b/355590943): Remove dormant deprecated ARC kiosk user cryptohomes.
// Remove this once confident that all ARC kiosk cryptohomes are cleaned up.
void UserManagerImpl::RemoveDeprecatedArcKioskUser(
const AccountId& account_id) {
CHECK(IsDeprecatedArcKioskAccountId(account_id));
if (base::FeatureList::IsEnabled(kRemoveDeprecatedArcKioskUsersOnStartup)) {
RemoveUserInternal(account_id, UserRemovalReason::UNKNOWN);
base::UmaHistogramEnumeration(kDeprecatedArcKioskUsersHistogramName,
DeprecatedArcKioskUserStatus::kDeleted);
} else {
base::UmaHistogramEnumeration(kDeprecatedArcKioskUsersHistogramName,
DeprecatedArcKioskUserStatus::kHidden);
}
}
bool UserManagerImpl::IsEnterpriseManaged() const {
if (!ash::InstallAttributes::IsInitialized()) {
// In unit tests, InstallAttributes may not be initialized.
// For enterprise test cases, it must be.
CHECK_IS_TEST();
return false;
}
return ash::InstallAttributes::Get()->IsEnterpriseManaged();
}
} // namespace user_manager