| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/signin/model/authentication_service.h" |
| |
| #import "base/auto_reset.h" |
| #import "base/check_is_test.h" |
| #import "base/containers/to_vector.h" |
| #import "base/functional/bind.h" |
| #import "base/functional/callback_helpers.h" |
| #import "base/location.h" |
| #import "base/logging.h" |
| #import "base/metrics/histogram_functions.h" |
| #import "base/metrics/histogram_macros.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/task/single_thread_task_runner.h" |
| #import "components/browser_sync/sync_to_signin_migration.h" |
| #import "components/policy/core/common/management/platform_management_service.h" |
| #import "components/pref_registry/pref_registry_syncable.h" |
| #import "components/prefs/pref_service.h" |
| #import "components/signin/ios/browser/features.h" |
| #import "components/signin/public/base/gaia_id_hash.h" |
| #import "components/signin/public/base/signin_pref_names.h" |
| #import "components/signin/public/base/signin_switches.h" |
| #import "components/signin/public/identity_manager/account_info.h" |
| #import "components/signin/public/identity_manager/device_accounts_synchronizer.h" |
| #import "components/signin/public/identity_manager/primary_account_mutator.h" |
| #import "components/signin/public/identity_manager/signin_constants.h" |
| #import "components/signin/public/identity_manager/tribool.h" |
| #import "components/sync/base/account_pref_utils.h" |
| #import "components/sync/service/sync_service.h" |
| #import "components/sync/service/sync_user_settings.h" |
| #import "google_apis/gaia/gaia_auth_util.h" |
| #import "google_apis/gaia/gaia_id.h" |
| #import "ios/chrome/browser/bookmarks/model/bookmarks_utils.h" |
| #import "ios/chrome/browser/crash_report/model/crash_keys_helper.h" |
| #import "ios/chrome/browser/policy/model/policy_util.h" |
| #import "ios/chrome/browser/shared/model/application_context/application_context.h" |
| #import "ios/chrome/browser/shared/model/prefs/pref_names.h" |
| #import "ios/chrome/browser/shared/model/profile/features.h" |
| #import "ios/chrome/browser/shared/model/profile/profile_attributes_ios.h" |
| #import "ios/chrome/browser/shared/model/profile/profile_attributes_storage_ios.h" |
| #import "ios/chrome/browser/shared/model/profile/profile_manager_ios.h" |
| #import "ios/chrome/browser/shared/public/features/features.h" |
| #import "ios/chrome/browser/shared/public/features/system_flags.h" |
| #import "ios/chrome/browser/signin/model/authentication_service_delegate.h" |
| #import "ios/chrome/browser/signin/model/authentication_service_observer.h" |
| #import "ios/chrome/browser/signin/model/refresh_access_token_error.h" |
| #import "ios/chrome/browser/signin/model/signin_util.h" |
| #import "ios/chrome/browser/signin/model/system_identity.h" |
| #import "ios/chrome/browser/signin/model/system_identity_manager.h" |
| #import "ios/chrome/browser/signin/model/system_identity_util.h" |
| #import "ios/chrome/common/app_group/app_group_constants.h" |
| |
| using signin::constants::kNoHostedDomainFound; |
| |
| namespace { |
| |
| // Enum for Signin.IOSDeviceRestoreSignedInState histogram. |
| // Entries should not be renumbered and numeric values should never be reused. |
| enum class IOSDeviceRestoreSignedinState : int { |
| // Case when the user is not signed in before the device restore. |
| kUserNotSignedInBeforeDeviceRestore = 0, |
| // Case when the user is signed in before the device restore but not after. |
| kUserSignedInBeforeDeviceRestoreAndSignedOutAfterDeviceRestore = 1, |
| // Case when the user is signed in before and after the device restore. |
| kUserSignedInBeforeAndAfterDeviceRestore = 2, |
| kMaxValue = kUserSignedInBeforeAndAfterDeviceRestore, |
| }; |
| |
| // Returns the account id associated with `identity`. |
| CoreAccountId SystemIdentityToAccountID( |
| signin::IdentityManager* identity_manager, |
| id<SystemIdentity> identity) { |
| GaiaId gaia_id([identity gaiaID]); |
| std::string email = base::SysNSStringToUTF8([identity userEmail]); |
| return identity_manager->PickAccountIdForAccount(gaia_id, email); |
| } |
| |
| } // namespace |
| |
| AuthenticationService::AuthenticationService( |
| PrefService* pref_service, |
| ChromeAccountManagerService* account_manager_service, |
| signin::IdentityManager* identity_manager, |
| syncer::SyncService* sync_service) |
| : pref_service_(pref_service), |
| account_manager_service_(account_manager_service), |
| identity_manager_(identity_manager), |
| sync_service_(sync_service), |
| weak_pointer_factory_(this) { |
| DCHECK(pref_service_); |
| DCHECK(identity_manager_); |
| DCHECK(sync_service_); |
| } |
| |
| AuthenticationService::~AuthenticationService() { |
| DCHECK(!delegate_); |
| } |
| |
| // static |
| void AuthenticationService::RegisterPrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterBooleanPref(prefs::kSigninShouldPromptForSigninAgain, |
| false); |
| } |
| |
| void AuthenticationService::Initialize( |
| std::unique_ptr<AuthenticationServiceDelegate> delegate) { |
| CHECK(delegate); |
| CHECK(!initialized()); |
| bool has_primary_account_before_initialize = |
| identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin); |
| int account_count_before_initialize = |
| identity_manager_->GetAccountsWithRefreshTokens().size(); |
| delegate_ = std::move(delegate); |
| signin::Tribool device_restore_session = IsFirstSessionAfterDeviceRestore(); |
| initialized_ = true; |
| |
| identity_manager_observation_.Observe(identity_manager_.get()); |
| HandleForgottenIdentity(nil, |
| device_restore_session == signin::Tribool::kTrue); |
| |
| // Clean up account-scoped settings, in case any accounts were removed from |
| // the device while Chrome wasn't running. |
| ClearAccountSettingsPrefsOfRemovedAccounts(); |
| |
| crash_keys::SetCurrentlySignedIn( |
| HasPrimaryIdentity(signin::ConsentLevel::kSignin)); |
| |
| account_manager_service_observation_.Observe(account_manager_service_.get()); |
| |
| // Synchronize local state and profile signin prefs. This is needed because |
| // many low level services still rely on the profile pref. |
| PrefService* local_pref_service = GetApplicationContext()->GetLocalState(); |
| const PrefService::Preference* signin_allowed_on_device = |
| local_pref_service->FindPreference(prefs::kSigninAllowedOnDevice); |
| CHECK(signin_allowed_on_device); |
| const PrefService::Preference* signin_allowed_profile = |
| pref_service_->FindPreference(prefs::kSigninAllowed); |
| CHECK(signin_allowed_profile); |
| // One time migration from the profile prefs to the local state if the local |
| // state is still using the default value. Otherwise update the profile pref |
| // to match the local state value which is the source of truth. |
| if (signin_allowed_on_device->IsDefaultValue()) { |
| local_pref_service->Set(prefs::kSigninAllowedOnDevice, |
| *signin_allowed_profile->GetValue()); |
| } else { |
| pref_service_->Set(prefs::kSigninAllowed, |
| *signin_allowed_on_device->GetValue()); |
| } |
| |
| // Register for prefs::kBrowserSigninPolicy. |
| local_pref_change_registrar_.Init(local_pref_service); |
| PrefChangeRegistrar::NamedChangeCallback browser_signin_policy_callback = |
| base::BindRepeating(&AuthenticationService::OnBrowserSigninPolicyChanged, |
| base::Unretained(this)); |
| local_pref_change_registrar_.Add(prefs::kBrowserSigninPolicy, |
| browser_signin_policy_callback); |
| |
| // Register for prefs::kSigninAllowedOnDevice. |
| PrefChangeRegistrar::NamedChangeCallback signin_allowed_on_device_callback = |
| base::BindRepeating( |
| &AuthenticationService::OnSigninAllowedOnDeviceChanged, |
| base::Unretained(this)); |
| local_pref_change_registrar_.Add(prefs::kSigninAllowedOnDevice, |
| signin_allowed_on_device_callback); |
| |
| // Register for prefs::kSigninAllowed. |
| pref_change_registrar_.Init(pref_service_); |
| PrefChangeRegistrar::NamedChangeCallback signin_allowed_callback = |
| base::BindRepeating(&AuthenticationService::OnSigninAllowedChanged, |
| base::Unretained(this)); |
| pref_change_registrar_.Add(prefs::kSigninAllowed, signin_allowed_callback); |
| |
| // Migrate primary identity info to widgets if needed. |
| NSUserDefaults* shared_defaults = app_group::GetGroupUserDefaults(); |
| NSString* primary_account = |
| [shared_defaults objectForKey:app_group::kPrimaryAccount]; |
| |
| if (!primary_account || primary_account.length == 0) { |
| id<SystemIdentity> identity = |
| GetPrimaryIdentity(signin::ConsentLevel::kSignin); |
| if (identity.gaiaID) { |
| [shared_defaults setObject:identity.gaiaID |
| forKey:app_group::kPrimaryAccount]; |
| } |
| } |
| |
| // Reload credentials to ensure the accounts from the token service are |
| // up-to-date. |
| ReloadCredentialsFromIdentities(); |
| |
| OnApplicationWillEnterForeground(); |
| bool has_primary_account_after_initialize = |
| identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin); |
| DCHECK(!has_primary_account_after_initialize || |
| has_primary_account_before_initialize); |
| if (device_restore_session == signin::Tribool::kTrue) { |
| // Records device restore histograms. |
| if (has_primary_account_before_initialize) { |
| base::UmaHistogramCounts100("Signin.IOSDeviceRestoreIdentityCountBefore", |
| account_count_before_initialize); |
| int account_count_after_initialize = |
| identity_manager_->GetAccountsWithRefreshTokens().size(); |
| base::UmaHistogramCounts100("Signin.IOSDeviceRestoreIdentityCountAfter", |
| account_count_after_initialize); |
| } |
| IOSDeviceRestoreSignedinState signed_in_state = |
| IOSDeviceRestoreSignedinState::kUserNotSignedInBeforeDeviceRestore; |
| if (has_primary_account_before_initialize) { |
| signed_in_state = |
| has_primary_account_after_initialize |
| ? IOSDeviceRestoreSignedinState:: |
| kUserSignedInBeforeAndAfterDeviceRestore |
| : IOSDeviceRestoreSignedinState:: |
| kUserSignedInBeforeDeviceRestoreAndSignedOutAfterDeviceRestore; |
| } |
| base::UmaHistogramEnumeration("Signin.IOSDeviceRestoreSignedInState", |
| signed_in_state); |
| } |
| |
| ProfileInitializationOutcome outcome = |
| PerformProfileInitializationIfNecessary(); |
| base::UmaHistogramEnumeration( |
| "Signin.IOSAuthenticationServiceInitializationOutcome", outcome); |
| } |
| |
| void AuthenticationService::Shutdown() { |
| identity_manager_observation_.Reset(); |
| account_manager_service_observation_.Reset(); |
| delegate_.reset(); |
| } |
| |
| void AuthenticationService::AddObserver( |
| AuthenticationServiceObserver* observer) { |
| observer_list_.AddObserver(observer); |
| // Handle messages for late observers. |
| if (primary_account_was_restricted_) { |
| observer->OnPrimaryAccountRestricted(); |
| } |
| } |
| |
| void AuthenticationService::RemoveObserver( |
| AuthenticationServiceObserver* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| AuthenticationService::ServiceStatus AuthenticationService::GetServiceStatus() |
| const { |
| if (!account_manager_service_->IsServiceSupported()) { |
| return ServiceStatus::SigninDisabledByInternal; |
| } |
| PrefService* local_pref_service = GetApplicationContext()->GetLocalState(); |
| BrowserSigninMode policy_mode = static_cast<BrowserSigninMode>( |
| local_pref_service->GetInteger(prefs::kBrowserSigninPolicy)); |
| switch (policy_mode) { |
| case BrowserSigninMode::kDisabled: |
| return ServiceStatus::SigninDisabledByPolicy; |
| case BrowserSigninMode::kForced: |
| return ServiceStatus::SigninForcedByPolicy; |
| case BrowserSigninMode::kEnabled: |
| break; |
| } |
| if (!local_pref_service->GetBoolean(prefs::kSigninAllowedOnDevice)) { |
| return ServiceStatus::SigninDisabledByUser; |
| } |
| return ServiceStatus::SigninAllowed; |
| } |
| |
| void AuthenticationService::OnApplicationWillEnterForeground() { |
| if (GetServiceStatus() != |
| AuthenticationService::ServiceStatus::SigninDisabledByInternal) { |
| UMA_HISTOGRAM_COUNTS_100( |
| "Signin.IOSNumberOfDeviceAccounts", |
| [account_manager_service_->GetAllIdentities() count]); |
| } |
| |
| // Clear signin errors on the accounts that had a specific MDM device status. |
| // This will trigger services to fetch data for these accounts again. |
| using std::swap; |
| std::map<CoreAccountId, id<RefreshAccessTokenError>> cached_mdm_errors; |
| swap(cached_mdm_errors_, cached_mdm_errors); |
| |
| if (!cached_mdm_errors.empty()) { |
| signin::DeviceAccountsSynchronizer* device_accounts_synchronizer = |
| identity_manager_->GetDeviceAccountsSynchronizer(); |
| for (const auto& pair : cached_mdm_errors) { |
| const CoreAccountId& account_id = pair.first; |
| // For some reasons, it is possible to have a MDM error for an unknown |
| // identity. This MDM error can be ignored. |
| // See crbug.com/1482236. |
| if (identity_manager_->HasAccountWithRefreshToken(account_id)) { |
| device_accounts_synchronizer->ReloadAccountFromSystem(account_id); |
| } |
| } |
| } |
| } |
| |
| bool AuthenticationService::SigninEnabled() const { |
| switch (GetServiceStatus()) { |
| case AuthenticationService::ServiceStatus::SigninForcedByPolicy: |
| case AuthenticationService::ServiceStatus::SigninAllowed: |
| return YES; |
| case AuthenticationService::ServiceStatus::SigninDisabledByUser: |
| case AuthenticationService::ServiceStatus::SigninDisabledByPolicy: |
| case AuthenticationService::ServiceStatus::SigninDisabledByInternal: |
| return NO; |
| } |
| } |
| |
| void AuthenticationService::SetReauthPromptForSignInAndSync() { |
| pref_service_->SetBoolean(prefs::kSigninShouldPromptForSigninAgain, true); |
| } |
| |
| void AuthenticationService::ResetReauthPromptForSignInAndSync() { |
| pref_service_->SetBoolean(prefs::kSigninShouldPromptForSigninAgain, false); |
| } |
| |
| bool AuthenticationService::ShouldReauthPromptForSignInAndSync() const { |
| return pref_service_->GetBoolean(prefs::kSigninShouldPromptForSigninAgain); |
| } |
| |
| bool AuthenticationService::HasPrimaryIdentity( |
| signin::ConsentLevel consent_level) const { |
| return GetPrimaryIdentity(consent_level) != nil; |
| } |
| |
| bool AuthenticationService::HasPrimaryIdentityManaged( |
| signin::ConsentLevel consent_level) const { |
| return identity_manager_ |
| ->FindExtendedAccountInfo( |
| identity_manager_->GetPrimaryAccountInfo(consent_level)) |
| .IsManaged() == signin::Tribool::kTrue; |
| } |
| |
| bool AuthenticationService::ShouldClearDataForSignedInPeriodOnSignOut() const { |
| // Data on the device should be cleared on signout when all conditions are |
| // met: |
| // 1. The user is signed in with a managed account. |
| // 2. The app management configuration key is present. |
| // Note: data will be cleared from the time of sign-in in this case. |
| return HasPrimaryIdentityManaged(signin::ConsentLevel::kSignin) && |
| !policy::PlatformManagementService::GetInstance()->IsManaged(); |
| } |
| |
| id<SystemIdentity> AuthenticationService::GetPrimaryIdentity( |
| signin::ConsentLevel consent_level) const { |
| return GetPrimarySystemIdentity(consent_level, identity_manager_, |
| account_manager_service_); |
| } |
| |
| void AuthenticationService::SignIn(id<SystemIdentity> identity, |
| signin_metrics::AccessPoint access_point) { |
| CHECK(SigninEnabled()) << "Service status " |
| << static_cast<int>(GetServiceStatus()); |
| DCHECK(account_manager_service_->IsValidIdentity(identity)); |
| |
| primary_account_was_restricted_ = false; |
| |
| ResetReauthPromptForSignInAndSync(); |
| |
| // TODO(crbug.com/40266839): Move this reset to a place more consistent with |
| // bookmarks. |
| ResetLastUsedBookmarkFolder(pref_service_); |
| |
| // Load all credentials from SSO library. This must load the credentials |
| // for the primary account too. |
| identity_manager_->GetDeviceAccountsSynchronizer() |
| ->ReloadAllAccountsFromSystemWithPrimaryAccount(CoreAccountId()); |
| |
| const CoreAccountId account_id = identity_manager_->PickAccountIdForAccount( |
| GaiaId(identity.gaiaID), base::SysNSStringToUTF8(identity.userEmail)); |
| |
| // Ensure that the account the user is trying to sign into has been loaded |
| // from the SSO library. |
| CHECK(identity_manager_->HasAccountWithRefreshToken(account_id)); |
| const AccountInfo account_info = |
| identity_manager_->FindExtendedAccountInfoByAccountId(account_id); |
| CHECK(!account_info.IsEmpty()); |
| |
| // `PrimaryAccountMutator::SetPrimaryAccount` simply ignores the call |
| // if there is already a signed in user. Check that there is no signed in |
| // account or that the new signed in account matches the old one to avoid a |
| // mismatch between the old and the new authenticated accounts. |
| if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { |
| DCHECK(identity_manager_->GetPrimaryAccountMutator()); |
| signin::PrimaryAccountMutator::PrimaryAccountError error = |
| identity_manager_->GetPrimaryAccountMutator()->SetPrimaryAccount( |
| account_id, signin::ConsentLevel::kSignin, access_point); |
| CHECK_EQ(signin::PrimaryAccountMutator::PrimaryAccountError::kNoError, |
| error); |
| } |
| |
| // The primary account should now be set to the expected account_id. |
| // If CHECK_EQ() fails, having the CHECK() before would help to understand if |
| // the primary account is empty or different that `account_id`. |
| // Related to crbug.com/1308448. |
| CoreAccountId primary_account = |
| identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin); |
| CHECK(!primary_account.empty()); |
| CHECK_EQ(account_id, primary_account); |
| pref_service_->SetTime(prefs::kLastSigninTimestamp, base::Time::Now()); |
| |
| PrefService* local_pref_service = GetApplicationContext()->GetLocalState(); |
| local_pref_service->SetTime( |
| prefs::kIdentityConfirmationSnackbarLastPromptTime, base::Time::Now()); |
| local_pref_service->SetInteger( |
| prefs::kIdentityConfirmationSnackbarDisplayCount, 0); |
| crash_keys::SetCurrentlySignedIn(true); |
| } |
| |
| void AuthenticationService::SignOut( |
| signin_metrics::ProfileSignout signout_source, |
| ProceduralBlock completion) { |
| if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { |
| if (completion) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(completion)); |
| } |
| return; |
| } |
| |
| // TODO(crbug.com/40266839): Move this reset to a place more consistent with |
| // bookmarks. |
| ResetLastUsedBookmarkFolder(pref_service_); |
| |
| const bool is_managed = |
| HasPrimaryIdentityManaged(signin::ConsentLevel::kSignin); |
| const bool is_migrated_from_syncing = |
| browser_sync::WasPrimaryAccountMigratedFromSyncingToSignedIn( |
| identity_manager_, pref_service_); |
| const bool should_clear_data_for_signed_in_period = |
| ShouldClearDataForSignedInPeriodOnSignOut(); |
| |
| auto* account_mutator = identity_manager_->GetPrimaryAccountMutator(); |
| // GetPrimaryAccountMutator() returns nullptr on ChromeOS only. |
| DCHECK(account_mutator); |
| |
| account_mutator->ClearPrimaryAccount(signout_source); |
| crash_keys::SetCurrentlySignedIn(false); |
| cached_mdm_errors_.clear(); |
| |
| // ClearPrimaryAccount() removed all the accounts from IdentityManager. |
| // Populate them again. |
| ReloadCredentialsFromIdentities(); |
| |
| base::OnceClosure callback_closure = |
| completion ? base::BindOnce(completion) : base::DoNothing(); |
| |
| // Note: Once `kSeparateProfilesForManagedAccounts` is launched, the "clear |
| // browsing data" cases are only reachable for managed accounts that were |
| // already signed in before that feature was enabled. Once those users have |
| // been migrated, this code can be cleaned up. |
| if (is_managed && is_migrated_from_syncing) { |
| // If `is_clear_data_feature_for_managed_users_enabled` is false, browsing |
| // data for managed account needs to be cleared only if sync has started at |
| // least once. This also includes the case where a previously-syncing user |
| // was migrated to signed-in. |
| delegate_->ClearBrowsingData(std::move(callback_closure)); |
| } else if (should_clear_data_for_signed_in_period) { |
| delegate_->ClearBrowsingDataForSignedinPeriod(std::move(callback_closure)); |
| } else if (completion) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, std::move(callback_closure)); |
| } |
| } |
| |
| AuthenticationService::ProfileInitializationOutcome |
| AuthenticationService::PerformProfileInitializationIfNecessary() { |
| ProfileManagerIOS* profile_manager = |
| GetApplicationContext()->GetProfileManager(); |
| if (!profile_manager) { |
| // Skip if there is no profile manager, but this is possible only for test. |
| CHECK_IS_TEST(); |
| return ProfileInitializationOutcome::kNoneForTesting; |
| } |
| ProfileAttributesStorageIOS* attributes_storage = |
| profile_manager->GetProfileAttributesStorage(); |
| |
| const std::string profile_name = account_manager_service_->GetProfileName(); |
| |
| // Once this method returns, the profile is considered fully initialized. (If |
| // the profile was already initialized, this is a no-op.) |
| base::ScopedClosureRunner mark_profile_initialized(base::BindOnce( |
| [](ProfileAttributesStorageIOS* attributes_storage, |
| std::string_view profile_name) { |
| attributes_storage->UpdateAttributesForProfileWithName( |
| profile_name, base::BindOnce([](ProfileAttributesIOS& attrs) { |
| attrs.SetFullyInitialized(); |
| })); |
| }, |
| attributes_storage, profile_name)); |
| |
| const bool was_already_initialized = |
| attributes_storage->GetAttributesForProfileWithName(profile_name) |
| .IsFullyInitialized(); |
| |
| if (!AreSeparateProfilesForManagedAccountsEnabled()) { |
| return was_already_initialized |
| ? ProfileInitializationOutcome:: |
| kFeatureDisabledAlreadyInitialized |
| : ProfileInitializationOutcome::kFeatureDisabledNewlyInitialized; |
| } |
| |
| // When opening a managed profile for the first time, the user needs to be |
| // signed in automatically. |
| |
| const bool is_personal_profile = |
| profile_name == attributes_storage->GetPersonalProfileName(); |
| if (is_personal_profile) { |
| // Nothing to do if the current profile is the personal profile. |
| return was_already_initialized |
| ? ProfileInitializationOutcome:: |
| kPersonalProfileAlreadyInitialized |
| : ProfileInitializationOutcome::kPersonalProfileNewlyInitialized; |
| } |
| |
| const bool is_signed_in = HasPrimaryIdentity(signin::ConsentLevel::kSignin); |
| if (is_signed_in) { |
| // Nothing to do if the managed profile is already signed in. |
| return was_already_initialized |
| ? ProfileInitializationOutcome::kManagedProfileAlreadyInitialized |
| : ProfileInitializationOutcome:: |
| kManagedProfileNewlyInitializedButAlreadySignedIn; |
| } |
| |
| NSArray<id<SystemIdentity>>* identities_for_profile = |
| account_manager_service_->GetAllIdentities(); |
| if (identities_for_profile.count == 0) { |
| return was_already_initialized |
| ? ProfileInitializationOutcome:: |
| kManagedProfileAlreadyInitializedNoAccounts |
| : ProfileInitializationOutcome:: |
| kManagedProfileNewlyInitializedNoAccounts; |
| } |
| |
| SignIn(identities_for_profile[0], |
| signin_metrics::AccessPoint::kManagedProfileAutoSigninIos); |
| if (identities_for_profile.count > 1) { |
| return was_already_initialized |
| ? ProfileInitializationOutcome:: |
| kManagedProfileAlreadyInitializedMultipleAccountsAndNewlySignedIn |
| : ProfileInitializationOutcome:: |
| kManagedProfileNewlyInitializedMultipleAccounts; |
| } |
| return was_already_initialized |
| ? ProfileInitializationOutcome:: |
| kManagedProfileAlreadyInitializedButNewlySignedIn |
| : ProfileInitializationOutcome::kManagedProfileNewlyInitialized; |
| } |
| |
| id<RefreshAccessTokenError> AuthenticationService::GetCachedMDMError( |
| id<SystemIdentity> identity) { |
| CoreAccountId account_id = |
| SystemIdentityToAccountID(identity_manager_, identity); |
| auto it = cached_mdm_errors_.find(account_id); |
| if (it == cached_mdm_errors_.end()) { |
| return nil; |
| } |
| |
| if (!identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState( |
| account_id)) { |
| // Account has no error, invalidate the cache. |
| cached_mdm_errors_.erase(it); |
| return nil; |
| } |
| |
| return it->second; |
| } |
| |
| bool AuthenticationService::HasCachedMDMErrorForIdentity( |
| id<SystemIdentity> identity) { |
| return GetCachedMDMError(identity) != nil; |
| } |
| |
| bool AuthenticationService::ShowMDMErrorDialogForIdentity( |
| id<SystemIdentity> identity) { |
| id<RefreshAccessTokenError> cached_error = GetCachedMDMError(identity); |
| if (!cached_error) { |
| return false; |
| } |
| |
| GetApplicationContext()->GetSystemIdentityManager()->HandleMDMNotification( |
| identity, ActiveIdentities(), cached_error, base::DoNothing()); |
| |
| return true; |
| } |
| |
| base::WeakPtr<AuthenticationService> AuthenticationService::GetWeakPtr() { |
| return weak_pointer_factory_.GetWeakPtr(); |
| } |
| |
| void AuthenticationService::OnPrimaryAccountChanged( |
| const signin::PrimaryAccountChangeEvent& event_details) { |
| for (auto& observer : observer_list_) { |
| observer.OnPrimaryIdentityChanged(); |
| } |
| } |
| |
| void AuthenticationService::OnIdentitiesInProfileChanged() { |
| ClearAccountSettingsPrefsOfRemovedAccounts(); |
| ReloadCredentialsFromIdentities(); |
| } |
| |
| bool AuthenticationService::HandleMDMError(id<SystemIdentity> identity, |
| id<RefreshAccessTokenError> error) { |
| id<RefreshAccessTokenError> cached_error = GetCachedMDMError(identity); |
| if (cached_error && [cached_error isEqualToError:error]) { |
| // Same status as the last error, ignore it to avoid spamming users. |
| return false; |
| } |
| |
| SystemIdentityManager* system_identity_manager = |
| GetApplicationContext()->GetSystemIdentityManager(); |
| if (base::FeatureList::IsEnabled(switches::kAllowlistScopesForMdmErrors)) { |
| bool scope_limited_error_suppressed = |
| system_identity_manager->IsScopeLimitedError(error); |
| base::UmaHistogramBoolean("Signin.ScopeLimitedErrorSuppressed", |
| scope_limited_error_suppressed); |
| if (scope_limited_error_suppressed) { |
| return false; |
| } |
| } |
| |
| if (system_identity_manager->HandleMDMNotification( |
| identity, ActiveIdentities(), error, |
| base::BindOnce(&AuthenticationService::MDMErrorHandled, |
| weak_pointer_factory_.GetWeakPtr(), identity))) { |
| CoreAccountId account_id = |
| SystemIdentityToAccountID(identity_manager_, identity); |
| DUMP_WILL_BE_CHECK(!account_id.empty()) |
| << "Unexpected identity with empty account id: [gaiaID = " |
| << identity.gaiaID << "; userEmail = " << identity.userEmail << "]"; |
| cached_mdm_errors_[account_id] = error; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void AuthenticationService::MDMErrorHandled(id<SystemIdentity> identity, |
| bool is_blocked) { |
| // If the identity is blocked, sign-out of the account. As only managed |
| // account can be blocked, this will clear the associated browsing data. |
| if (!is_blocked) { |
| return; |
| } |
| |
| if (![identity isEqual:GetPrimaryIdentity(signin::ConsentLevel::kSignin)]) { |
| return; |
| } |
| |
| SignOut(signin_metrics::ProfileSignout::kAbortSignin, nil); |
| } |
| |
| void AuthenticationService::OnRefreshTokenUpdated(id<SystemIdentity> identity) { |
| const CoreAccountId account_id = identity_manager_->PickAccountIdForAccount( |
| GaiaId(identity.gaiaID), base::SysNSStringToUTF8(identity.userEmail)); |
| if (!identity_manager_->HasAccountWithRefreshToken(account_id)) { |
| return; |
| } |
| identity_manager_->GetDeviceAccountsSynchronizer()->ReloadAccountFromSystem( |
| account_id); |
| } |
| |
| void AuthenticationService::OnAccessTokenRefreshFailed( |
| id<SystemIdentity> identity, |
| id<RefreshAccessTokenError> error, |
| const std::set<std::string>& scopes) { |
| if (!identity) { |
| DLOG(ERROR) |
| << "Unexpected call of OnAccessTokenRefreshFailed with null identity"; |
| return; |
| } |
| |
| if (HandleMDMError(identity, error)) { |
| return; |
| } |
| |
| if (!error.isInvalidGrantError) { |
| // If the failure is not due to an invalid grant, the identity is not |
| // invalid and there is nothing to do. |
| return; |
| } |
| |
| // Handle the failure of access token refresh on the next message loop cycle. |
| // `identity` is now invalid and the authentication service might need to |
| // react to this loss of identity. |
| // Note that no reload of the credentials is necessary here, as `identity` |
| // might still be accessible in SSO, and `OnIdentityListChanged` will handle |
| // this when `identity` will actually disappear from SSO. |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&AuthenticationService::HandleForgottenIdentity, |
| GetWeakPtr(), identity, |
| /*device_restore=*/false)); |
| } |
| |
| void AuthenticationService::HandleForgottenIdentity( |
| id<SystemIdentity> invalid_identity, |
| bool device_restore) { |
| if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { |
| // User is not signed in. Nothing to do here. |
| return; |
| } |
| |
| // Tests if the primary identity still exists. |
| id<SystemIdentity> authenticated_identity = |
| GetPrimaryIdentity(signin::ConsentLevel::kSignin); |
| if (authenticated_identity && |
| ![authenticated_identity isEqual:invalid_identity]) { |
| // `authenticated_identity` exists and is a valid identity. Nothing to do |
| // here. |
| return; |
| } |
| |
| const CoreAccountInfo account_info = |
| identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin); |
| const bool account_filtered_out = |
| account_manager_service_->IsEmailRestricted(account_info.email); |
| |
| // Metrics. |
| signin_metrics::ProfileSignout signout_source; |
| if (account_filtered_out) { |
| // Account filtered out by enterprise policy. |
| signout_source = signin_metrics::ProfileSignout::kPrefChanged; |
| } else if (device_restore) { |
| // Account removed from the device after a device restore. |
| signout_source = signin_metrics::ProfileSignout:: |
| kIosAccountRemovedFromDeviceAfterRestore; |
| } else { |
| // Account removed from the device by another app or the token being |
| // invalid. |
| signout_source = signin_metrics::ProfileSignout::kAccountRemovedFromDevice; |
| } |
| |
| // Store the pre-device-restore identity in-memory in order to prompt user |
| // to sign-in again later. |
| if (device_restore) { |
| AccountInfo extended_account_info = |
| identity_manager_->FindExtendedAccountInfoByAccountId( |
| account_info.account_id); |
| syncer::SyncUserSettings* user_settings = sync_service_->GetUserSettings(); |
| bool history_sync_enabled = user_settings->GetSelectedTypes().HasAll( |
| {syncer::UserSelectableType::kHistory, |
| syncer::UserSelectableType::kTabs}); |
| StorePreRestoreIdentity(pref_service_, extended_account_info, |
| history_sync_enabled); |
| } |
| |
| // Sign the user out. |
| SignOut(signout_source, nil); |
| |
| NSString* gaia_id = account_info.gaia.ToNSString(); |
| // Should prompt the user if the identity was not removed by the user. |
| bool should_prompt = !GetApplicationContext() |
| ->GetSystemIdentityManager() |
| ->IdentityRemovedByUser(gaia_id); |
| if (should_prompt && account_filtered_out) { |
| FirePrimaryAccountRestricted(); |
| } else if (should_prompt && |
| IsFirstSessionAfterDeviceRestore() != signin::Tribool::kTrue) { |
| // If the device is restored, the restore shorty UI will be shown. |
| // Therefore, the reauth UI should be skipped. |
| SetReauthPromptForSignInAndSync(); |
| } |
| } |
| |
| void AuthenticationService::ReloadCredentialsFromIdentities() { |
| if (is_reloading_credentials_) { |
| return; |
| } |
| |
| base::AutoReset<bool> auto_reset(&is_reloading_credentials_, true); |
| |
| HandleForgottenIdentity(nil, /*device_restore=*/false); |
| |
| identity_manager_->GetDeviceAccountsSynchronizer() |
| ->ReloadAllAccountsFromSystemWithPrimaryAccount( |
| identity_manager_->GetPrimaryAccountId( |
| signin::ConsentLevel::kSignin)); |
| } |
| |
| void AuthenticationService::FirePrimaryAccountRestricted() { |
| primary_account_was_restricted_ = true; |
| for (auto& observer : observer_list_) { |
| observer.OnPrimaryAccountRestricted(); |
| } |
| } |
| |
| void AuthenticationService::OnSigninAllowedChanged(const std::string& name) { |
| DCHECK_EQ(prefs::kSigninAllowed, name); |
| GetApplicationContext()->GetLocalState()->SetBoolean( |
| prefs::kSigninAllowedOnDevice, |
| pref_service_->GetBoolean(prefs::kSigninAllowed)); |
| } |
| |
| void AuthenticationService::OnSigninAllowedOnDeviceChanged( |
| const std::string& name) { |
| DCHECK_EQ(prefs::kSigninAllowedOnDevice, name); |
| pref_service_->SetBoolean( |
| prefs::kSigninAllowed, |
| GetApplicationContext()->GetLocalState()->GetBoolean( |
| prefs::kSigninAllowedOnDevice)); |
| FireServiceStatusNotification(); |
| } |
| |
| void AuthenticationService::OnBrowserSigninPolicyChanged( |
| const std::string& name) { |
| DCHECK_EQ(prefs::kBrowserSigninPolicy, name); |
| FireServiceStatusNotification(); |
| } |
| |
| void AuthenticationService::FireServiceStatusNotification() { |
| for (auto& observer : observer_list_) { |
| observer.OnServiceStatusChanged(); |
| } |
| } |
| |
| void AuthenticationService::ClearAccountSettingsPrefsOfRemovedAccounts() { |
| std::vector<GaiaId> available_gaia_ids; |
| for (id<SystemIdentity> identity in account_manager_service_ |
| ->GetAllIdentities()) { |
| available_gaia_ids.emplace_back(identity.gaiaID); |
| } |
| sync_service_->GetUserSettings()->KeepAccountSettingsPrefsOnlyForUsers( |
| available_gaia_ids); |
| syncer::KeepAccountKeyedPrefValuesOnlyForUsers( |
| pref_service_, prefs::kSigninHasAcceptedManagementDialog, |
| base::ToVector(available_gaia_ids, &signin::GaiaIdHash::FromGaiaId)); |
| } |
| |
| NSArray<id<SystemIdentity>>* AuthenticationService::ActiveIdentities() { |
| return GetPrimaryIdentity(signin::ConsentLevel::kSignin) |
| ? account_manager_service_->GetAllIdentities() |
| : @[]; |
| } |