// 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, ®ular_users, ®ular_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