Index: ios/chrome/browser/ui/authentication/authentication_flow.mm |
diff --git a/ios/chrome/browser/ui/authentication/authentication_flow.mm b/ios/chrome/browser/ui/authentication/authentication_flow.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ca6c1dddd09746f0a6c8d280449f9e6787877ae0 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/authentication/authentication_flow.mm |
@@ -0,0 +1,456 @@ |
+// 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.h" |
+ |
+#include "base/ios/weak_nsobject.h" |
+#include "base/logging.h" |
+#include "base/mac/objc_property_releaser.h" |
+#include "base/mac/scoped_block.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#include "ios/chrome/browser/signin/authentication_service.h" |
+#include "ios/chrome/browser/signin/authentication_service_factory.h" |
+#include "ios/chrome/browser/signin/constants.h" |
+#import "ios/chrome/browser/ui/authentication/authentication_flow_performer.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#import "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+#import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
+#include "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
+#include "ios/public/provider/chrome/browser/signin/signin_error_provider.h" |
+#include "ui/base/l10n/l10n_util.h" |
+ |
+using signin_ui::CompletionCallback; |
+ |
+namespace { |
+ |
+// The states of the sign-in flow state machine. |
+enum AuthenticationState { |
+ BEGIN, |
+ CHECK_SIGNIN_STEPS, |
+ FETCH_MANAGED_STATUS, |
+ CHECK_MERGE_CASE, |
+ SHOW_MANAGED_CONFIRMATION, |
+ SIGN_OUT_IF_NEEDED, |
+ CLEAR_DATA, |
+ SIGN_IN, |
+ START_SYNC, |
+ COMPLETE_WITH_SUCCESS, |
+ COMPLETE_WITH_FAILURE, |
+ CLEANUP_BEFORE_DONE, |
+ DONE |
+}; |
+ |
+NSError* IdentityMissingError() { |
+ ios::SigninErrorProvider* provider = |
+ ios::GetChromeBrowserProvider()->GetSigninErrorProvider(); |
+ return [NSError |
+ errorWithDomain:provider->GetSigninErrorDomain() |
+ code:provider->GetCode(ios::SigninError::MISSING_IDENTITY) |
+ userInfo:nil]; |
+} |
+ |
+} // namespace |
+ |
+@interface AuthenticationFlow () |
+ |
+// Whether this flow is curently handling an error. |
+@property(nonatomic, assign) BOOL handlingError; |
+ |
+// Checks which sign-in steps to perform and updates member variables |
+// accordingly. |
+- (void)checkSigninSteps; |
+ |
+// Continues the sign-in state machine starting from |_state| and invokes |
+// |completion_| when finished. |
+- (void)continueSignin; |
+ |
+// Runs |completion_| asynchronously with |success| argument. |
+- (void)completeSignInWithSuccess:(BOOL)success; |
+ |
+// Cancels the current sign-in flow. |
+- (void)cancelFlow; |
+ |
+// Handles an authentication error and show an alert to the user. |
+- (void)handleAuthenticationError:(NSError*)error; |
+ |
+@end |
+ |
+@implementation AuthenticationFlow { |
+ ShouldClearData _shouldClearData; |
+ PostSignInAction _postSignInAction; |
+ base::scoped_nsobject<UIViewController> _presentingViewController; |
+ base::mac::ScopedBlock<CompletionCallback> _signInCompletion; |
+ base::scoped_nsobject<AuthenticationFlowPerformer> _performer; |
+ |
+ // State machine tracking. |
+ AuthenticationState _state; |
+ BOOL _didSignIn; |
+ BOOL _failedOrCancelled; |
+ BOOL _shouldSignIn; |
+ BOOL _shouldSignOut; |
+ BOOL _shouldShowManagedConfirmation; |
+ BOOL _shouldStartSync; |
+ ios::ChromeBrowserState* _browserState; |
+ base::scoped_nsobject<ChromeIdentity> _browserStateIdentity; |
+ base::scoped_nsobject<ChromeIdentity> _identityToSignIn; |
+ base::scoped_nsobject<NSString> _identityToSignInHostedDomain; |
+ |
+ // This AuthenticationFlow keeps a reference to |self| while a sign-in flow is |
+ // is in progress to ensure it outlives any attempt to destroy it in |
+ // |_signInCompletion|. |
+ base::scoped_nsobject<AuthenticationFlow> _selfRetainer; |
+ |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_AuthenticationFlow; |
+} |
+ |
+@synthesize handlingError = _handlingError; |
+ |
+#pragma mark - Public methods |
+ |
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
+ identity:(ChromeIdentity*)identity |
+ shouldClearData:(ShouldClearData)shouldClearData |
+ postSignInAction:(PostSignInAction)postSignInAction |
+ presentingViewController: |
+ (UIViewController*)presentingViewController { |
+ if ((self = [super init])) { |
+ DCHECK(browserState); |
+ DCHECK(presentingViewController); |
+ _browserState = browserState; |
+ _identityToSignIn.reset([identity retain]); |
+ _shouldClearData = shouldClearData; |
+ _postSignInAction = postSignInAction; |
+ _presentingViewController.reset([presentingViewController retain]); |
+ _state = BEGIN; |
+ _propertyReleaser_AuthenticationFlow.Init(self, [AuthenticationFlow class]); |
+ } |
+ return self; |
+} |
+ |
+- (void)startSignInWithCompletion:(CompletionCallback)completion { |
+ DCHECK_EQ(BEGIN, _state); |
+ DCHECK(!_signInCompletion); |
+ DCHECK(completion); |
+ _signInCompletion.reset(completion, base::scoped_policy::RETAIN); |
+ _selfRetainer.reset([self retain]); |
+ // Kick off the state machine. |
+ if (!_performer) { |
+ _performer.reset( |
+ [[AuthenticationFlowPerformer alloc] initWithDelegate:self]); |
+ } |
+ [self continueSignin]; |
+} |
+ |
+- (void)cancelAndDismiss { |
+ if (_state == DONE) |
+ return; |
+ |
+ [_performer cancelAndDismiss]; |
+ if (_state != DONE) { |
+ // The performer might not have been able to continue the flow if it was |
+ // waiting for a callback (e.g. waiting for AccountReconcilor). In this |
+ // case, we force the flow to finish synchronously. |
+ [self cancelFlow]; |
+ } |
+ |
+ DCHECK_EQ(DONE, _state); |
+} |
+ |
+- (void)setPresentingViewController: |
+ (UIViewController*)presentingViewController { |
+ _presentingViewController.reset([presentingViewController retain]); |
+} |
+ |
+#pragma mark State machine management |
+ |
+- (AuthenticationState)nextStateFailedOrCancelled { |
+ DCHECK(_failedOrCancelled); |
+ switch (_state) { |
+ case BEGIN: |
+ case CHECK_SIGNIN_STEPS: |
+ case FETCH_MANAGED_STATUS: |
+ case CHECK_MERGE_CASE: |
+ case SHOW_MANAGED_CONFIRMATION: |
+ case SIGN_OUT_IF_NEEDED: |
+ case CLEAR_DATA: |
+ case SIGN_IN: |
+ case START_SYNC: |
+ return COMPLETE_WITH_FAILURE; |
+ case COMPLETE_WITH_SUCCESS: |
+ case COMPLETE_WITH_FAILURE: |
+ return CLEANUP_BEFORE_DONE; |
+ case CLEANUP_BEFORE_DONE: |
+ case DONE: |
+ return DONE; |
+ } |
+} |
+ |
+- (AuthenticationState)nextState { |
+ DCHECK(!self.handlingError); |
+ if (_failedOrCancelled) { |
+ return [self nextStateFailedOrCancelled]; |
+ } |
+ DCHECK(!_failedOrCancelled); |
+ switch (_state) { |
+ case BEGIN: |
+ return CHECK_SIGNIN_STEPS; |
+ case CHECK_SIGNIN_STEPS: |
+ if (_shouldSignIn) |
+ return FETCH_MANAGED_STATUS; |
+ else |
+ return CHECK_MERGE_CASE; |
+ case FETCH_MANAGED_STATUS: |
+ return CHECK_MERGE_CASE; |
+ case CHECK_MERGE_CASE: |
+ if (_shouldShowManagedConfirmation) |
+ return SHOW_MANAGED_CONFIRMATION; |
+ else if (_shouldSignOut) |
+ return SIGN_OUT_IF_NEEDED; |
+ else if (_shouldClearData == SHOULD_CLEAR_DATA_CLEAR_DATA) |
+ return CLEAR_DATA; |
+ else if (_shouldSignIn) |
+ return SIGN_IN; |
+ else |
+ return COMPLETE_WITH_SUCCESS; |
+ case SHOW_MANAGED_CONFIRMATION: |
+ if (_shouldSignOut) |
+ return SIGN_OUT_IF_NEEDED; |
+ else if (_shouldClearData == SHOULD_CLEAR_DATA_CLEAR_DATA) |
+ return CLEAR_DATA; |
+ else if (_shouldSignIn) |
+ return SIGN_IN; |
+ else |
+ return COMPLETE_WITH_SUCCESS; |
+ case SIGN_OUT_IF_NEEDED: |
+ return _shouldClearData == SHOULD_CLEAR_DATA_CLEAR_DATA ? CLEAR_DATA |
+ : SIGN_IN; |
+ case CLEAR_DATA: |
+ return SIGN_IN; |
+ case SIGN_IN: |
+ if (_shouldStartSync) |
+ return START_SYNC; |
+ else |
+ return COMPLETE_WITH_SUCCESS; |
+ case START_SYNC: |
+ return COMPLETE_WITH_SUCCESS; |
+ case COMPLETE_WITH_SUCCESS: |
+ case COMPLETE_WITH_FAILURE: |
+ return CLEANUP_BEFORE_DONE; |
+ case CLEANUP_BEFORE_DONE: |
+ case DONE: |
+ return DONE; |
+ } |
+} |
+ |
+- (void)continueSignin { |
+ if (self.handlingError) { |
+ // The flow should not continue while the error is being handled, e.g. while |
+ // the user is being informed of an issue. |
+ return; |
+ } |
+ _state = [self nextState]; |
+ switch (_state) { |
+ case BEGIN: |
+ NOTREACHED(); |
+ return; |
+ |
+ case CHECK_SIGNIN_STEPS: |
+ [self checkSigninSteps]; |
+ [self continueSignin]; |
+ return; |
+ |
+ case FETCH_MANAGED_STATUS: |
+ [_performer fetchManagedStatus:_browserState |
+ forIdentity:_identityToSignIn]; |
+ return; |
+ |
+ case CHECK_MERGE_CASE: |
+ if ([_performer shouldHandleMergeCaseForIdentity:_identityToSignIn |
+ browserState:_browserState]) { |
+ if (_shouldClearData == SHOULD_CLEAR_DATA_USER_CHOICE) { |
+ [_performer promptMergeCaseForIdentity:_identityToSignIn |
+ browserState:_browserState |
+ viewController:_presentingViewController]; |
+ return; |
+ } |
+ } |
+ [self continueSignin]; |
+ return; |
+ |
+ case SHOW_MANAGED_CONFIRMATION: |
+ [_performer |
+ showManagedConfirmationForHostedDomain:_identityToSignInHostedDomain |
+ viewController:_presentingViewController]; |
+ return; |
+ |
+ case SIGN_OUT_IF_NEEDED: |
+ [_performer signOutBrowserState:_browserState]; |
+ return; |
+ |
+ case CLEAR_DATA: |
+ [_performer clearData:_browserState]; |
+ return; |
+ |
+ case SIGN_IN: |
+ [self signInIdentity:_identityToSignIn]; |
+ return; |
+ |
+ case START_SYNC: |
+ [_performer commitSyncForBrowserState:_browserState]; |
+ [self continueSignin]; |
+ return; |
+ |
+ case COMPLETE_WITH_SUCCESS: |
+ [self completeSignInWithSuccess:YES]; |
+ return; |
+ |
+ case COMPLETE_WITH_FAILURE: |
+ if (_didSignIn) { |
+ [_performer signOutImmediatelyFromBrowserState:_browserState]; |
+ // Enabling/disabling sync does not take effect in the sync backend |
+ // until committing changes. |
+ [_performer commitSyncForBrowserState:_browserState]; |
+ } |
+ [self completeSignInWithSuccess:NO]; |
+ return; |
+ case CLEANUP_BEFORE_DONE: |
+ // Clean up asynchronously to ensure that |self| does not die while |
+ // the flow is running. |
+ DCHECK([NSThread isMainThread]); |
+ dispatch_async(dispatch_get_main_queue(), ^{ |
+ _selfRetainer.reset(); |
+ }); |
+ [self continueSignin]; |
+ return; |
+ case DONE: |
+ return; |
+ } |
+ NOTREACHED(); |
+} |
+ |
+- (void)checkSigninSteps { |
+ _browserStateIdentity.reset( |
+ [AuthenticationServiceFactory::GetForBrowserState(_browserState) |
+ ->GetAuthenticatedIdentity() retain]); |
+ if (_browserStateIdentity) |
+ _shouldSignOut = YES; |
+ |
+ _shouldSignIn = YES; |
+ _shouldStartSync = _postSignInAction == POST_SIGNIN_ACTION_START_SYNC; |
+} |
+ |
+- (void)signInIdentity:(ChromeIdentity*)identity { |
+ if (ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->IsValidIdentity(identity)) { |
+ [_performer signInIdentity:identity |
+ withHostedDomain:_identityToSignInHostedDomain |
+ toBrowserState:_browserState]; |
+ _didSignIn = YES; |
+ [self continueSignin]; |
+ } else { |
+ // Handle the case where the identity is no longer valid. |
+ [self handleAuthenticationError:IdentityMissingError()]; |
+ } |
+} |
+ |
+- (void)completeSignInWithSuccess:(BOOL)success { |
+ DCHECK(_signInCompletion) |
+ << "|completeSignInWithSuccess| should not be called twice."; |
+ _signInCompletion.get()(success); |
+ _signInCompletion.reset(); |
+ [self continueSignin]; |
+} |
+ |
+- (void)cancelFlow { |
+ if (_failedOrCancelled) { |
+ // Avoid double handling of cancel or error. |
+ return; |
+ } |
+ _failedOrCancelled = YES; |
+ [self continueSignin]; |
+} |
+ |
+- (void)handleAuthenticationError:(NSError*)error { |
+ if (_failedOrCancelled) { |
+ // Avoid double handling of cancel or error. |
+ return; |
+ } |
+ DCHECK(error); |
+ _failedOrCancelled = YES; |
+ self.handlingError = YES; |
+ base::WeakNSObject<AuthenticationFlow> weakSelf(self); |
+ [_performer showAuthenticationError:error |
+ withCompletion:^{ |
+ base::scoped_nsobject<AuthenticationFlow> strongSelf( |
+ [weakSelf retain]); |
+ if (!strongSelf) |
+ return; |
+ [strongSelf setHandlingError:NO]; |
+ [strongSelf continueSignin]; |
+ } |
+ viewController:_presentingViewController]; |
+} |
+ |
+#pragma mark AuthenticationFlowPerformerDelegate |
+ |
+- (void)didSignOut { |
+ [self continueSignin]; |
+} |
+ |
+- (void)didClearData { |
+ [self continueSignin]; |
+} |
+ |
+- (void)didChooseClearDataPolicy:(ShouldClearData)shouldClearData { |
+ DCHECK_NE(SHOULD_CLEAR_DATA_USER_CHOICE, shouldClearData); |
+ _shouldSignOut = YES; |
+ _shouldClearData = shouldClearData; |
+ [self continueSignin]; |
+} |
+ |
+- (void)didChooseCancel { |
+ [self cancelFlow]; |
+} |
+ |
+- (void)didFetchManagedStatus:(NSString*)hostedDomain { |
+ DCHECK_EQ(FETCH_MANAGED_STATUS, _state); |
+ _shouldShowManagedConfirmation = [hostedDomain length] > 0; |
+ _identityToSignInHostedDomain.reset([hostedDomain retain]); |
+ [self continueSignin]; |
+} |
+ |
+- (void)didFailFetchManagedStatus:(NSError*)error { |
+ DCHECK_EQ(FETCH_MANAGED_STATUS, _state); |
+ NSError* flowError = |
+ [NSError errorWithDomain:kAuthenticationErrorDomain |
+ code:AUTHENTICATION_FLOW_ERROR |
+ userInfo:@{ |
+ NSLocalizedDescriptionKey : |
+ l10n_util::GetNSString(IDS_IOS_SIGN_IN_FAILED), |
+ NSUnderlyingErrorKey : error |
+ }]; |
+ [self handleAuthenticationError:flowError]; |
+} |
+ |
+- (void)didAcceptManagedConfirmation { |
+ [self continueSignin]; |
+} |
+ |
+- (void)didCancelManagedConfirmation { |
+ [self cancelFlow]; |
+} |
+ |
+- (UIViewController*)presentingViewController { |
+ return _presentingViewController; |
+} |
+ |
+#pragma mark - Used for testing |
+ |
+- (void)setPerformerForTesting:(AuthenticationFlowPerformer*)performer { |
+ _performer.reset([performer retain]); |
+} |
+ |
+@end |