OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/authentication/authentication_flow_performer.h" |
| 6 |
| 7 #include <memory> |
| 8 |
| 9 #include "base/ios/block_types.h" |
| 10 #include "base/ios/weak_nsobject.h" |
| 11 #include "base/logging.h" |
| 12 #include "base/mac/bind_objc_block.h" |
| 13 #include "base/mac/scoped_nsobject.h" |
| 14 #include "base/metrics/user_metrics.h" |
| 15 #include "base/strings/sys_string_conversions.h" |
| 16 #include "base/time/time.h" |
| 17 #include "base/timer/timer.h" |
| 18 #include "components/prefs/pref_service.h" |
| 19 #include "components/signin/core/browser/account_tracker_service.h" |
| 20 #include "components/signin/core/browser/signin_manager.h" |
| 21 #include "components/signin/core/common/signin_pref_names.h" |
| 22 #include "components/strings/grit/components_strings.h" |
| 23 #include "google_apis/gaia/gaia_auth_util.h" |
| 24 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 25 #include "ios/chrome/browser/experimental_flags.h" |
| 26 #include "ios/chrome/browser/signin/account_tracker_service_factory.h" |
| 27 #include "ios/chrome/browser/signin/authentication_service.h" |
| 28 #include "ios/chrome/browser/signin/authentication_service_factory.h" |
| 29 #import "ios/chrome/browser/signin/browser_state_data_remover.h" |
| 30 #import "ios/chrome/browser/signin/constants.h" |
| 31 #include "ios/chrome/browser/signin/signin_manager_factory.h" |
| 32 #include "ios/chrome/browser/sync/sync_setup_service.h" |
| 33 #include "ios/chrome/browser/sync/sync_setup_service_factory.h" |
| 34 #include "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
| 35 #import "ios/chrome/browser/ui/authentication/authentication_ui_util.h" |
| 36 #import "ios/chrome/browser/ui/settings/import_data_collection_view_controller.h
" |
| 37 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
| 38 #include "ios/chrome/grit/ios_chromium_strings.h" |
| 39 #include "ios/chrome/grit/ios_strings.h" |
| 40 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 41 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
| 42 #include "ui/base/l10n/l10n_util.h" |
| 43 |
| 44 using signin_ui::CompletionCallback; |
| 45 |
| 46 namespace { |
| 47 |
| 48 const int64_t kAuthenticationFlowTimeoutSeconds = 10; |
| 49 |
| 50 } // namespace |
| 51 |
| 52 @interface AuthenticationFlowPerformer ()<ImportDataControllerDelegate, |
| 53 SettingsNavigationControllerDelegate> |
| 54 |
| 55 // Starts the watchdog timer with a timeout of |
| 56 // |kAuthenticationFlowTimeoutSeconds| for the fetching managed status |
| 57 // operation. It will notify |_delegate| of the failure unless |
| 58 // |stopWatchdogTimer| is called before it times out. |
| 59 - (void)startWatchdogTimerForManagedStatus; |
| 60 |
| 61 // Stops the watchdog timer, and doesn't call the |timeoutDelegateSelector|. |
| 62 // Returns whether the watchdog was actually running. |
| 63 - (BOOL)stopWatchdogTimer; |
| 64 |
| 65 // Callback for when the alert is dismissed. |
| 66 - (void)alertControllerDidDisappear:(AlertCoordinator*)alertCoordinator; |
| 67 |
| 68 @end |
| 69 |
| 70 @implementation AuthenticationFlowPerformer { |
| 71 base::WeakNSProtocol<id<AuthenticationFlowPerformerDelegate>> _delegate; |
| 72 base::scoped_nsobject<AlertCoordinator> _alertCoordinator; |
| 73 base::scoped_nsobject<SettingsNavigationController> _navigationController; |
| 74 std::unique_ptr<base::Timer> _watchdogTimer; |
| 75 } |
| 76 |
| 77 - (id<AuthenticationFlowPerformerDelegate>)delegate { |
| 78 return _delegate.get(); |
| 79 } |
| 80 |
| 81 - (instancetype)initWithDelegate: |
| 82 (id<AuthenticationFlowPerformerDelegate>)delegate { |
| 83 self = [super init]; |
| 84 if (self) |
| 85 _delegate.reset(delegate); |
| 86 return self; |
| 87 } |
| 88 |
| 89 - (void)cancelAndDismiss { |
| 90 [_alertCoordinator executeCancelHandler]; |
| 91 [_alertCoordinator stop]; |
| 92 if (_navigationController) { |
| 93 [_navigationController settingsWillBeDismissed]; |
| 94 _navigationController.reset(); |
| 95 [[_delegate presentingViewController] dismissViewControllerAnimated:NO |
| 96 completion:nil]; |
| 97 } |
| 98 [self stopWatchdogTimer]; |
| 99 } |
| 100 |
| 101 - (void)commitSyncForBrowserState:(ios::ChromeBrowserState*)browserState { |
| 102 SyncSetupServiceFactory::GetForBrowserState(browserState)->CommitChanges(); |
| 103 } |
| 104 |
| 105 - (void)startWatchdogTimerForManagedStatus { |
| 106 base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
| 107 ProceduralBlock onTimeout = ^{ |
| 108 base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
| 109 [weakSelf retain]); |
| 110 if (!strongSelf) |
| 111 return; |
| 112 [strongSelf stopWatchdogTimer]; |
| 113 NSError* error = [NSError errorWithDomain:kAuthenticationErrorDomain |
| 114 code:TIMED_OUT_FETCH_POLICY |
| 115 userInfo:nil]; |
| 116 [strongSelf.get()->_delegate didFailFetchManagedStatus:error]; |
| 117 }; |
| 118 _watchdogTimer.reset(new base::Timer(false, false)); |
| 119 _watchdogTimer->Start(FROM_HERE, base::TimeDelta::FromSeconds( |
| 120 kAuthenticationFlowTimeoutSeconds), |
| 121 base::BindBlock(onTimeout)); |
| 122 } |
| 123 |
| 124 - (BOOL)stopWatchdogTimer { |
| 125 if (_watchdogTimer) { |
| 126 _watchdogTimer->Stop(); |
| 127 _watchdogTimer.reset(); |
| 128 return YES; |
| 129 } |
| 130 return NO; |
| 131 } |
| 132 |
| 133 - (void)fetchManagedStatus:(ios::ChromeBrowserState*)browserState |
| 134 forIdentity:(ChromeIdentity*)identity { |
| 135 if (!experimental_flags::IsMDMIntegrationEnabled()) { |
| 136 [_delegate didFetchManagedStatus:nil]; |
| 137 return; |
| 138 } |
| 139 if (gaia::ExtractDomainName(gaia::CanonicalizeEmail( |
| 140 base::SysNSStringToUTF8(identity.userEmail))) == "gmail.com") { |
| 141 // Do nothing for @gmail.com addresses as they can't have a hosted domain. |
| 142 // This avoids waiting for this step to complete (and a network call). |
| 143 [_delegate didFetchManagedStatus:nil]; |
| 144 return; |
| 145 } |
| 146 |
| 147 [self startWatchdogTimerForManagedStatus]; |
| 148 base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
| 149 ios::GetChromeBrowserProvider() |
| 150 ->GetChromeIdentityService() |
| 151 ->GetHostedDomainForIdentity( |
| 152 identity, ^(NSString* hosted_domain, NSError* error) { |
| 153 [weakSelf handleGetHostedDomain:hosted_domain |
| 154 error:error |
| 155 browserState:browserState]; |
| 156 }); |
| 157 } |
| 158 |
| 159 - (void)handleGetHostedDomain:(NSString*)hostedDomain |
| 160 error:(NSError*)error |
| 161 browserState:(ios::ChromeBrowserState*)browserState { |
| 162 if (![self stopWatchdogTimer]) { |
| 163 // Watchdog timer has already fired, don't notify the delegate. |
| 164 return; |
| 165 } |
| 166 if (error) { |
| 167 [_delegate didFailFetchManagedStatus:error]; |
| 168 return; |
| 169 } |
| 170 [_delegate didFetchManagedStatus:hostedDomain]; |
| 171 } |
| 172 |
| 173 - (void)signInIdentity:(ChromeIdentity*)identity |
| 174 withHostedDomain:(NSString*)hostedDomain |
| 175 toBrowserState:(ios::ChromeBrowserState*)browserState { |
| 176 AuthenticationServiceFactory::GetForBrowserState(browserState) |
| 177 ->SignIn(identity, base::SysNSStringToUTF8(hostedDomain)); |
| 178 } |
| 179 |
| 180 - (void)signOutBrowserState:(ios::ChromeBrowserState*)browserState { |
| 181 AuthenticationServiceFactory::GetForBrowserState(browserState) |
| 182 ->SignOut(signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, ^{ |
| 183 [_delegate didSignOut]; |
| 184 }); |
| 185 } |
| 186 |
| 187 - (void)signOutImmediatelyFromBrowserState: |
| 188 (ios::ChromeBrowserState*)browserState { |
| 189 AuthenticationServiceFactory::GetForBrowserState(browserState) |
| 190 ->SignOut(signin_metrics::ABORT_SIGNIN, nil); |
| 191 } |
| 192 |
| 193 - (void)promptSwitchFromManagedEmail:(NSString*)managedEmail |
| 194 withHostedDomain:(NSString*)hostedDomain |
| 195 toEmail:(NSString*)toEmail |
| 196 viewController:(UIViewController*)viewController { |
| 197 DCHECK(!_alertCoordinator); |
| 198 NSString* title = l10n_util::GetNSString(IDS_IOS_MANAGED_SWITCH_TITLE); |
| 199 NSString* subtitle = l10n_util::GetNSStringF( |
| 200 IDS_IOS_MANAGED_SWITCH_SUBTITLE, base::SysNSStringToUTF16(managedEmail), |
| 201 base::SysNSStringToUTF16(toEmail), |
| 202 base::SysNSStringToUTF16(hostedDomain)); |
| 203 NSString* acceptLabel = |
| 204 l10n_util::GetNSString(IDS_IOS_MANAGED_SWITCH_ACCEPT_BUTTON); |
| 205 NSString* cancelLabel = l10n_util::GetNSString(IDS_CANCEL); |
| 206 |
| 207 _alertCoordinator.reset([[AlertCoordinator alloc] |
| 208 initWithBaseViewController:viewController |
| 209 title:title |
| 210 message:subtitle]); |
| 211 |
| 212 base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
| 213 base::WeakNSObject<AlertCoordinator> weakAlert(_alertCoordinator); |
| 214 ProceduralBlock acceptBlock = ^{ |
| 215 base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
| 216 [weakSelf retain]); |
| 217 if (!strongSelf) |
| 218 return; |
| 219 [[strongSelf delegate] |
| 220 didChooseClearDataPolicy:SHOULD_CLEAR_DATA_CLEAR_DATA]; |
| 221 [strongSelf alertControllerDidDisappear:weakAlert]; |
| 222 }; |
| 223 ProceduralBlock cancelBlock = ^{ |
| 224 base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
| 225 [weakSelf retain]); |
| 226 if (!strongSelf) |
| 227 return; |
| 228 [[strongSelf delegate] didChooseCancel]; |
| 229 [strongSelf alertControllerDidDisappear:weakAlert]; |
| 230 }; |
| 231 |
| 232 [_alertCoordinator addItemWithTitle:cancelLabel |
| 233 action:cancelBlock |
| 234 style:UIAlertActionStyleCancel]; |
| 235 [_alertCoordinator addItemWithTitle:acceptLabel |
| 236 action:acceptBlock |
| 237 style:UIAlertActionStyleDefault]; |
| 238 [_alertCoordinator setCancelAction:cancelBlock]; |
| 239 [_alertCoordinator start]; |
| 240 } |
| 241 |
| 242 - (void)promptMergeCaseForIdentity:(ChromeIdentity*)identity |
| 243 browserState:(ios::ChromeBrowserState*)browserState |
| 244 viewController:(UIViewController*)viewController { |
| 245 BOOL isSignedIn = YES; |
| 246 NSString* lastSignedInEmail = |
| 247 [AuthenticationServiceFactory::GetForBrowserState(browserState) |
| 248 ->GetAuthenticatedIdentity() userEmail]; |
| 249 if (!lastSignedInEmail) { |
| 250 lastSignedInEmail = |
| 251 base::SysUTF8ToNSString(browserState->GetPrefs()->GetString( |
| 252 prefs::kGoogleServicesLastUsername)); |
| 253 isSignedIn = NO; |
| 254 } |
| 255 |
| 256 if (AuthenticationServiceFactory::GetForBrowserState(browserState) |
| 257 ->IsAuthenticatedIdentityManaged()) { |
| 258 NSString* hostedDomain = base::SysUTF8ToNSString( |
| 259 ios::SigninManagerFactory::GetForBrowserState(browserState) |
| 260 ->GetAuthenticatedAccountInfo() |
| 261 .hosted_domain); |
| 262 [self promptSwitchFromManagedEmail:lastSignedInEmail |
| 263 withHostedDomain:hostedDomain |
| 264 toEmail:[identity userEmail] |
| 265 viewController:viewController]; |
| 266 return; |
| 267 } |
| 268 _navigationController.reset([SettingsNavigationController |
| 269 newImportDataController:browserState |
| 270 delegate:self |
| 271 importDataDelegate:self |
| 272 fromEmail:lastSignedInEmail |
| 273 toEmail:[identity userEmail] |
| 274 isSignedIn:isSignedIn]); |
| 275 [_navigationController setShouldCommitSyncChangesOnDismissal:NO]; |
| 276 [[_delegate presentingViewController] |
| 277 presentViewController:_navigationController |
| 278 animated:YES |
| 279 completion:nil]; |
| 280 } |
| 281 |
| 282 - (void)clearData:(ios::ChromeBrowserState*)browserState { |
| 283 DCHECK(!AuthenticationServiceFactory::GetForBrowserState(browserState) |
| 284 ->GetAuthenticatedUserEmail()); |
| 285 BrowserStateDataRemover::ClearData(browserState, ^{ |
| 286 [_delegate didClearData]; |
| 287 }); |
| 288 } |
| 289 |
| 290 - (BOOL)shouldHandleMergeCaseForIdentity:(ChromeIdentity*)identity |
| 291 browserState: |
| 292 (ios::ChromeBrowserState*)browserState { |
| 293 std::string lastSignedInAccountId = |
| 294 browserState->GetPrefs()->GetString(prefs::kGoogleServicesLastAccountId); |
| 295 std::string currentSignedInAccountId = |
| 296 ios::AccountTrackerServiceFactory::GetForBrowserState(browserState) |
| 297 ->PickAccountIdForAccount( |
| 298 base::SysNSStringToUTF8([identity gaiaID]), |
| 299 base::SysNSStringToUTF8([identity userEmail])); |
| 300 if (!lastSignedInAccountId.empty()) { |
| 301 // Merge case exists if the id of the previously signed in account is |
| 302 // different from the one of the account being signed in. |
| 303 return lastSignedInAccountId != currentSignedInAccountId; |
| 304 } |
| 305 |
| 306 // kGoogleServicesLastAccountId pref might not have been populated yet, |
| 307 // check the old kGoogleServicesLastUsername pref. |
| 308 std::string lastSignedInEmail = |
| 309 browserState->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername); |
| 310 std::string currentSignedInEmail = |
| 311 base::SysNSStringToUTF8([identity userEmail]); |
| 312 return !lastSignedInEmail.empty() && |
| 313 !gaia::AreEmailsSame(currentSignedInEmail, lastSignedInEmail); |
| 314 } |
| 315 |
| 316 - (void)showManagedConfirmationForHostedDomain:(NSString*)hostedDomain |
| 317 viewController: |
| 318 (UIViewController*)viewController { |
| 319 DCHECK(!_alertCoordinator); |
| 320 NSString* title = l10n_util::GetNSString(IDS_IOS_MANAGED_SIGNIN_TITLE); |
| 321 NSString* subtitle = l10n_util::GetNSStringF( |
| 322 IDS_IOS_MANAGED_SIGNIN_SUBTITLE, base::SysNSStringToUTF16(hostedDomain)); |
| 323 NSString* acceptLabel = |
| 324 l10n_util::GetNSString(IDS_IOS_MANAGED_SIGNIN_ACCEPT_BUTTON); |
| 325 NSString* cancelLabel = l10n_util::GetNSString(IDS_CANCEL); |
| 326 |
| 327 _alertCoordinator.reset([[AlertCoordinator alloc] |
| 328 initWithBaseViewController:viewController |
| 329 title:title |
| 330 message:subtitle]); |
| 331 |
| 332 base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
| 333 base::WeakNSObject<AlertCoordinator> weakAlert(_alertCoordinator); |
| 334 ProceduralBlock acceptBlock = ^{ |
| 335 base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
| 336 [weakSelf retain]); |
| 337 if (!strongSelf) |
| 338 return; |
| 339 [[strongSelf delegate] didAcceptManagedConfirmation]; |
| 340 [strongSelf alertControllerDidDisappear:weakAlert]; |
| 341 }; |
| 342 ProceduralBlock cancelBlock = ^{ |
| 343 base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
| 344 [weakSelf retain]); |
| 345 if (!strongSelf) |
| 346 return; |
| 347 [[strongSelf delegate] didCancelManagedConfirmation]; |
| 348 [strongSelf alertControllerDidDisappear:weakAlert]; |
| 349 }; |
| 350 |
| 351 [_alertCoordinator addItemWithTitle:cancelLabel |
| 352 action:cancelBlock |
| 353 style:UIAlertActionStyleCancel]; |
| 354 [_alertCoordinator addItemWithTitle:acceptLabel |
| 355 action:acceptBlock |
| 356 style:UIAlertActionStyleDefault]; |
| 357 [_alertCoordinator setCancelAction:cancelBlock]; |
| 358 [_alertCoordinator start]; |
| 359 } |
| 360 |
| 361 - (void)showAuthenticationError:(NSError*)error |
| 362 withCompletion:(ProceduralBlock)callback |
| 363 viewController:(UIViewController*)viewController { |
| 364 DCHECK(!_alertCoordinator); |
| 365 |
| 366 _alertCoordinator.reset( |
| 367 [ios_internal::ErrorCoordinatorNoItem(error, viewController) retain]); |
| 368 |
| 369 base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
| 370 base::WeakNSObject<AlertCoordinator> weakAlert(_alertCoordinator); |
| 371 ProceduralBlock dismissAction = ^{ |
| 372 if (callback) |
| 373 callback(); |
| 374 [weakSelf alertControllerDidDisappear:weakAlert]; |
| 375 }; |
| 376 |
| 377 NSString* okButtonLabel = l10n_util::GetNSString(IDS_OK); |
| 378 [_alertCoordinator addItemWithTitle:okButtonLabel |
| 379 action:dismissAction |
| 380 style:UIAlertActionStyleDefault]; |
| 381 |
| 382 [_alertCoordinator setCancelAction:dismissAction]; |
| 383 |
| 384 [_alertCoordinator start]; |
| 385 } |
| 386 |
| 387 - (void)alertControllerDidDisappear:(AlertCoordinator*)alertCoordinator { |
| 388 if (_alertCoordinator.get() != alertCoordinator) { |
| 389 // Do not reset the |_alertCoordinator| if it has changed. This typically |
| 390 // happens when the user taps on any of the actions on "Clear Data Before |
| 391 // Syncing?" dialog, as the sign-in confirmation dialog is created before |
| 392 // the "Clear Data Before Syncing?" dialog is dismissed. |
| 393 return; |
| 394 } |
| 395 _alertCoordinator.reset(); |
| 396 } |
| 397 |
| 398 #pragma mark - ImportDataControllerDelegate |
| 399 |
| 400 - (void)didChooseClearDataPolicy:(ImportDataCollectionViewController*)controller |
| 401 shouldClearData:(ShouldClearData)shouldClearData { |
| 402 DCHECK_NE(SHOULD_CLEAR_DATA_USER_CHOICE, shouldClearData); |
| 403 if (shouldClearData == SHOULD_CLEAR_DATA_CLEAR_DATA) { |
| 404 base::RecordAction( |
| 405 base::UserMetricsAction("Signin_ImportDataPrompt_DontImport")); |
| 406 } else { |
| 407 base::RecordAction( |
| 408 base::UserMetricsAction("Signin_ImportDataPrompt_ImportData")); |
| 409 } |
| 410 |
| 411 base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
| 412 ProceduralBlock block = ^{ |
| 413 base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
| 414 [weakSelf retain]); |
| 415 if (!strongSelf) |
| 416 return; |
| 417 strongSelf.get()->_navigationController.reset(); |
| 418 [[strongSelf delegate] didChooseClearDataPolicy:shouldClearData]; |
| 419 }; |
| 420 [_navigationController settingsWillBeDismissed]; |
| 421 [[_delegate presentingViewController] dismissViewControllerAnimated:YES |
| 422 completion:block]; |
| 423 } |
| 424 |
| 425 #pragma mark - SettingsNavigationControllerDelegate |
| 426 |
| 427 - (void)closeSettings { |
| 428 base::RecordAction(base::UserMetricsAction("Signin_ImportDataPrompt_Cancel")); |
| 429 |
| 430 base::WeakNSObject<AuthenticationFlowPerformer> weakSelf(self); |
| 431 ProceduralBlock block = ^{ |
| 432 base::scoped_nsobject<AuthenticationFlowPerformer> strongSelf( |
| 433 [weakSelf retain]); |
| 434 if (!strongSelf) |
| 435 return; |
| 436 strongSelf.get()->_navigationController.reset(); |
| 437 [[strongSelf delegate] didChooseCancel]; |
| 438 }; |
| 439 [_navigationController settingsWillBeDismissed]; |
| 440 [[_delegate presentingViewController] dismissViewControllerAnimated:YES |
| 441 completion:block]; |
| 442 } |
| 443 |
| 444 - (void)closeSettingsAndOpenNewIncognitoTab { |
| 445 NOTREACHED(); |
| 446 } |
| 447 |
| 448 - (void)closeSettingsAndOpenUrl:(OpenUrlCommand*)command { |
| 449 NOTREACHED(); |
| 450 } |
| 451 |
| 452 @end |
OLD | NEW |