/* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "MainViewController.h" #import "MainViewController+App.h" #import "MainViewController+Auth.h" #import "MainViewController+AutoTests.h" #import "MainViewController+Custom.h" #import "MainViewController+Email.h" #import "MainViewController+Facebook.h" #import "MainViewController+GameCenter.h" #import "MainViewController+Google.h" #import "MainViewController+Internal.h" #import "MainViewController+OAuth.h" #import "MainViewController+OOB.h" #import "MainViewController+Phone.h" #import "MainViewController+User.h" #import #import #import #import "AppManager.h" #import "AuthCredentials.h" #import "FIRAdditionalUserInfo.h" #import "FIROAuthProvider.h" #import "FIRPhoneAuthCredential.h" #import "FIRPhoneAuthProvider.h" #import "FIRAuthTokenResult.h" #import "FirebaseAuth.h" #import "FacebookAuthProvider.h" #import "GoogleAuthProvider.h" #import "SettingsViewController.h" #import "StaticContentTableViewManager.h" #import "UIViewController+Alerts.h" #import "UserInfoViewController.h" #import "UserTableViewCell.h" #import "FIRAuth_Internal.h" NS_ASSUME_NONNULL_BEGIN static NSString *const kSectionTitleSettings = @"Settings"; static NSString *const kSectionTitleUserDetails = @"User Defaults"; static NSString *const kSwitchToInMemoryUserTitle = @"Switch to in memory user"; static NSString *const kNewOrExistingUserToggleTitle = @"New or Existing User Toggle"; typedef void (^FIRTokenCallback)(NSString *_Nullable token, NSError *_Nullable error); @implementation MainViewController { NSMutableString *_consoleString; /** @var _userInMemory @brief Acts like the "memory" function of a calculator. An operation allows sample app users to assign this value based on @c FIRAuth.currentUser or clear this value. */ FIRUser *_userInMemory; /** @var _useUserInMemory @brief Instructs the application to use _userInMemory instead of @c FIRAuth.currentUser for testing operations. This allows us to test if things still work with a user who is not the @c FIRAuth.currentUser, and also allows us to test those things while @c FIRAuth.currentUser remains nil (after a sign-out) and also when @c FIRAuth.currentUser is non-nil (do to a subsequent sign-in.) */ BOOL _useUserInMemory; } - (id)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { _actionCodeRequestType = ActionCodeRequestTypeInApp; _actionCodeContinueURL = [NSURL URLWithString:KCONTINUE_URL]; _authStateDidChangeListeners = [NSMutableArray array]; _IDTokenDidChangeListeners = [NSMutableArray array]; _googleOAuthProvider = [FIROAuthProvider providerWithProviderID:FIRGoogleAuthProviderID]; _microsoftOAuthProvider = [FIROAuthProvider providerWithProviderID:@"microsoft.com"]; _twitterOAuthProvider = [FIROAuthProvider providerWithProviderID:@"twitter.com"]; _linkedinOAuthProvider = [FIROAuthProvider providerWithProviderID:@"linkedin.com"]; _yahooOAuthProvider = [FIROAuthProvider providerWithProviderID:@"yahoo.com"]; _gitHubOAuthProvider = [FIROAuthProvider providerWithProviderID:@"github.com"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(authStateChangedForAuth:) name:FIRAuthStateDidChangeNotification object:nil]; self.useStatusBarSpinner = YES; } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Give us a circle for the image view: _userInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius = _userInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f; _userInfoTableViewCell.userInfoProfileURLImageView.layer.masksToBounds = YES; _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius = _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f; _userInMemoryInfoTableViewCell.userInfoProfileURLImageView.layer.masksToBounds = YES; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self updateTable]; [self updateUserInfo]; } #pragma mark - Public - (BOOL)handleIncomingLinkWithURL:(NSURL *)URL { // Parse the query portion of the incoming URL. NSDictionary *queryItems = parseURL([NSURLComponents componentsWithString:URL.absoluteString].query); // Check that all necessary query items are available. NSString *actionCode = queryItems[@"oobCode"]; NSString *mode = queryItems[@"mode"]; if (!actionCode || !mode) { return NO; } // Handle Password Reset action. if ([mode isEqualToString:kPasswordResetAction]) { [self showTextInputPromptWithMessage:@"New Password:" completionBlock:^(BOOL userPressedOK, NSString *_Nullable newPassword) { if (!userPressedOK || !newPassword.length) { [UIPasteboard generalPasteboard].string = actionCode; return; } [self showSpinner:^() { [[AppManager auth] confirmPasswordResetWithCode:actionCode newPassword:newPassword completion:^(NSError *_Nullable error) { [self hideSpinner:^{ if (error) { [self logFailure:@"Password reset in app failed" error:error]; [self showMessagePrompt:error.localizedDescription]; return; } [self logSuccess:@"Password reset in app succeeded."]; [self showMessagePrompt:@"Password reset in app succeeded."]; }]; }]; }]; }]; return YES; } if ([mode isEqualToString:kVerifyEmailAction]) { [self showMessagePromptWithTitle:@"Tap OK to verify email" message:actionCode showCancelButton:YES completion:^(BOOL userPressedOK, NSString *_Nullable userInput) { if (!userPressedOK) { return; } [self showSpinner:^() { [[AppManager auth] applyActionCode:actionCode completion:^(NSError *_Nullable error) { [self hideSpinner:^{ if (error) { [self logFailure:@"Verify email in app failed" error:error]; [self showMessagePrompt:error.localizedDescription]; return; } [self logSuccess:@"Verify email in app succeeded."]; [self showMessagePrompt:@"Verify email in app succeeded."]; }]; }]; }]; }]; return YES; } return NO; } static NSDictionary *parseURL(NSString *urlString) { NSString *linkURL = [NSURLComponents componentsWithString:urlString].query; NSArray *URLComponents = [linkURL componentsSeparatedByString:@"&"]; NSMutableDictionary *queryItems = [[NSMutableDictionary alloc] initWithCapacity:URLComponents.count]; for (NSString *component in URLComponents) { NSRange equalRange = [component rangeOfString:@"="]; if (equalRange.location != NSNotFound) { NSString *queryItemKey = [[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding]; NSString *queryItemValue = [[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding]; if (queryItemKey && queryItemValue) { queryItems[queryItemKey] = queryItemValue; } } } return queryItems; } - (void)updateTable { __weak typeof(self) weakSelf = self; _tableViewManager.contents = [StaticContentTableViewContent contentWithSections:@[ // User Defaults [StaticContentTableViewSection sectionWithTitle:kSectionTitleUserDetails cells:@[ [StaticContentTableViewCell cellWithCustomCell:_userInfoTableViewCell action:^{ [weakSelf presentUserInfo]; }], [StaticContentTableViewCell cellWithCustomCell:_userToUseCell], [StaticContentTableViewCell cellWithCustomCell:_userInMemoryInfoTableViewCell action:^{ [weakSelf presentUserInMemoryInfo]; }], ]], // Settings [StaticContentTableViewSection sectionWithTitle:kSectionTitleSettings cells:@[ [StaticContentTableViewCell cellWithTitle:kSectionTitleSettings action:^{ [weakSelf presentSettings]; }], [StaticContentTableViewCell cellWithTitle:kNewOrExistingUserToggleTitle value:_isNewUserToggleOn ? @"Enabled" : @"Disabled" action:^{ _isNewUserToggleOn = !_isNewUserToggleOn; [self updateTable]; }], [StaticContentTableViewCell cellWithTitle:kSwitchToInMemoryUserTitle action:^{ [weakSelf updateToSavedUser]; }], ]], // Auth [weakSelf authSection], // Email Auth [weakSelf emailAuthSection], // Phone Auth [weakSelf phoneAuthSection], // Google Auth [weakSelf googleAuthSection], // Facebook Auth [weakSelf facebookAuthSection], // OAuth [weakSelf oAuthSection], // Custom Auth [weakSelf customAuthSection], // Game Center Auth [weakSelf gameCenterAuthSection], // User [weakSelf userSection], // App [weakSelf appSection], // OOB [weakSelf oobSection], // Auto Tests [weakSelf autoTestsSection], ]]; } #pragma mark - Internal - (FIRUser *)user { return _useUserInMemory ? _userInMemory : [AppManager auth].currentUser; } - (void)signInWithProvider:(nonnull id)provider callback:(void(^)(void))callback { if (!provider) { [self logFailedTest:@"A valid auth provider was not provided to the signInWithProvider."]; return; } [provider getAuthCredentialWithPresentingViewController:self callback:^(FIRAuthCredential *credential, NSError *error) { if (!credential) { [self logFailedTest:@"The test needs a valid credential to continue."]; return; } [[AppManager auth] signInWithCredential:credential completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) { if (error) { [self logFailure:@"sign-in with provider failed" error:error]; [self logFailedTest:@"Sign-in should succeed"]; return; } else { [self logSuccess:@"sign-in with provider succeeded."]; callback(); } }]; }]; } - (FIRActionCodeSettings *)actionCodeSettings { FIRActionCodeSettings *actionCodeSettings = [[FIRActionCodeSettings alloc] init]; actionCodeSettings.URL = self.actionCodeContinueURL; actionCodeSettings.handleCodeInApp = self.actionCodeRequestType == ActionCodeRequestTypeInApp; return actionCodeSettings; } - (void)reauthenticate:(id)authProvider retrieveData:(BOOL)retrieveData { FIRUser *user = [self user]; if (!user) { NSString *provider = @"Firebase"; if ([authProvider isKindOfClass:[GoogleAuthProvider class]]) { provider = @"Google"; } else if ([authProvider isKindOfClass:[FacebookAuthProvider class]]) { provider = @"Facebook"; } NSString *title = @"Missing User"; NSString *message = [NSString stringWithFormat:@"There is no signed-in %@ user.", provider]; [self showMessagePromptWithTitle:title message:message showCancelButton:NO completion:nil]; return; } [authProvider getAuthCredentialWithPresentingViewController:self callback:^(FIRAuthCredential *credential, NSError *error) { if (credential) { FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { if (error) { [self logFailure:@"reauthenticate operation failed" error:error]; } else { [self logSuccess:@"reauthenticate operation succeeded."]; } if (authResult.additionalUserInfo) { [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]]; } [self showTypicalUIForUserUpdateResultsWithTitle:@"Reauthenticate" error:error]; }; [user reauthenticateWithCredential:credential completion:completion]; } }]; } - (void)signinWithProvider:(id)authProvider retrieveData:(BOOL)retrieveData { FIRAuth *auth = [AppManager auth]; if (!auth) { return; } [authProvider getAuthCredentialWithPresentingViewController:self callback:^(FIRAuthCredential *credential, NSError *error) { if (credential) { FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { if (error) { [self logFailure:@"sign-in with provider failed" error:error]; } else { [self logSuccess:@"sign-in with provider succeeded."]; } if (authResult.additionalUserInfo) { [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]]; if (_isNewUserToggleOn) { NSString *newUserString = authResult.additionalUserInfo.isNewUser ? @"New user" : @"Existing user"; [self showMessagePromptWithTitle:@"New or Existing" message:newUserString showCancelButton:NO completion:nil]; } } [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In" error:error]; }; [auth signInWithCredential:credential completion:completion]; } }]; } - (void)linkWithAuthProvider:(id)authProvider retrieveData:(BOOL)retrieveData { FIRUser *user = [self user]; if (!user) { return; } [authProvider getAuthCredentialWithPresentingViewController:self callback:^(FIRAuthCredential *credential, NSError *error) { if (credential) { FIRAuthDataResultCallback completion = ^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) { if (error) { [self logFailure:@"link auth provider failed" error:error]; } else { [self logSuccess:@"link auth provider succeeded."]; } if (authResult.additionalUserInfo) { [self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]]; } if (retrieveData) { [self showUIForAuthDataResultWithResult:authResult error:error]; } else { [self showTypicalUIForUserUpdateResultsWithTitle:@"Link Account" error:error]; } }; [user linkWithCredential:credential completion:completion]; } }]; } - (void)unlinkFromProvider:(NSString *)provider completion:(nullable TestAutomationCallback)completion { [[self user] unlinkFromProvider:provider completion:^(FIRUser *_Nullable user, NSError *_Nullable error) { if (error) { [self logFailure:@"unlink auth provider failed" error:error]; if (completion) { completion(error); } return; } [self logSuccess:@"unlink auth provider succeeded."]; if (completion) { completion(nil); } [self showTypicalUIForUserUpdateResultsWithTitle:@"Unlink from Provider" error:error]; }]; } - (void)updateToSavedUser { if(![AppManager auth].currentUser) { NSLog(@"You must be signed in to perform this action"); return; } if (!_userInMemory) { [self showMessagePrompt:[NSString stringWithFormat:@"You need an in-memory user to perform this" "action, use the M+ button to save a user to memory.", nil]]; return; } [[AppManager auth] updateCurrentUser:_userInMemory completion:^(NSError *_Nullable error) { if (error) { [self showMessagePrompt: [NSString stringWithFormat:@"An error Occurred: %@", error.localizedDescription]]; return; } }]; } #pragma mark - Private - (void)presentSettings { SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:NSStringFromClass([SettingsViewController class]) bundle:nil]; [self showViewController:settingsViewController sender:self]; } - (void)presentUserInfo { UserInfoViewController *userInfoViewController = [[UserInfoViewController alloc] initWithUser:[AppManager auth].currentUser]; [self showViewController:userInfoViewController sender:self]; } - (void)presentUserInMemoryInfo { UserInfoViewController *userInfoViewController = [[UserInfoViewController alloc] initWithUser:_userInMemory]; [self showViewController:userInfoViewController sender:self]; } - (NSString *)stringWithAdditionalUserInfo:(nullable FIRAdditionalUserInfo *)additionalUserInfo { if (!additionalUserInfo) { return @"(no additional user info)"; } NSString *newUserString = additionalUserInfo.isNewUser ? @"new user" : @"existing user"; return [NSString stringWithFormat:@"%@: %@", newUserString, additionalUserInfo.profile]; } - (void)showTypicalUIForUserUpdateResultsWithTitle:(NSString *)resultsTitle error:(NSError * _Nullable)error { if (error) { NSString *message = [NSString stringWithFormat:@"%@ (%ld)\n%@", error.domain, (long)error.code, error.localizedDescription]; if (error.code == FIRAuthErrorCodeAccountExistsWithDifferentCredential) { NSString *errorEmail = error.userInfo[FIRAuthErrorUserInfoEmailKey]; resultsTitle = [NSString stringWithFormat:@"Existing email : %@", errorEmail]; } [self showMessagePromptWithTitle:resultsTitle message:message showCancelButton:NO completion:nil]; return; } [self updateUserInfo]; } - (void)showUIForAuthDataResultWithResult:(FIRAuthDataResult *)result error:(NSError * _Nullable)error { NSString *errorMessage = [NSString stringWithFormat:@"%@ (%ld)\n%@", error.domain ?: @"", (long)error.code, error.localizedDescription ?: @""]; [self showMessagePromptWithTitle:@"Error" message:errorMessage showCancelButton:NO completion:^(BOOL userPressedOK, NSString *_Nullable userInput) { [self showMessagePromptWithTitle:@"Profile Info" message:[self stringWithAdditionalUserInfo:result.additionalUserInfo] showCancelButton:NO completion:nil]; [self updateUserInfo]; }]; } - (void)updateUserInfo { [_userInfoTableViewCell updateContentsWithUser:[AppManager auth].currentUser]; [_userInMemoryInfoTableViewCell updateContentsWithUser:_userInMemory]; } - (void)authStateChangedForAuth:(NSNotification *)notification { [self updateUserInfo]; if (notification) { [self log:[NSString stringWithFormat: @"received FIRAuthStateDidChange notification on user '%@'.", ((FIRAuth *)notification.object).currentUser.uid]]; } } - (void)log:(NSString *)string { dispatch_async(dispatch_get_main_queue(), ^{ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; NSString *date = [dateFormatter stringFromDate:[NSDate date]]; if (!_consoleString) { _consoleString = [NSMutableString string]; } [_consoleString appendString:[NSString stringWithFormat:@"%@ %@\n", date, string]]; _consoleTextView.text = _consoleString; CGRect targetRect = CGRectMake(0, _consoleTextView.contentSize.height - 1, 1, 1); [_consoleTextView scrollRectToVisible:targetRect animated:YES]; }); } - (void)logSuccess:(NSString *)string { [self log:[NSString stringWithFormat:@"SUCCESS: %@", string]]; } - (void)logFailure:(NSString *)string error:(NSError * _Nullable) error { NSString *message = [NSString stringWithFormat:@"FAILURE: %@ Error Description: %@.", string, error.description]; [self log:message]; } - (void)logFailedTest:( NSString *_Nonnull )reason { [self log:[NSString stringWithFormat:@"FAILIURE: TEST FAILED - %@", reason]]; } #pragma mark - IBAction - (IBAction)userToUseDidChange:(UISegmentedControl *)sender { _useUserInMemory = (sender.selectedSegmentIndex == 1); } - (IBAction)memoryPlus { _userInMemory = [AppManager auth].currentUser; [self updateUserInfo]; } - (IBAction)memoryClear { _userInMemory = nil; [self updateUserInfo]; } - (IBAction)clearConsole:(id)sender { [_consoleString appendString:@"\n\n"]; _consoleTextView.text = @""; } - (IBAction)copyConsole:(id)sender { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; pasteboard.string = _consoleString ?: @""; } @end NS_ASSUME_NONNULL_END