| // Copyright 2018 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/settings/google_services/google_services_settings_coordinator.h" |
| |
| #include "base/mac/foundation_util.h" |
| #include "components/google/core/common/google_util.h" |
| #include "components/sync/driver/sync_service_utils.h" |
| #include "ios/chrome/browser/application_context.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #include "ios/chrome/browser/chrome_url_constants.h" |
| #import "ios/chrome/browser/main/browser.h" |
| #include "ios/chrome/browser/signin/authentication_service.h" |
| #import "ios/chrome/browser/signin/authentication_service_factory.h" |
| #include "ios/chrome/browser/signin/identity_manager_factory.h" |
| #include "ios/chrome/browser/sync/profile_sync_service_factory.h" |
| #include "ios/chrome/browser/sync/sync_setup_service.h" |
| #include "ios/chrome/browser/sync/sync_setup_service_factory.h" |
| #import "ios/chrome/browser/ui/authentication/authentication_flow.h" |
| #import "ios/chrome/browser/ui/commands/application_commands.h" |
| #import "ios/chrome/browser/ui/commands/browsing_data_commands.h" |
| #import "ios/chrome/browser/ui/commands/command_dispatcher.h" |
| #import "ios/chrome/browser/ui/commands/open_new_tab_command.h" |
| #import "ios/chrome/browser/ui/commands/show_signin_command.h" |
| #import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h" |
| #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_command_handler.h" |
| #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.h" |
| #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_view_controller.h" |
| #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.h" |
| #import "ios/chrome/browser/ui/settings/google_services/sync_error_settings_command_handler.h" |
| #import "ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.h" |
| #include "ios/chrome/browser/ui/ui_feature_flags.h" |
| #import "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using signin_metrics::AccessPoint; |
| using signin_metrics::PromoAction; |
| |
| @interface GoogleServicesSettingsCoordinator () < |
| GoogleServicesSettingsCommandHandler, |
| GoogleServicesSettingsViewControllerPresentationDelegate, |
| ManageSyncSettingsCoordinatorDelegate, |
| SyncErrorSettingsCommandHandler> |
| |
| // Google services settings mode. |
| @property(nonatomic, assign, readonly) GoogleServicesSettingsMode mode; |
| // Google services settings mediator. |
| @property(nonatomic, strong) GoogleServicesSettingsMediator* mediator; |
| // Returns the authentication service. |
| @property(nonatomic, assign, readonly) AuthenticationService* authService; |
| // Manages the authentication flow for a given identity. |
| @property(nonatomic, strong) AuthenticationFlow* authenticationFlow; |
| // View controller presented by this coordinator. |
| @property(nonatomic, strong, readonly) |
| GoogleServicesSettingsViewController* googleServicesSettingsViewController; |
| // Coordinator to present the manage sync settings. |
| @property(nonatomic, strong) |
| ManageSyncSettingsCoordinator* manageSyncSettingsCoordinator; |
| // YES if stop has been called. |
| @property(nonatomic, assign) BOOL stopDone; |
| // YES if the last sign-in has been interrupted. In that case, the coordinator |
| // is going to be stopped, and the sync setup flag should not be marked as done. |
| // And the sync should not be marked as disabled. The sync should be kept |
| // undecided. |
| @property(nonatomic, assign) BOOL signinInterrupted; |
| |
| @end |
| |
| @implementation GoogleServicesSettingsCoordinator |
| |
| @synthesize baseNavigationController = _baseNavigationController; |
| |
| - (instancetype)initWithBaseNavigationController: |
| (UINavigationController*)navigationController |
| browser:(Browser*)browser |
| mode:(GoogleServicesSettingsMode) |
| mode { |
| if ([super initWithBaseViewController:navigationController browser:browser]) { |
| _baseNavigationController = navigationController; |
| _mode = mode; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| // -[GoogleServicesSettingsCoordinator stop] needs to be called explicitly. |
| DCHECK(self.stopDone); |
| } |
| |
| - (void)start { |
| UITableViewStyle style = base::FeatureList::IsEnabled(kSettingsRefresh) |
| ? UITableViewStylePlain |
| : UITableViewStyleGrouped; |
| |
| GoogleServicesSettingsViewController* viewController = |
| [[GoogleServicesSettingsViewController alloc] initWithStyle:style]; |
| viewController.presentationDelegate = self; |
| self.viewController = viewController; |
| SyncSetupService* syncSetupService = |
| SyncSetupServiceFactory::GetForBrowserState( |
| self.browser->GetBrowserState()); |
| self.mediator = [[GoogleServicesSettingsMediator alloc] |
| initWithUserPrefService:self.browser->GetBrowserState()->GetPrefs() |
| localPrefService:GetApplicationContext()->GetLocalState() |
| syncSetupService:syncSetupService |
| mode:self.mode]; |
| self.mediator.consumer = viewController; |
| self.mediator.authService = self.authService; |
| self.mediator.identityManager = IdentityManagerFactory::GetForBrowserState( |
| self.browser->GetBrowserState()); |
| self.mediator.commandHandler = self; |
| self.mediator.syncErrorHandler = self; |
| self.mediator.syncService = ProfileSyncServiceFactory::GetForBrowserState( |
| self.browser->GetBrowserState()); |
| viewController.modelDelegate = self.mediator; |
| viewController.serviceDelegate = self.mediator; |
| viewController.dispatcher = static_cast< |
| id<ApplicationCommands, BrowserCommands, BrowsingDataCommands>>( |
| self.browser->GetCommandDispatcher()); |
| DCHECK(self.baseNavigationController); |
| [self.baseNavigationController pushViewController:self.viewController |
| animated:YES]; |
| } |
| |
| - (void)stop { |
| if (self.stopDone) { |
| return; |
| } |
| // Sync changes should only be commited if the user is authenticated and |
| // the sign-in has not been interrupted. |
| if (self.authService->IsAuthenticated() && !self.signinInterrupted) { |
| SyncSetupService* syncSetupService = |
| SyncSetupServiceFactory::GetForBrowserState( |
| self.browser->GetBrowserState()); |
| if (self.mode == GoogleServicesSettingsModeSettings && |
| syncSetupService->GetSyncServiceState() == |
| SyncSetupService::kSyncSettingsNotConfirmed) { |
| // If Sync is still in aborted state, this means the user didn't turn on |
| // sync, and wants Sync off. To acknowledge, Sync has to be turned off. |
| syncSetupService->SetSyncEnabled(false); |
| } |
| syncSetupService->CommitSyncChanges(); |
| } |
| self.stopDone = YES; |
| } |
| |
| #pragma mark - Private |
| |
| - (void)authenticationFlowDidComplete { |
| DCHECK(self.authenticationFlow); |
| self.authenticationFlow = nil; |
| [self.googleServicesSettingsViewController allowUserInteraction]; |
| } |
| |
| #pragma mark - Properties |
| |
| - (AuthenticationService*)authService { |
| return AuthenticationServiceFactory::GetForBrowserState( |
| self.browser->GetBrowserState()); |
| } |
| |
| - (GoogleServicesSettingsViewController*)googleServicesSettingsViewController { |
| return base::mac::ObjCCast<GoogleServicesSettingsViewController>( |
| self.viewController); |
| } |
| |
| - (BOOL)googleServicesSettingsViewIsShown { |
| return [self.viewController |
| isEqual:self.baseNavigationController.topViewController]; |
| } |
| |
| #pragma mark - SyncErrorSettingsCommandHandler |
| |
| - (void)restartAuthenticationFlow { |
| ChromeIdentity* authenticatedIdentity = |
| AuthenticationServiceFactory::GetForBrowserState( |
| self.browser->GetBrowserState()) |
| ->GetAuthenticatedIdentity(); |
| [self.googleServicesSettingsViewController preventUserInteraction]; |
| DCHECK(!self.authenticationFlow); |
| self.authenticationFlow = |
| [[AuthenticationFlow alloc] initWithBrowser:self.browser |
| identity:authenticatedIdentity |
| shouldClearData:SHOULD_CLEAR_DATA_USER_CHOICE |
| postSignInAction:POST_SIGNIN_ACTION_START_SYNC |
| presentingViewController:self.viewController]; |
| self.authenticationFlow.dispatcher = HandlerForProtocol( |
| self.browser->GetCommandDispatcher(), BrowsingDataCommands); |
| __weak GoogleServicesSettingsCoordinator* weakSelf = self; |
| [self.authenticationFlow startSignInWithCompletion:^(BOOL success) { |
| // TODO(crbug.com/889919): Needs to add histogram for |success|. |
| [weakSelf authenticationFlowDidComplete]; |
| }]; |
| } |
| |
| - (void)openReauthDialogAsSyncIsInAuthError { |
| ChromeIdentity* identity = self.authService->GetAuthenticatedIdentity(); |
| if (self.authService->HasCachedMDMErrorForIdentity(identity)) { |
| self.authService->ShowMDMErrorDialogForIdentity(identity); |
| return; |
| } |
| // Sync enters in a permanent auth error state when fetching an access token |
| // fails with invalid credentials. This corresponds to Gaia responding with an |
| // "invalid grant" error. The current implementation of the iOS SSOAuth |
| // library user by Chrome removes the identity from the device when receiving |
| // an "invalid grant" response, which leads to the account being also signed |
| // out of Chrome. So the sync permanent auth error is a transient state on |
| // iOS. The decision was to avoid handling this error in the UI, which means |
| // that the reauth dialog is not actually presented on iOS. |
| } |
| |
| - (void)openPassphraseDialog { |
| SyncEncryptionPassphraseTableViewController* controller = |
| [[SyncEncryptionPassphraseTableViewController alloc] |
| initWithBrowser:self.browser]; |
| // TODO(crbug.com/1045047): Use HandlerForProtocol after commands protocol |
| // clean up. |
| controller.dispatcher = static_cast< |
| id<ApplicationCommands, BrowserCommands, BrowsingDataCommands>>( |
| self.browser->GetCommandDispatcher()); |
| [self.baseNavigationController pushViewController:controller animated:YES]; |
| } |
| |
| - (void)openTrustedVaultReauth { |
| id<ApplicationCommands> applicationCommands = |
| static_cast<id<ApplicationCommands>>( |
| self.browser->GetCommandDispatcher()); |
| [applicationCommands |
| showTrustedVaultReauthenticationFromViewController: |
| self.googleServicesSettingsViewController |
| retrievalTrigger: |
| syncer::KeyRetrievalTriggerForUMA:: |
| kSettings]; |
| } |
| |
| #pragma mark - GoogleServicesSettingsCommandHandler |
| |
| - (void)showSignIn { |
| __weak __typeof(self) weakSelf = self; |
| DCHECK(self.handler); |
| signin_metrics::RecordSigninUserActionForAccessPoint( |
| AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS, |
| PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO); |
| ShowSigninCommand* command = [[ShowSigninCommand alloc] |
| initWithOperation:AUTHENTICATION_OPERATION_SIGNIN |
| identity:nil |
| accessPoint:AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS |
| promoAction:PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO |
| callback:^(BOOL success) { |
| [weakSelf signinFinishedWithSuccess:success]; |
| }]; |
| [self.handler showSignin:command |
| baseViewController:self.googleServicesSettingsViewController]; |
| } |
| |
| - (void)signinFinishedWithSuccess:(BOOL)success { |
| // TODO(crbug.com/1101346): SigninCoordinatorResult should be received instead |
| // of guessing if the sign-in has been interrupted. |
| ChromeIdentity* primaryAccount = |
| AuthenticationServiceFactory::GetForBrowserState( |
| self.browser->GetBrowserState()) |
| ->GetAuthenticatedIdentity(); |
| self.signinInterrupted = !success && primaryAccount; |
| } |
| |
| - (void)openAccountSettings { |
| AccountsTableViewController* controller = |
| [[AccountsTableViewController alloc] initWithBrowser:self.browser |
| closeSettingsOnAddAccount:NO]; |
| controller.dispatcher = static_cast< |
| id<ApplicationCommands, BrowserCommands, BrowsingDataCommands>>( |
| self.browser->GetCommandDispatcher()); |
| [self.baseNavigationController pushViewController:controller animated:YES]; |
| } |
| |
| - (void)openManageSyncSettings { |
| DCHECK(!self.manageSyncSettingsCoordinator); |
| self.manageSyncSettingsCoordinator = [[ManageSyncSettingsCoordinator alloc] |
| initWithBaseNavigationController:self.baseNavigationController |
| browser:self.browser]; |
| self.manageSyncSettingsCoordinator.delegate = self; |
| [self.manageSyncSettingsCoordinator start]; |
| } |
| |
| - (void)openManageGoogleAccount { |
| ios::GetChromeBrowserProvider() |
| ->GetChromeIdentityService() |
| ->PresentAccountDetailsController( |
| self.authService->GetAuthenticatedIdentity(), |
| self.googleServicesSettingsViewController, /*animated=*/YES); |
| } |
| |
| - (void)openManageGoogleAccountWebPage { |
| GURL url = google_util::AppendGoogleLocaleParam( |
| GURL(kManageYourGoogleAccountURL), |
| GetApplicationContext()->GetApplicationLocale()); |
| OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:url]; |
| id<ApplicationCommands> handler = HandlerForProtocol( |
| self.browser->GetCommandDispatcher(), ApplicationCommands); |
| [handler closeSettingsUIAndOpenURL:command]; |
| } |
| |
| #pragma mark - GoogleServicesSettingsViewControllerPresentationDelegate |
| |
| - (void)googleServicesSettingsViewControllerDidRemove: |
| (GoogleServicesSettingsViewController*)controller { |
| DCHECK_EQ(self.viewController, controller); |
| [self.delegate googleServicesSettingsCoordinatorDidRemove:self]; |
| } |
| |
| #pragma mark - ManageSyncSettingsCoordinatorDelegate |
| |
| - (void)manageSyncSettingsCoordinatorWasRemoved: |
| (ManageSyncSettingsCoordinator*)coordinator { |
| DCHECK_EQ(self.manageSyncSettingsCoordinator, coordinator); |
| [self.manageSyncSettingsCoordinator stop]; |
| self.manageSyncSettingsCoordinator = nil; |
| } |
| |
| @end |