Index: ios/chrome/browser/ui/authentication/authentication_flow_performer.mm |
diff --git a/ios/chrome/browser/ui/authentication/authentication_flow_performer.mm b/ios/chrome/browser/ui/authentication/authentication_flow_performer.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6600833e43476a5922757b831fea273eb706f2dc |
--- /dev/null |
+++ b/ios/chrome/browser/ui/authentication/authentication_flow_performer.mm |
@@ -0,0 +1,452 @@ |
+// 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/ios/weak_nsobject.h" |
+#include "base/logging.h" |
+#include "base/mac/bind_objc_block.h" |
+#include "base/mac/scoped_nsobject.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" |
+ |
+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 { |
+ base::WeakNSProtocol<id<AuthenticationFlowPerformerDelegate>> _delegate; |
+ base::scoped_nsobject<AlertCoordinator> _alertCoordinator; |
+ base::scoped_nsobject<SettingsNavigationController> _navigationController; |
+ std::unique_ptr<base::Timer> _watchdogTimer; |
+} |
+ |
+- (id<AuthenticationFlowPerformerDelegate>)delegate { |
+ return _delegate.get(); |
+} |
+ |
+- (instancetype)initWithDelegate: |
+ (id<AuthenticationFlowPerformerDelegate>)delegate { |
+ self = [super init]; |
+ if (self) |
+ _delegate.reset(delegate); |
+ return self; |
+} |
+ |
+- (void)cancelAndDismiss { |
+ [_alertCoordinator executeCancelHandler]; |
+ [_alertCoordinator stop]; |
+ if (_navigationController) { |
+ [_navigationController settingsWillBeDismissed]; |
+ _navigationController.reset(); |
+ [[_delegate presentingViewController] dismissViewControllerAnimated:NO |
+ completion:nil]; |
+ } |
+ [self stopWatchdogTimer]; |
+} |
+ |
+- (void)commitSyncForBrowserState:(ios::ChromeBrowserState*)browserState { |
+ SyncSetupServiceFactory::GetForBrowserState(browserState)->CommitChanges(); |
+} |
+ |
+- (void)startWatchdogTimerForManagedStatus { |
+ base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
+ ProceduralBlock onTimeout = ^{ |
+ base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ [strongSelf stopWatchdogTimer]; |
+ NSError* error = [NSError errorWithDomain:kAuthenticationErrorDomain |
+ code:TIMED_OUT_FETCH_POLICY |
+ userInfo:nil]; |
+ [strongSelf.get()->_delegate didFailFetchManagedStatus:error]; |
+ }; |
+ _watchdogTimer.reset(new base::Timer(false, false)); |
+ _watchdogTimer->Start(FROM_HERE, base::TimeDelta::FromSeconds( |
+ kAuthenticationFlowTimeoutSeconds), |
+ base::BindBlock(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]; |
+ base::WeakNSObject<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.reset([[AlertCoordinator alloc] |
+ initWithBaseViewController:viewController |
+ title:title |
+ message:subtitle]); |
+ |
+ base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
+ base::WeakNSObject<AlertCoordinator> weakAlert(_alertCoordinator); |
+ ProceduralBlock acceptBlock = ^{ |
+ base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ [[strongSelf delegate] |
+ didChooseClearDataPolicy:SHOULD_CLEAR_DATA_CLEAR_DATA]; |
+ [strongSelf alertControllerDidDisappear:weakAlert]; |
+ }; |
+ ProceduralBlock cancelBlock = ^{ |
+ base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
+ [weakSelf retain]); |
+ 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.reset([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.reset([[AlertCoordinator alloc] |
+ initWithBaseViewController:viewController |
+ title:title |
+ message:subtitle]); |
+ |
+ base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
+ base::WeakNSObject<AlertCoordinator> weakAlert(_alertCoordinator); |
+ ProceduralBlock acceptBlock = ^{ |
+ base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ [[strongSelf delegate] didAcceptManagedConfirmation]; |
+ [strongSelf alertControllerDidDisappear:weakAlert]; |
+ }; |
+ ProceduralBlock cancelBlock = ^{ |
+ base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
+ [weakSelf retain]); |
+ 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.reset( |
+ [ios_internal::ErrorCoordinatorNoItem(error, viewController) retain]); |
+ |
+ base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
+ base::WeakNSObject<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.get() != 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.reset(); |
+} |
+ |
+#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")); |
+ } |
+ |
+ base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
+ ProceduralBlock block = ^{ |
+ base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ strongSelf.get()->_navigationController.reset(); |
+ [[strongSelf delegate] didChooseClearDataPolicy:shouldClearData]; |
+ }; |
+ [_navigationController settingsWillBeDismissed]; |
+ [[_delegate presentingViewController] dismissViewControllerAnimated:YES |
+ completion:block]; |
+} |
+ |
+#pragma mark - SettingsNavigationControllerDelegate |
+ |
+- (void)closeSettings { |
+ base::RecordAction(base::UserMetricsAction("Signin_ImportDataPrompt_Cancel")); |
+ |
+ base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
+ ProceduralBlock block = ^{ |
+ base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ strongSelf.get()->_navigationController.reset(); |
+ [[strongSelf delegate] didChooseCancel]; |
+ }; |
+ [_navigationController settingsWillBeDismissed]; |
+ [[_delegate presentingViewController] dismissViewControllerAnimated:YES |
+ completion:block]; |
+} |
+ |
+- (void)closeSettingsAndOpenNewIncognitoTab { |
+ NOTREACHED(); |
+} |
+ |
+- (void)closeSettingsAndOpenUrl:(OpenUrlCommand*)command { |
+ NOTREACHED(); |
+} |
+ |
+@end |