| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/browser/ui/authentication/authentication_flow_performer.h" |
| |
| #include <memory> |
| |
| #include "base/ios/block_types.h" |
| #include "base/logging.h" |
| #include "base/mac/bind_objc_block.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/signin/core/browser/account_tracker_service.h" |
| #include "components/signin/core/browser/signin_manager.h" |
| #include "components/signin/core/common/signin_pref_names.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "google_apis/gaia/gaia_auth_util.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #include "ios/chrome/browser/experimental_flags.h" |
| #include "ios/chrome/browser/signin/account_tracker_service_factory.h" |
| #include "ios/chrome/browser/signin/authentication_service.h" |
| #include "ios/chrome/browser/signin/authentication_service_factory.h" |
| #import "ios/chrome/browser/signin/browser_state_data_remover.h" |
| #import "ios/chrome/browser/signin/constants.h" |
| #include "ios/chrome/browser/signin/signin_manager_factory.h" |
| #include "ios/chrome/browser/sync/sync_setup_service.h" |
| #include "ios/chrome/browser/sync/sync_setup_service_factory.h" |
| #include "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
| #import "ios/chrome/browser/ui/authentication/authentication_ui_util.h" |
| #import "ios/chrome/browser/ui/settings/import_data_collection_view_controller.h" |
| #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
| #include "ios/chrome/grit/ios_chromium_strings.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| #import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using signin_ui::CompletionCallback; |
| |
| namespace { |
| |
| const int64_t kAuthenticationFlowTimeoutSeconds = 10; |
| |
| } // namespace |
| |
| @interface AuthenticationFlowPerformer ()<ImportDataControllerDelegate, |
| SettingsNavigationControllerDelegate> |
| |
| // Starts the watchdog timer with a timeout of |
| // |kAuthenticationFlowTimeoutSeconds| for the fetching managed status |
| // operation. It will notify |_delegate| of the failure unless |
| // |stopWatchdogTimer| is called before it times out. |
| - (void)startWatchdogTimerForManagedStatus; |
| |
| // Stops the watchdog timer, and doesn't call the |timeoutDelegateSelector|. |
| // Returns whether the watchdog was actually running. |
| - (BOOL)stopWatchdogTimer; |
| |
| // Callback for when the alert is dismissed. |
| - (void)alertControllerDidDisappear:(AlertCoordinator*)alertCoordinator; |
| |
| @end |
| |
| @implementation AuthenticationFlowPerformer { |
| __weak id<AuthenticationFlowPerformerDelegate> _delegate; |
| AlertCoordinator* _alertCoordinator; |
| SettingsNavigationController* _navigationController; |
| std::unique_ptr<base::Timer> _watchdogTimer; |
| } |
| |
| - (id<AuthenticationFlowPerformerDelegate>)delegate { |
| return _delegate; |
| } |
| |
| - (instancetype)initWithDelegate: |
| (id<AuthenticationFlowPerformerDelegate>)delegate { |
| self = [super init]; |
| if (self) |
| _delegate = delegate; |
| return self; |
| } |
| |
| - (void)cancelAndDismiss { |
| [_alertCoordinator executeCancelHandler]; |
| [_alertCoordinator stop]; |
| if (_navigationController) { |
| [_navigationController settingsWillBeDismissed]; |
| _navigationController = nil; |
| [[_delegate presentingViewController] dismissViewControllerAnimated:NO |
| completion:nil]; |
| } |
| [self stopWatchdogTimer]; |
| } |
| |
| - (void)commitSyncForBrowserState:(ios::ChromeBrowserState*)browserState { |
| SyncSetupServiceFactory::GetForBrowserState(browserState)->CommitChanges(); |
| } |
| |
| - (void)startWatchdogTimerForManagedStatus { |
| __weak AuthenticationFlowPerformer* weakSelf = self; |
| ProceduralBlock onTimeout = ^{ |
| AuthenticationFlowPerformer* strongSelf = weakSelf; |
| if (!strongSelf) |
| return; |
| [strongSelf stopWatchdogTimer]; |
| NSError* error = [NSError errorWithDomain:kAuthenticationErrorDomain |
| code:TIMED_OUT_FETCH_POLICY |
| userInfo:nil]; |
| [strongSelf->_delegate didFailFetchManagedStatus:error]; |
| }; |
| _watchdogTimer.reset(new base::Timer(false, false)); |
| _watchdogTimer->Start( |
| FROM_HERE, |
| base::TimeDelta::FromSeconds(kAuthenticationFlowTimeoutSeconds), |
| base::BindBlockArc(onTimeout)); |
| } |
| |
| - (BOOL)stopWatchdogTimer { |
| if (_watchdogTimer) { |
| _watchdogTimer->Stop(); |
| _watchdogTimer.reset(); |
| return YES; |
| } |
| return NO; |
| } |
| |
| - (void)fetchManagedStatus:(ios::ChromeBrowserState*)browserState |
| forIdentity:(ChromeIdentity*)identity { |
| if (!experimental_flags::IsMDMIntegrationEnabled()) { |
| [_delegate didFetchManagedStatus:nil]; |
| return; |
| } |
| if (gaia::ExtractDomainName(gaia::CanonicalizeEmail( |
| base::SysNSStringToUTF8(identity.userEmail))) == "gmail.com") { |
| // Do nothing for @gmail.com addresses as they can't have a hosted domain. |
| // This avoids waiting for this step to complete (and a network call). |
| [_delegate didFetchManagedStatus:nil]; |
| return; |
| } |
| |
| [self startWatchdogTimerForManagedStatus]; |
| __weak AuthenticationFlowPerformer* weakSelf = self; |
| ios::GetChromeBrowserProvider() |
| ->GetChromeIdentityService() |
| ->GetHostedDomainForIdentity( |
| identity, ^(NSString* hosted_domain, NSError* error) { |
| [weakSelf handleGetHostedDomain:hosted_domain |
| error:error |
| browserState:browserState]; |
| }); |
| } |
| |
| - (void)handleGetHostedDomain:(NSString*)hostedDomain |
| error:(NSError*)error |
| browserState:(ios::ChromeBrowserState*)browserState { |
| if (![self stopWatchdogTimer]) { |
| // Watchdog timer has already fired, don't notify the delegate. |
| return; |
| } |
| if (error) { |
| [_delegate didFailFetchManagedStatus:error]; |
| return; |
| } |
| [_delegate didFetchManagedStatus:hostedDomain]; |
| } |
| |
| - (void)signInIdentity:(ChromeIdentity*)identity |
| withHostedDomain:(NSString*)hostedDomain |
| toBrowserState:(ios::ChromeBrowserState*)browserState { |
| AuthenticationServiceFactory::GetForBrowserState(browserState) |
| ->SignIn(identity, base::SysNSStringToUTF8(hostedDomain)); |
| } |
| |
| - (void)signOutBrowserState:(ios::ChromeBrowserState*)browserState { |
| AuthenticationServiceFactory::GetForBrowserState(browserState) |
| ->SignOut(signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, ^{ |
| [_delegate didSignOut]; |
| }); |
| } |
| |
| - (void)signOutImmediatelyFromBrowserState: |
| (ios::ChromeBrowserState*)browserState { |
| AuthenticationServiceFactory::GetForBrowserState(browserState) |
| ->SignOut(signin_metrics::ABORT_SIGNIN, nil); |
| } |
| |
| - (void)promptSwitchFromManagedEmail:(NSString*)managedEmail |
| withHostedDomain:(NSString*)hostedDomain |
| toEmail:(NSString*)toEmail |
| viewController:(UIViewController*)viewController { |
| DCHECK(!_alertCoordinator); |
| NSString* title = l10n_util::GetNSString(IDS_IOS_MANAGED_SWITCH_TITLE); |
| NSString* subtitle = l10n_util::GetNSStringF( |
| IDS_IOS_MANAGED_SWITCH_SUBTITLE, base::SysNSStringToUTF16(managedEmail), |
| base::SysNSStringToUTF16(toEmail), |
| base::SysNSStringToUTF16(hostedDomain)); |
| NSString* acceptLabel = |
| l10n_util::GetNSString(IDS_IOS_MANAGED_SWITCH_ACCEPT_BUTTON); |
| NSString* cancelLabel = l10n_util::GetNSString(IDS_CANCEL); |
| |
| _alertCoordinator = |
| [[AlertCoordinator alloc] initWithBaseViewController:viewController |
| title:title |
| message:subtitle]; |
| |
| __weak AuthenticationFlowPerformer* weakSelf = self; |
| __weak AlertCoordinator* weakAlert = _alertCoordinator; |
| ProceduralBlock acceptBlock = ^{ |
| AuthenticationFlowPerformer* strongSelf = weakSelf; |
| if (!strongSelf) |
| return; |
| [[strongSelf delegate] |
| didChooseClearDataPolicy:SHOULD_CLEAR_DATA_CLEAR_DATA]; |
| [strongSelf alertControllerDidDisappear:weakAlert]; |
| }; |
| ProceduralBlock cancelBlock = ^{ |
| AuthenticationFlowPerformer* strongSelf = weakSelf; |
| if (!strongSelf) |
| return; |
| [[strongSelf delegate] didChooseCancel]; |
| [strongSelf alertControllerDidDisappear:weakAlert]; |
| }; |
| |
| [_alertCoordinator addItemWithTitle:cancelLabel |
| action:cancelBlock |
| style:UIAlertActionStyleCancel]; |
| [_alertCoordinator addItemWithTitle:acceptLabel |
| action:acceptBlock |
| style:UIAlertActionStyleDefault]; |
| [_alertCoordinator setCancelAction:cancelBlock]; |
| [_alertCoordinator start]; |
| } |
| |
| - (void)promptMergeCaseForIdentity:(ChromeIdentity*)identity |
| browserState:(ios::ChromeBrowserState*)browserState |
| viewController:(UIViewController*)viewController { |
| BOOL isSignedIn = YES; |
| NSString* lastSignedInEmail = |
| [AuthenticationServiceFactory::GetForBrowserState(browserState) |
| ->GetAuthenticatedIdentity() userEmail]; |
| if (!lastSignedInEmail) { |
| lastSignedInEmail = |
| base::SysUTF8ToNSString(browserState->GetPrefs()->GetString( |
| prefs::kGoogleServicesLastUsername)); |
| isSignedIn = NO; |
| } |
| |
| if (AuthenticationServiceFactory::GetForBrowserState(browserState) |
| ->IsAuthenticatedIdentityManaged()) { |
| NSString* hostedDomain = base::SysUTF8ToNSString( |
| ios::SigninManagerFactory::GetForBrowserState(browserState) |
| ->GetAuthenticatedAccountInfo() |
| .hosted_domain); |
| [self promptSwitchFromManagedEmail:lastSignedInEmail |
| withHostedDomain:hostedDomain |
| toEmail:[identity userEmail] |
| viewController:viewController]; |
| return; |
| } |
| _navigationController = |
| [SettingsNavigationController newImportDataController:browserState |
| delegate:self |
| importDataDelegate:self |
| fromEmail:lastSignedInEmail |
| toEmail:[identity userEmail] |
| isSignedIn:isSignedIn]; |
| [_navigationController setShouldCommitSyncChangesOnDismissal:NO]; |
| [[_delegate presentingViewController] |
| presentViewController:_navigationController |
| animated:YES |
| completion:nil]; |
| } |
| |
| - (void)clearData:(ios::ChromeBrowserState*)browserState { |
| DCHECK(!AuthenticationServiceFactory::GetForBrowserState(browserState) |
| ->GetAuthenticatedUserEmail()); |
| BrowserStateDataRemover::ClearData(browserState, ^{ |
| [_delegate didClearData]; |
| }); |
| } |
| |
| - (BOOL)shouldHandleMergeCaseForIdentity:(ChromeIdentity*)identity |
| browserState: |
| (ios::ChromeBrowserState*)browserState { |
| std::string lastSignedInAccountId = |
| browserState->GetPrefs()->GetString(prefs::kGoogleServicesLastAccountId); |
| std::string currentSignedInAccountId = |
| ios::AccountTrackerServiceFactory::GetForBrowserState(browserState) |
| ->PickAccountIdForAccount( |
| base::SysNSStringToUTF8([identity gaiaID]), |
| base::SysNSStringToUTF8([identity userEmail])); |
| if (!lastSignedInAccountId.empty()) { |
| // Merge case exists if the id of the previously signed in account is |
| // different from the one of the account being signed in. |
| return lastSignedInAccountId != currentSignedInAccountId; |
| } |
| |
| // kGoogleServicesLastAccountId pref might not have been populated yet, |
| // check the old kGoogleServicesLastUsername pref. |
| std::string lastSignedInEmail = |
| browserState->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername); |
| std::string currentSignedInEmail = |
| base::SysNSStringToUTF8([identity userEmail]); |
| return !lastSignedInEmail.empty() && |
| !gaia::AreEmailsSame(currentSignedInEmail, lastSignedInEmail); |
| } |
| |
| - (void)showManagedConfirmationForHostedDomain:(NSString*)hostedDomain |
| viewController: |
| (UIViewController*)viewController { |
| DCHECK(!_alertCoordinator); |
| NSString* title = l10n_util::GetNSString(IDS_IOS_MANAGED_SIGNIN_TITLE); |
| NSString* subtitle = l10n_util::GetNSStringF( |
| IDS_IOS_MANAGED_SIGNIN_SUBTITLE, base::SysNSStringToUTF16(hostedDomain)); |
| NSString* acceptLabel = |
| l10n_util::GetNSString(IDS_IOS_MANAGED_SIGNIN_ACCEPT_BUTTON); |
| NSString* cancelLabel = l10n_util::GetNSString(IDS_CANCEL); |
| |
| _alertCoordinator = |
| [[AlertCoordinator alloc] initWithBaseViewController:viewController |
| title:title |
| message:subtitle]; |
| |
| __weak AuthenticationFlowPerformer* weakSelf = self; |
| __weak AlertCoordinator* weakAlert = _alertCoordinator; |
| ProceduralBlock acceptBlock = ^{ |
| AuthenticationFlowPerformer* strongSelf = weakSelf; |
| if (!strongSelf) |
| return; |
| [[strongSelf delegate] didAcceptManagedConfirmation]; |
| [strongSelf alertControllerDidDisappear:weakAlert]; |
| }; |
| ProceduralBlock cancelBlock = ^{ |
| AuthenticationFlowPerformer* strongSelf = weakSelf; |
| if (!strongSelf) |
| return; |
| [[strongSelf delegate] didCancelManagedConfirmation]; |
| [strongSelf alertControllerDidDisappear:weakAlert]; |
| }; |
| |
| [_alertCoordinator addItemWithTitle:cancelLabel |
| action:cancelBlock |
| style:UIAlertActionStyleCancel]; |
| [_alertCoordinator addItemWithTitle:acceptLabel |
| action:acceptBlock |
| style:UIAlertActionStyleDefault]; |
| [_alertCoordinator setCancelAction:cancelBlock]; |
| [_alertCoordinator start]; |
| } |
| |
| - (void)showAuthenticationError:(NSError*)error |
| withCompletion:(ProceduralBlock)callback |
| viewController:(UIViewController*)viewController { |
| DCHECK(!_alertCoordinator); |
| |
| _alertCoordinator = |
| ios_internal::ErrorCoordinatorNoItem(error, viewController); |
| |
| __weak AuthenticationFlowPerformer* weakSelf = self; |
| __weak AlertCoordinator* weakAlert = _alertCoordinator; |
| ProceduralBlock dismissAction = ^{ |
| if (callback) |
| callback(); |
| [weakSelf alertControllerDidDisappear:weakAlert]; |
| }; |
| |
| NSString* okButtonLabel = l10n_util::GetNSString(IDS_OK); |
| [_alertCoordinator addItemWithTitle:okButtonLabel |
| action:dismissAction |
| style:UIAlertActionStyleDefault]; |
| |
| [_alertCoordinator setCancelAction:dismissAction]; |
| |
| [_alertCoordinator start]; |
| } |
| |
| - (void)alertControllerDidDisappear:(AlertCoordinator*)alertCoordinator { |
| if (_alertCoordinator != alertCoordinator) { |
| // Do not reset the |_alertCoordinator| if it has changed. This typically |
| // happens when the user taps on any of the actions on "Clear Data Before |
| // Syncing?" dialog, as the sign-in confirmation dialog is created before |
| // the "Clear Data Before Syncing?" dialog is dismissed. |
| return; |
| } |
| _alertCoordinator = nil; |
| } |
| |
| #pragma mark - ImportDataControllerDelegate |
| |
| - (void)didChooseClearDataPolicy:(ImportDataCollectionViewController*)controller |
| shouldClearData:(ShouldClearData)shouldClearData { |
| DCHECK_NE(SHOULD_CLEAR_DATA_USER_CHOICE, shouldClearData); |
| if (shouldClearData == SHOULD_CLEAR_DATA_CLEAR_DATA) { |
| base::RecordAction( |
| base::UserMetricsAction("Signin_ImportDataPrompt_DontImport")); |
| } else { |
| base::RecordAction( |
| base::UserMetricsAction("Signin_ImportDataPrompt_ImportData")); |
| } |
| |
| __weak AuthenticationFlowPerformer* weakSelf = self; |
| ProceduralBlock block = ^{ |
| AuthenticationFlowPerformer* strongSelf = weakSelf; |
| if (!strongSelf) |
| return; |
| strongSelf->_navigationController = nil; |
| [[strongSelf delegate] didChooseClearDataPolicy:shouldClearData]; |
| }; |
| [_navigationController settingsWillBeDismissed]; |
| [[_delegate presentingViewController] dismissViewControllerAnimated:YES |
| completion:block]; |
| } |
| |
| #pragma mark - SettingsNavigationControllerDelegate |
| |
| - (void)closeSettings { |
| base::RecordAction(base::UserMetricsAction("Signin_ImportDataPrompt_Cancel")); |
| |
| __weak AuthenticationFlowPerformer* weakSelf = self; |
| ProceduralBlock block = ^{ |
| AuthenticationFlowPerformer* strongSelf = weakSelf; |
| if (!strongSelf) |
| return; |
| strongSelf->_navigationController = nil; |
| [[strongSelf delegate] didChooseCancel]; |
| }; |
| [_navigationController settingsWillBeDismissed]; |
| [[_delegate presentingViewController] dismissViewControllerAnimated:YES |
| completion:block]; |
| } |
| |
| - (void)closeSettingsAndOpenNewIncognitoTab { |
| NOTREACHED(); |
| } |
| |
| - (void)closeSettingsAndOpenUrl:(OpenUrlCommand*)command { |
| NOTREACHED(); |
| } |
| |
| @end |