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/signin_interaction_controller.h" |
| 6 |
| 7 #include "base/ios/weak_nsobject.h" |
| 8 #include "base/logging.h" |
| 9 #include "base/mac/scoped_block.h" |
| 10 #include "base/mac/scoped_nsobject.h" |
| 11 #include "base/strings/sys_string_conversions.h" |
| 12 #include "components/prefs/pref_service.h" |
| 13 #include "components/signin/core/browser/signin_manager.h" |
| 14 #include "components/signin/core/common/signin_pref_names.h" |
| 15 #import "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 16 #include "ios/chrome/browser/signin/authentication_service.h" |
| 17 #include "ios/chrome/browser/signin/authentication_service_factory.h" |
| 18 #include "ios/chrome/browser/signin/signin_manager_factory.h" |
| 19 #import "ios/chrome/browser/signin/signin_util.h" |
| 20 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
| 21 #import "ios/chrome/browser/ui/authentication/authentication_ui_util.h" |
| 22 #import "ios/chrome/browser/ui/authentication/chrome_signin_view_controller.h" |
| 23 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
| 24 #import "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 25 #import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
| 26 #import "ios/public/provider/chrome/browser/signin/chrome_identity_interaction_m
anager.h" |
| 27 #import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
| 28 |
| 29 using signin_ui::CompletionCallback; |
| 30 |
| 31 @interface SigninInteractionController ()< |
| 32 ChromeIdentityInteractionManagerDelegate, |
| 33 ChromeSigninViewControllerDelegate> { |
| 34 ios::ChromeBrowserState* browserState_; |
| 35 signin_metrics::AccessPoint signInAccessPoint_; |
| 36 base::scoped_nsobject<UIViewController> presentingViewController_; |
| 37 BOOL isPresentedOnSettings_; |
| 38 BOOL isCancelling_; |
| 39 BOOL isDismissing_; |
| 40 BOOL interactionManagerDismissalIgnored_; |
| 41 base::scoped_nsobject<AlertCoordinator> alertCoordinator_; |
| 42 base::mac::ScopedBlock<CompletionCallback> completionCallback_; |
| 43 base::scoped_nsobject<ChromeSigninViewController> signinViewController_; |
| 44 base::scoped_nsobject<ChromeIdentityInteractionManager> |
| 45 identityInteractionManager_; |
| 46 base::scoped_nsobject<ChromeIdentity> signInIdentity_; |
| 47 } |
| 48 @end |
| 49 |
| 50 @implementation SigninInteractionController |
| 51 |
| 52 - (id)init { |
| 53 NOTREACHED(); |
| 54 return nil; |
| 55 } |
| 56 |
| 57 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
| 58 presentingViewController:(UIViewController*)presentingViewController |
| 59 isPresentedOnSettings:(BOOL)isPresentedOnSettings |
| 60 signInAccessPoint:(signin_metrics::AccessPoint)accessPoint { |
| 61 self = [super init]; |
| 62 if (self) { |
| 63 DCHECK(browserState); |
| 64 DCHECK(presentingViewController); |
| 65 browserState_ = browserState; |
| 66 presentingViewController_.reset([presentingViewController retain]); |
| 67 isPresentedOnSettings_ = isPresentedOnSettings; |
| 68 signInAccessPoint_ = accessPoint; |
| 69 } |
| 70 return self; |
| 71 } |
| 72 |
| 73 - (void)cancel { |
| 74 // Cancelling and dismissing the |identityInteractionManager_| may call the |
| 75 // |completionCallback_| which could lead to |self| being released before the |
| 76 // end of this method. |self| is retained here to prevent this from happening. |
| 77 base::scoped_nsobject<SigninInteractionController> strongSelf([self retain]); |
| 78 isCancelling_ = YES; |
| 79 [alertCoordinator_ executeCancelHandler]; |
| 80 [alertCoordinator_ stop]; |
| 81 [identityInteractionManager_ cancelAndDismissAnimated:NO]; |
| 82 [signinViewController_ cancel]; |
| 83 isCancelling_ = NO; |
| 84 } |
| 85 |
| 86 - (void)cancelAndDismiss { |
| 87 isDismissing_ = YES; |
| 88 [self cancel]; |
| 89 isDismissing_ = NO; |
| 90 } |
| 91 |
| 92 - (void)signInWithCompletion:(CompletionCallback)completion |
| 93 viewController:(UIViewController*)viewController { |
| 94 signin_metrics::LogSigninAccessPointStarted(signInAccessPoint_); |
| 95 completionCallback_.reset(completion, base::scoped_policy::RETAIN); |
| 96 if (ios::GetChromeBrowserProvider() |
| 97 ->GetChromeIdentityService() |
| 98 ->HasIdentities()) { |
| 99 DCHECK(!signinViewController_); |
| 100 [self showSigninViewControllerWithIdentity:nil]; |
| 101 } else { |
| 102 identityInteractionManager_ = |
| 103 ios::GetChromeBrowserProvider() |
| 104 ->GetChromeIdentityService() |
| 105 ->NewChromeIdentityInteractionManager(browserState_, self); |
| 106 if (!identityInteractionManager_) { |
| 107 // Abort sign-in if the ChromeIdentityInteractionManager returned is |
| 108 // nil (this can happen when the iOS internal provider is not used). |
| 109 [self runCompletionCallbackWithSuccess:NO executeCommand:nil]; |
| 110 return; |
| 111 } |
| 112 |
| 113 base::WeakNSObject<SigninInteractionController> weakSelf(self); |
| 114 [identityInteractionManager_ |
| 115 addAccountWithCompletion:^(ChromeIdentity* identity, NSError* error) { |
| 116 [weakSelf handleIdentityAdded:identity |
| 117 error:error |
| 118 shouldSignIn:YES |
| 119 viewController:viewController]; |
| 120 }]; |
| 121 } |
| 122 } |
| 123 |
| 124 - (void)reAuthenticateWithCompletion:(CompletionCallback)completion |
| 125 viewController:(UIViewController*)viewController { |
| 126 signin_metrics::LogSigninAccessPointStarted(signInAccessPoint_); |
| 127 completionCallback_.reset(completion, base::scoped_policy::RETAIN); |
| 128 AccountInfo accountInfo = |
| 129 ios::SigninManagerFactory::GetForBrowserState(browserState_) |
| 130 ->GetAuthenticatedAccountInfo(); |
| 131 std::string emailToReauthenticate = accountInfo.email; |
| 132 std::string idToReauthenticate = accountInfo.gaia; |
| 133 if (emailToReauthenticate.empty() || idToReauthenticate.empty()) { |
| 134 // This corresponds to a re-authenticate request after the user was signed |
| 135 // out. This corresponds to the case where the identity was removed as a |
| 136 // result of the permissions being removed on the server or the identity |
| 137 // being removed from another app. |
| 138 // |
| 139 // Simply use the the last signed-in user email in this case and go though |
| 140 // the entire sign-in flow as sync needs to be configured. |
| 141 emailToReauthenticate = browserState_->GetPrefs()->GetString( |
| 142 prefs::kGoogleServicesLastUsername); |
| 143 idToReauthenticate = browserState_->GetPrefs()->GetString( |
| 144 prefs::kGoogleServicesLastAccountId); |
| 145 } |
| 146 DCHECK(!emailToReauthenticate.empty()); |
| 147 DCHECK(!idToReauthenticate.empty()); |
| 148 identityInteractionManager_ = |
| 149 ios::GetChromeBrowserProvider() |
| 150 ->GetChromeIdentityService() |
| 151 ->NewChromeIdentityInteractionManager(browserState_, self); |
| 152 base::WeakNSObject<SigninInteractionController> weakSelf(self); |
| 153 [identityInteractionManager_ |
| 154 reauthenticateUserWithID:base::SysUTF8ToNSString(idToReauthenticate) |
| 155 email:base::SysUTF8ToNSString(emailToReauthenticate) |
| 156 completion:^(ChromeIdentity* identity, NSError* error) { |
| 157 [weakSelf handleIdentityAdded:identity |
| 158 error:error |
| 159 shouldSignIn:YES |
| 160 viewController:viewController]; |
| 161 }]; |
| 162 } |
| 163 |
| 164 - (void)addAccountWithCompletion:(CompletionCallback)completion |
| 165 viewController:(UIViewController*)viewController { |
| 166 completionCallback_.reset(completion, base::scoped_policy::RETAIN); |
| 167 identityInteractionManager_ = |
| 168 ios::GetChromeBrowserProvider() |
| 169 ->GetChromeIdentityService() |
| 170 ->NewChromeIdentityInteractionManager(browserState_, self); |
| 171 base::WeakNSObject<SigninInteractionController> weakSelf(self); |
| 172 [identityInteractionManager_ |
| 173 addAccountWithCompletion:^(ChromeIdentity* identity, NSError* error) { |
| 174 [weakSelf handleIdentityAdded:identity |
| 175 error:error |
| 176 shouldSignIn:NO |
| 177 viewController:viewController]; |
| 178 }]; |
| 179 } |
| 180 |
| 181 #pragma mark - ChromeIdentityInteractionManager operations |
| 182 |
| 183 - (void)handleIdentityAdded:(ChromeIdentity*)identity |
| 184 error:(NSError*)error |
| 185 shouldSignIn:(BOOL)shouldSignIn |
| 186 viewController:(UIViewController*)viewController { |
| 187 if (!identityInteractionManager_) |
| 188 return; |
| 189 |
| 190 if (error) { |
| 191 // Filter out cancel and errors handled internally by ChromeIdentity. |
| 192 if (!ShouldHandleSigninError(error)) { |
| 193 [self runCompletionCallbackWithSuccess:NO executeCommand:nil]; |
| 194 return; |
| 195 } |
| 196 |
| 197 base::WeakNSObject<SigninInteractionController> weakSelf(self); |
| 198 ProceduralBlock dismissAction = ^{ |
| 199 [weakSelf runCompletionCallbackWithSuccess:NO executeCommand:nil]; |
| 200 }; |
| 201 |
| 202 alertCoordinator_.reset([ios_internal::ErrorCoordinator( |
| 203 error, dismissAction, viewController) retain]); |
| 204 [alertCoordinator_ start]; |
| 205 return; |
| 206 } |
| 207 if (shouldSignIn) { |
| 208 [self showSigninViewControllerWithIdentity:identity]; |
| 209 } else { |
| 210 [self runCompletionCallbackWithSuccess:YES executeCommand:nil]; |
| 211 } |
| 212 } |
| 213 |
| 214 - (void)dismissPresentedViewControllersAnimated:(BOOL)animated |
| 215 completion:(ProceduralBlock)completion { |
| 216 if ([presentingViewController_ presentedViewController]) { |
| 217 [presentingViewController_ dismissViewControllerAnimated:animated |
| 218 completion:completion]; |
| 219 } else if (completion) { |
| 220 completion(); |
| 221 } |
| 222 interactionManagerDismissalIgnored_ = NO; |
| 223 } |
| 224 |
| 225 #pragma mark - ChromeIdentityInteractionManagerDelegate |
| 226 |
| 227 - (void)interactionManager:(ChromeIdentityInteractionManager*)interactionManager |
| 228 presentViewController:(UIViewController*)viewController |
| 229 animated:(BOOL)animated |
| 230 completion:(ProceduralBlock)completion { |
| 231 [presentingViewController_ presentViewController:viewController |
| 232 animated:animated |
| 233 completion:completion]; |
| 234 } |
| 235 |
| 236 - (void)interactionManager:(ChromeIdentityInteractionManager*)interactionManager |
| 237 dismissViewControllerAnimated:(BOOL)animated |
| 238 completion:(ProceduralBlock)completion { |
| 239 // Avoid awkward double transitions by not dismissing |
| 240 // identityInteractionManager_| if the signin view controller will be |
| 241 // displayed on top of it. |identityInteractionManager_| will be dismissed |
| 242 // when the signin view controller will be dismissed. |
| 243 if ([interactionManager isCanceling]) { |
| 244 [self dismissPresentedViewControllersAnimated:animated |
| 245 completion:completion]; |
| 246 } else { |
| 247 interactionManagerDismissalIgnored_ = YES; |
| 248 if (completion) { |
| 249 completion(); |
| 250 } |
| 251 } |
| 252 } |
| 253 |
| 254 #pragma mark - ChromeSigninViewController operations |
| 255 |
| 256 - (void)showSigninViewControllerWithIdentity:(ChromeIdentity*)signInIdentity { |
| 257 signinViewController_.reset([[ChromeSigninViewController alloc] |
| 258 initWithBrowserState:browserState_ |
| 259 isPresentedOnSettings:isPresentedOnSettings_ |
| 260 signInAccessPoint:signInAccessPoint_ |
| 261 signInIdentity:signInIdentity]); |
| 262 [signinViewController_ setDelegate:self]; |
| 263 [signinViewController_ |
| 264 setModalPresentationStyle:UIModalPresentationFormSheet]; |
| 265 [signinViewController_ |
| 266 setModalTransitionStyle:UIModalTransitionStyleCoverVertical]; |
| 267 signInIdentity_.reset([signInIdentity retain]); |
| 268 |
| 269 UIViewController* presentingViewController = presentingViewController_; |
| 270 if (identityInteractionManager_) { |
| 271 // If |identityInteractionManager_| is currently displayed, |
| 272 // |signinViewController_| is presented on top of it (instead of on top of |
| 273 // |presentingViewController_|), to avoid an awkward transition (dismissing |
| 274 // |identityInteractionManager_|, followed by presenting |
| 275 // |signinViewController_|). |
| 276 while (presentingViewController.presentedViewController) { |
| 277 presentingViewController = |
| 278 presentingViewController.presentedViewController; |
| 279 } |
| 280 } |
| 281 [presentingViewController presentViewController:signinViewController_ |
| 282 animated:YES |
| 283 completion:nil]; |
| 284 } |
| 285 |
| 286 - (void)dismissSigninViewControllerWithSignInSuccess:(BOOL)success |
| 287 executeCommand: |
| 288 (GenericChromeCommand*)command { |
| 289 DCHECK(signinViewController_); |
| 290 if ((isCancelling_ && !isDismissing_) || |
| 291 ![presentingViewController_ presentedViewController]) { |
| 292 [self runCompletionCallbackWithSuccess:success executeCommand:command]; |
| 293 return; |
| 294 } |
| 295 ProceduralBlock completion = ^{ |
| 296 [self runCompletionCallbackWithSuccess:success executeCommand:command]; |
| 297 }; |
| 298 [self dismissPresentedViewControllersAnimated:YES completion:completion]; |
| 299 } |
| 300 |
| 301 #pragma mark - ChromeSigninViewControllerDelegate |
| 302 |
| 303 - (void)willStartSignIn:(ChromeSigninViewController*)controller { |
| 304 DCHECK_EQ(controller, signinViewController_.get()); |
| 305 } |
| 306 |
| 307 - (void)willStartAddAccount:(ChromeSigninViewController*)controller { |
| 308 DCHECK_EQ(controller, signinViewController_.get()); |
| 309 } |
| 310 |
| 311 - (void)didSkipSignIn:(ChromeSigninViewController*)controller { |
| 312 DCHECK_EQ(controller, signinViewController_.get()); |
| 313 [self dismissSigninViewControllerWithSignInSuccess:NO executeCommand:nil]; |
| 314 } |
| 315 |
| 316 - (void)didSignIn:(ChromeSigninViewController*)controller { |
| 317 DCHECK_EQ(controller, signinViewController_.get()); |
| 318 } |
| 319 |
| 320 - (void)didUndoSignIn:(ChromeSigninViewController*)controller |
| 321 identity:(ChromeIdentity*)identity { |
| 322 DCHECK_EQ(controller, signinViewController_.get()); |
| 323 if ([signInIdentity_.get() isEqual:identity]) { |
| 324 signInIdentity_.reset(); |
| 325 // This is best effort. If the operation fails, the account will be left on |
| 326 // the device. The user will not be warned either as this call is |
| 327 // asynchronous (but undo is not), the application might be in an unknown |
| 328 // state when the forget identity operation finishes. |
| 329 ios::GetChromeBrowserProvider()->GetChromeIdentityService()->ForgetIdentity( |
| 330 identity, nil); |
| 331 [self dismissSigninViewControllerWithSignInSuccess:NO executeCommand:nil]; |
| 332 } |
| 333 } |
| 334 |
| 335 - (void)didFailSignIn:(ChromeSigninViewController*)controller { |
| 336 DCHECK_EQ(controller, signinViewController_.get()); |
| 337 [self dismissSigninViewControllerWithSignInSuccess:NO executeCommand:nil]; |
| 338 } |
| 339 |
| 340 - (void)didAcceptSignIn:(ChromeSigninViewController*)controller |
| 341 executeCommand:(GenericChromeCommand*)command { |
| 342 DCHECK_EQ(controller, signinViewController_.get()); |
| 343 [self dismissSigninViewControllerWithSignInSuccess:YES |
| 344 executeCommand:command]; |
| 345 } |
| 346 |
| 347 #pragma mark - Utility methods |
| 348 |
| 349 - (void)runCompletionCallbackWithSuccess:(BOOL)success |
| 350 executeCommand:(GenericChromeCommand*)command { |
| 351 // In order to avoid awkward double transitions, |identityInteractionManager_| |
| 352 // is not dismissed when requested (except when canceling). However, in case |
| 353 // of errors, |identityInteractionManager_| needs to be directly dismissed, |
| 354 // which is done here. |
| 355 if (interactionManagerDismissalIgnored_) { |
| 356 [self dismissPresentedViewControllersAnimated:YES completion:nil]; |
| 357 } |
| 358 |
| 359 identityInteractionManager_.reset(); |
| 360 signinViewController_.reset(); |
| 361 UIViewController* presentingViewController = presentingViewController_; |
| 362 // Ensure self is not destroyed in the callbacks. |
| 363 base::scoped_nsobject<SigninInteractionController> strongSelf([self retain]); |
| 364 if (completionCallback_) { |
| 365 completionCallback_.get()(success); |
| 366 completionCallback_.reset(); |
| 367 } |
| 368 strongSelf.reset(); |
| 369 if (command) { |
| 370 [presentingViewController chromeExecuteCommand:command]; |
| 371 } |
| 372 } |
| 373 |
| 374 @end |
OLD | NEW |