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.h" |
| 6 |
| 7 #include "base/ios/weak_nsobject.h" |
| 8 #include "base/logging.h" |
| 9 #include "base/mac/objc_property_releaser.h" |
| 10 #include "base/mac/scoped_block.h" |
| 11 #include "base/mac/scoped_nsobject.h" |
| 12 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 13 #include "ios/chrome/browser/signin/authentication_service.h" |
| 14 #include "ios/chrome/browser/signin/authentication_service_factory.h" |
| 15 #include "ios/chrome/browser/signin/constants.h" |
| 16 #import "ios/chrome/browser/ui/authentication/authentication_flow_performer.h" |
| 17 #include "ios/chrome/grit/ios_strings.h" |
| 18 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 19 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
| 20 #include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
| 21 #include "ios/public/provider/chrome/browser/signin/signin_error_provider.h" |
| 22 #include "ui/base/l10n/l10n_util.h" |
| 23 |
| 24 using signin_ui::CompletionCallback; |
| 25 |
| 26 namespace { |
| 27 |
| 28 // The states of the sign-in flow state machine. |
| 29 enum AuthenticationState { |
| 30 BEGIN, |
| 31 CHECK_SIGNIN_STEPS, |
| 32 FETCH_MANAGED_STATUS, |
| 33 CHECK_MERGE_CASE, |
| 34 SHOW_MANAGED_CONFIRMATION, |
| 35 SIGN_OUT_IF_NEEDED, |
| 36 CLEAR_DATA, |
| 37 SIGN_IN, |
| 38 START_SYNC, |
| 39 COMPLETE_WITH_SUCCESS, |
| 40 COMPLETE_WITH_FAILURE, |
| 41 CLEANUP_BEFORE_DONE, |
| 42 DONE |
| 43 }; |
| 44 |
| 45 NSError* IdentityMissingError() { |
| 46 ios::SigninErrorProvider* provider = |
| 47 ios::GetChromeBrowserProvider()->GetSigninErrorProvider(); |
| 48 return [NSError |
| 49 errorWithDomain:provider->GetSigninErrorDomain() |
| 50 code:provider->GetCode(ios::SigninError::MISSING_IDENTITY) |
| 51 userInfo:nil]; |
| 52 } |
| 53 |
| 54 } // namespace |
| 55 |
| 56 @interface AuthenticationFlow () |
| 57 |
| 58 // Whether this flow is curently handling an error. |
| 59 @property(nonatomic, assign) BOOL handlingError; |
| 60 |
| 61 // Checks which sign-in steps to perform and updates member variables |
| 62 // accordingly. |
| 63 - (void)checkSigninSteps; |
| 64 |
| 65 // Continues the sign-in state machine starting from |_state| and invokes |
| 66 // |completion_| when finished. |
| 67 - (void)continueSignin; |
| 68 |
| 69 // Runs |completion_| asynchronously with |success| argument. |
| 70 - (void)completeSignInWithSuccess:(BOOL)success; |
| 71 |
| 72 // Cancels the current sign-in flow. |
| 73 - (void)cancelFlow; |
| 74 |
| 75 // Handles an authentication error and show an alert to the user. |
| 76 - (void)handleAuthenticationError:(NSError*)error; |
| 77 |
| 78 @end |
| 79 |
| 80 @implementation AuthenticationFlow { |
| 81 ShouldClearData _shouldClearData; |
| 82 PostSignInAction _postSignInAction; |
| 83 base::scoped_nsobject<UIViewController> _presentingViewController; |
| 84 base::mac::ScopedBlock<CompletionCallback> _signInCompletion; |
| 85 base::scoped_nsobject<AuthenticationFlowPerformer> _performer; |
| 86 |
| 87 // State machine tracking. |
| 88 AuthenticationState _state; |
| 89 BOOL _didSignIn; |
| 90 BOOL _failedOrCancelled; |
| 91 BOOL _shouldSignIn; |
| 92 BOOL _shouldSignOut; |
| 93 BOOL _shouldShowManagedConfirmation; |
| 94 BOOL _shouldStartSync; |
| 95 ios::ChromeBrowserState* _browserState; |
| 96 base::scoped_nsobject<ChromeIdentity> _browserStateIdentity; |
| 97 base::scoped_nsobject<ChromeIdentity> _identityToSignIn; |
| 98 base::scoped_nsobject<NSString> _identityToSignInHostedDomain; |
| 99 |
| 100 // This AuthenticationFlow keeps a reference to |self| while a sign-in flow is |
| 101 // is in progress to ensure it outlives any attempt to destroy it in |
| 102 // |_signInCompletion|. |
| 103 base::scoped_nsobject<AuthenticationFlow> _selfRetainer; |
| 104 |
| 105 base::mac::ObjCPropertyReleaser _propertyReleaser_AuthenticationFlow; |
| 106 } |
| 107 |
| 108 @synthesize handlingError = _handlingError; |
| 109 |
| 110 #pragma mark - Public methods |
| 111 |
| 112 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
| 113 identity:(ChromeIdentity*)identity |
| 114 shouldClearData:(ShouldClearData)shouldClearData |
| 115 postSignInAction:(PostSignInAction)postSignInAction |
| 116 presentingViewController: |
| 117 (UIViewController*)presentingViewController { |
| 118 if ((self = [super init])) { |
| 119 DCHECK(browserState); |
| 120 DCHECK(presentingViewController); |
| 121 _browserState = browserState; |
| 122 _identityToSignIn.reset([identity retain]); |
| 123 _shouldClearData = shouldClearData; |
| 124 _postSignInAction = postSignInAction; |
| 125 _presentingViewController.reset([presentingViewController retain]); |
| 126 _state = BEGIN; |
| 127 _propertyReleaser_AuthenticationFlow.Init(self, [AuthenticationFlow class]); |
| 128 } |
| 129 return self; |
| 130 } |
| 131 |
| 132 - (void)startSignInWithCompletion:(CompletionCallback)completion { |
| 133 DCHECK_EQ(BEGIN, _state); |
| 134 DCHECK(!_signInCompletion); |
| 135 DCHECK(completion); |
| 136 _signInCompletion.reset(completion, base::scoped_policy::RETAIN); |
| 137 _selfRetainer.reset([self retain]); |
| 138 // Kick off the state machine. |
| 139 if (!_performer) { |
| 140 _performer.reset( |
| 141 [[AuthenticationFlowPerformer alloc] initWithDelegate:self]); |
| 142 } |
| 143 [self continueSignin]; |
| 144 } |
| 145 |
| 146 - (void)cancelAndDismiss { |
| 147 if (_state == DONE) |
| 148 return; |
| 149 |
| 150 [_performer cancelAndDismiss]; |
| 151 if (_state != DONE) { |
| 152 // The performer might not have been able to continue the flow if it was |
| 153 // waiting for a callback (e.g. waiting for AccountReconcilor). In this |
| 154 // case, we force the flow to finish synchronously. |
| 155 [self cancelFlow]; |
| 156 } |
| 157 |
| 158 DCHECK_EQ(DONE, _state); |
| 159 } |
| 160 |
| 161 - (void)setPresentingViewController: |
| 162 (UIViewController*)presentingViewController { |
| 163 _presentingViewController.reset([presentingViewController retain]); |
| 164 } |
| 165 |
| 166 #pragma mark State machine management |
| 167 |
| 168 - (AuthenticationState)nextStateFailedOrCancelled { |
| 169 DCHECK(_failedOrCancelled); |
| 170 switch (_state) { |
| 171 case BEGIN: |
| 172 case CHECK_SIGNIN_STEPS: |
| 173 case FETCH_MANAGED_STATUS: |
| 174 case CHECK_MERGE_CASE: |
| 175 case SHOW_MANAGED_CONFIRMATION: |
| 176 case SIGN_OUT_IF_NEEDED: |
| 177 case CLEAR_DATA: |
| 178 case SIGN_IN: |
| 179 case START_SYNC: |
| 180 return COMPLETE_WITH_FAILURE; |
| 181 case COMPLETE_WITH_SUCCESS: |
| 182 case COMPLETE_WITH_FAILURE: |
| 183 return CLEANUP_BEFORE_DONE; |
| 184 case CLEANUP_BEFORE_DONE: |
| 185 case DONE: |
| 186 return DONE; |
| 187 } |
| 188 } |
| 189 |
| 190 - (AuthenticationState)nextState { |
| 191 DCHECK(!self.handlingError); |
| 192 if (_failedOrCancelled) { |
| 193 return [self nextStateFailedOrCancelled]; |
| 194 } |
| 195 DCHECK(!_failedOrCancelled); |
| 196 switch (_state) { |
| 197 case BEGIN: |
| 198 return CHECK_SIGNIN_STEPS; |
| 199 case CHECK_SIGNIN_STEPS: |
| 200 if (_shouldSignIn) |
| 201 return FETCH_MANAGED_STATUS; |
| 202 else |
| 203 return CHECK_MERGE_CASE; |
| 204 case FETCH_MANAGED_STATUS: |
| 205 return CHECK_MERGE_CASE; |
| 206 case CHECK_MERGE_CASE: |
| 207 if (_shouldShowManagedConfirmation) |
| 208 return SHOW_MANAGED_CONFIRMATION; |
| 209 else if (_shouldSignOut) |
| 210 return SIGN_OUT_IF_NEEDED; |
| 211 else if (_shouldClearData == SHOULD_CLEAR_DATA_CLEAR_DATA) |
| 212 return CLEAR_DATA; |
| 213 else if (_shouldSignIn) |
| 214 return SIGN_IN; |
| 215 else |
| 216 return COMPLETE_WITH_SUCCESS; |
| 217 case SHOW_MANAGED_CONFIRMATION: |
| 218 if (_shouldSignOut) |
| 219 return SIGN_OUT_IF_NEEDED; |
| 220 else if (_shouldClearData == SHOULD_CLEAR_DATA_CLEAR_DATA) |
| 221 return CLEAR_DATA; |
| 222 else if (_shouldSignIn) |
| 223 return SIGN_IN; |
| 224 else |
| 225 return COMPLETE_WITH_SUCCESS; |
| 226 case SIGN_OUT_IF_NEEDED: |
| 227 return _shouldClearData == SHOULD_CLEAR_DATA_CLEAR_DATA ? CLEAR_DATA |
| 228 : SIGN_IN; |
| 229 case CLEAR_DATA: |
| 230 return SIGN_IN; |
| 231 case SIGN_IN: |
| 232 if (_shouldStartSync) |
| 233 return START_SYNC; |
| 234 else |
| 235 return COMPLETE_WITH_SUCCESS; |
| 236 case START_SYNC: |
| 237 return COMPLETE_WITH_SUCCESS; |
| 238 case COMPLETE_WITH_SUCCESS: |
| 239 case COMPLETE_WITH_FAILURE: |
| 240 return CLEANUP_BEFORE_DONE; |
| 241 case CLEANUP_BEFORE_DONE: |
| 242 case DONE: |
| 243 return DONE; |
| 244 } |
| 245 } |
| 246 |
| 247 - (void)continueSignin { |
| 248 if (self.handlingError) { |
| 249 // The flow should not continue while the error is being handled, e.g. while |
| 250 // the user is being informed of an issue. |
| 251 return; |
| 252 } |
| 253 _state = [self nextState]; |
| 254 switch (_state) { |
| 255 case BEGIN: |
| 256 NOTREACHED(); |
| 257 return; |
| 258 |
| 259 case CHECK_SIGNIN_STEPS: |
| 260 [self checkSigninSteps]; |
| 261 [self continueSignin]; |
| 262 return; |
| 263 |
| 264 case FETCH_MANAGED_STATUS: |
| 265 [_performer fetchManagedStatus:_browserState |
| 266 forIdentity:_identityToSignIn]; |
| 267 return; |
| 268 |
| 269 case CHECK_MERGE_CASE: |
| 270 if ([_performer shouldHandleMergeCaseForIdentity:_identityToSignIn |
| 271 browserState:_browserState]) { |
| 272 if (_shouldClearData == SHOULD_CLEAR_DATA_USER_CHOICE) { |
| 273 [_performer promptMergeCaseForIdentity:_identityToSignIn |
| 274 browserState:_browserState |
| 275 viewController:_presentingViewController]; |
| 276 return; |
| 277 } |
| 278 } |
| 279 [self continueSignin]; |
| 280 return; |
| 281 |
| 282 case SHOW_MANAGED_CONFIRMATION: |
| 283 [_performer |
| 284 showManagedConfirmationForHostedDomain:_identityToSignInHostedDomain |
| 285 viewController:_presentingViewController]; |
| 286 return; |
| 287 |
| 288 case SIGN_OUT_IF_NEEDED: |
| 289 [_performer signOutBrowserState:_browserState]; |
| 290 return; |
| 291 |
| 292 case CLEAR_DATA: |
| 293 [_performer clearData:_browserState]; |
| 294 return; |
| 295 |
| 296 case SIGN_IN: |
| 297 [self signInIdentity:_identityToSignIn]; |
| 298 return; |
| 299 |
| 300 case START_SYNC: |
| 301 [_performer commitSyncForBrowserState:_browserState]; |
| 302 [self continueSignin]; |
| 303 return; |
| 304 |
| 305 case COMPLETE_WITH_SUCCESS: |
| 306 [self completeSignInWithSuccess:YES]; |
| 307 return; |
| 308 |
| 309 case COMPLETE_WITH_FAILURE: |
| 310 if (_didSignIn) { |
| 311 [_performer signOutImmediatelyFromBrowserState:_browserState]; |
| 312 // Enabling/disabling sync does not take effect in the sync backend |
| 313 // until committing changes. |
| 314 [_performer commitSyncForBrowserState:_browserState]; |
| 315 } |
| 316 [self completeSignInWithSuccess:NO]; |
| 317 return; |
| 318 case CLEANUP_BEFORE_DONE: |
| 319 // Clean up asynchronously to ensure that |self| does not die while |
| 320 // the flow is running. |
| 321 DCHECK([NSThread isMainThread]); |
| 322 dispatch_async(dispatch_get_main_queue(), ^{ |
| 323 _selfRetainer.reset(); |
| 324 }); |
| 325 [self continueSignin]; |
| 326 return; |
| 327 case DONE: |
| 328 return; |
| 329 } |
| 330 NOTREACHED(); |
| 331 } |
| 332 |
| 333 - (void)checkSigninSteps { |
| 334 _browserStateIdentity.reset( |
| 335 [AuthenticationServiceFactory::GetForBrowserState(_browserState) |
| 336 ->GetAuthenticatedIdentity() retain]); |
| 337 if (_browserStateIdentity) |
| 338 _shouldSignOut = YES; |
| 339 |
| 340 _shouldSignIn = YES; |
| 341 _shouldStartSync = _postSignInAction == POST_SIGNIN_ACTION_START_SYNC; |
| 342 } |
| 343 |
| 344 - (void)signInIdentity:(ChromeIdentity*)identity { |
| 345 if (ios::GetChromeBrowserProvider() |
| 346 ->GetChromeIdentityService() |
| 347 ->IsValidIdentity(identity)) { |
| 348 [_performer signInIdentity:identity |
| 349 withHostedDomain:_identityToSignInHostedDomain |
| 350 toBrowserState:_browserState]; |
| 351 _didSignIn = YES; |
| 352 [self continueSignin]; |
| 353 } else { |
| 354 // Handle the case where the identity is no longer valid. |
| 355 [self handleAuthenticationError:IdentityMissingError()]; |
| 356 } |
| 357 } |
| 358 |
| 359 - (void)completeSignInWithSuccess:(BOOL)success { |
| 360 DCHECK(_signInCompletion) |
| 361 << "|completeSignInWithSuccess| should not be called twice."; |
| 362 _signInCompletion.get()(success); |
| 363 _signInCompletion.reset(); |
| 364 [self continueSignin]; |
| 365 } |
| 366 |
| 367 - (void)cancelFlow { |
| 368 if (_failedOrCancelled) { |
| 369 // Avoid double handling of cancel or error. |
| 370 return; |
| 371 } |
| 372 _failedOrCancelled = YES; |
| 373 [self continueSignin]; |
| 374 } |
| 375 |
| 376 - (void)handleAuthenticationError:(NSError*)error { |
| 377 if (_failedOrCancelled) { |
| 378 // Avoid double handling of cancel or error. |
| 379 return; |
| 380 } |
| 381 DCHECK(error); |
| 382 _failedOrCancelled = YES; |
| 383 self.handlingError = YES; |
| 384 base::WeakNSObject<AuthenticationFlow> weakSelf(self); |
| 385 [_performer showAuthenticationError:error |
| 386 withCompletion:^{ |
| 387 base::scoped_nsobject<AuthenticationFlow> strongSelf( |
| 388 [weakSelf retain]); |
| 389 if (!strongSelf) |
| 390 return; |
| 391 [strongSelf setHandlingError:NO]; |
| 392 [strongSelf continueSignin]; |
| 393 } |
| 394 viewController:_presentingViewController]; |
| 395 } |
| 396 |
| 397 #pragma mark AuthenticationFlowPerformerDelegate |
| 398 |
| 399 - (void)didSignOut { |
| 400 [self continueSignin]; |
| 401 } |
| 402 |
| 403 - (void)didClearData { |
| 404 [self continueSignin]; |
| 405 } |
| 406 |
| 407 - (void)didChooseClearDataPolicy:(ShouldClearData)shouldClearData { |
| 408 DCHECK_NE(SHOULD_CLEAR_DATA_USER_CHOICE, shouldClearData); |
| 409 _shouldSignOut = YES; |
| 410 _shouldClearData = shouldClearData; |
| 411 [self continueSignin]; |
| 412 } |
| 413 |
| 414 - (void)didChooseCancel { |
| 415 [self cancelFlow]; |
| 416 } |
| 417 |
| 418 - (void)didFetchManagedStatus:(NSString*)hostedDomain { |
| 419 DCHECK_EQ(FETCH_MANAGED_STATUS, _state); |
| 420 _shouldShowManagedConfirmation = [hostedDomain length] > 0; |
| 421 _identityToSignInHostedDomain.reset([hostedDomain retain]); |
| 422 [self continueSignin]; |
| 423 } |
| 424 |
| 425 - (void)didFailFetchManagedStatus:(NSError*)error { |
| 426 DCHECK_EQ(FETCH_MANAGED_STATUS, _state); |
| 427 NSError* flowError = |
| 428 [NSError errorWithDomain:kAuthenticationErrorDomain |
| 429 code:AUTHENTICATION_FLOW_ERROR |
| 430 userInfo:@{ |
| 431 NSLocalizedDescriptionKey : |
| 432 l10n_util::GetNSString(IDS_IOS_SIGN_IN_FAILED), |
| 433 NSUnderlyingErrorKey : error |
| 434 }]; |
| 435 [self handleAuthenticationError:flowError]; |
| 436 } |
| 437 |
| 438 - (void)didAcceptManagedConfirmation { |
| 439 [self continueSignin]; |
| 440 } |
| 441 |
| 442 - (void)didCancelManagedConfirmation { |
| 443 [self cancelFlow]; |
| 444 } |
| 445 |
| 446 - (UIViewController*)presentingViewController { |
| 447 return _presentingViewController; |
| 448 } |
| 449 |
| 450 #pragma mark - Used for testing |
| 451 |
| 452 - (void)setPerformerForTesting:(AuthenticationFlowPerformer*)performer { |
| 453 _performer.reset([performer retain]); |
| 454 } |
| 455 |
| 456 @end |
OLD | NEW |