From 2314f93bcac8952c86aabf3a2fec61125cc34bff Mon Sep 17 00:00:00 2001 From: Chuan Ren Date: Fri, 17 May 2019 16:45:46 -0700 Subject: [PATCH 1/2] Fix link with email link bug --- .../Auth/Sample/MainViewController+Email.h | 2 - .../Auth/Sample/MainViewController+Email.m | 80 ++++++++++++++----- Firebase/Auth/Source/Auth/FIRAuth.m | 37 ++------- Firebase/Auth/Source/User/FIRUser.m | 73 ++++++++++++++--- .../Auth/Source/Utilities/FIRAuthWebUtils.h | 7 ++ .../Auth/Source/Utilities/FIRAuthWebUtils.m | 23 ++++++ 6 files changed, 159 insertions(+), 63 deletions(-) diff --git a/Example/Auth/Sample/MainViewController+Email.h b/Example/Auth/Sample/MainViewController+Email.h index 1a9193fc222..08858930178 100644 --- a/Example/Auth/Sample/MainViewController+Email.h +++ b/Example/Auth/Sample/MainViewController+Email.h @@ -23,8 +23,6 @@ NS_ASSUME_NONNULL_BEGIN -typedef void (^ShowEmailPasswordDialogCompletion)(FIRAuthCredential *credential); - @interface MainViewController (Email) - (StaticContentTableViewSection *)emailAuthSection; diff --git a/Example/Auth/Sample/MainViewController+Email.m b/Example/Auth/Sample/MainViewController+Email.m index 27b8d14700b..a69ac88834b 100644 --- a/Example/Auth/Sample/MainViewController+Email.m +++ b/Example/Auth/Sample/MainViewController+Email.m @@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN +typedef void (^ShowEmailDialogCompletion)(FIRAuthCredential *credential); + @implementation MainViewController (Email) - (StaticContentTableViewSection *)emailAuthSection { @@ -36,10 +38,12 @@ - (StaticContentTableViewSection *)emailAuthSection { action:^{ [weakSelf unlinkFromProvider:FIREmailAuthProviderID completion:nil]; }], [StaticContentTableViewCell cellWithTitle:@"Reauthenticate Email Password" action:^{ [weakSelf reauthenticateEmailPassword]; }], - [StaticContentTableViewCell cellWithTitle:@"Sign in with Email Link" - action:^{ [weakSelf sendEmailSignInLink]; }], [StaticContentTableViewCell cellWithTitle:@"Send Email Sign in Link" + action:^{ [weakSelf sendEmailSignInLink]; }], + [StaticContentTableViewCell cellWithTitle:@"Sign in with Email Link" action:^{ [weakSelf signInWithEmailLink]; }], + [StaticContentTableViewCell cellWithTitle:@"Link with Email Link" + action:^{ [weakSelf linkWithEmailLink]; }], ]]; } @@ -173,24 +177,6 @@ - (void)reauthenticateEmailPassword { }]; } -- (void)showEmailPasswordDialogWithCompletion:(ShowEmailPasswordDialogCompletion)completion { - [self showTextInputPromptWithMessage:@"Email Address:" - completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) { - if (!userPressedOK || !email.length) { - return; - } - [self showTextInputPromptWithMessage:@"Password:" - completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) { - if (!userPressedOK || !password.length) { - return; - } - FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:email - password:password]; - completion(credential); - }]; - }]; -} - - (void)signInWithEmailLink { [self showTextInputPromptWithMessage:@"Email Address:" keyboardType:UIKeyboardTypeEmailAddress @@ -254,6 +240,60 @@ - (void)sendEmailSignInLink { }]; } +- (void)linkWithEmailLink { + [self showEmailLinkDialogWithCompletion:^(FIRAuthCredential *credential) { + [self showSpinner:^{ + [[self user] linkWithCredential:credential + completion:^(FIRAuthDataResult *result, NSError *error) { + if (error) { + [self logFailure:@"link Email Link failed." error:error]; + } else { + [self logSuccess:@"link Email Link succeeded."]; + } + [self hideSpinner:^{ + [self showTypicalUIForUserUpdateResultsWithTitle:@"Link with Email Link" error:error]; + }]; + }]; + }]; + }]; +} + +- (void)showEmailPasswordDialogWithCompletion:(ShowEmailDialogCompletion)completion { + [self showTextInputPromptWithMessage:@"Email Address:" + completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) { + if (!userPressedOK || !email.length) { + return; + } + [self showTextInputPromptWithMessage:@"Password:" + completionBlock:^(BOOL userPressedOK, NSString *_Nullable password) { + if (!userPressedOK || !password.length) { + return; + } + FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:email + password:password]; + completion(credential); + }]; + }]; +} + +- (void)showEmailLinkDialogWithCompletion:(ShowEmailDialogCompletion)completion { + [self showTextInputPromptWithMessage:@"Email Address:" + completionBlock:^(BOOL userPressedOK, NSString *_Nullable email) { + if (!userPressedOK || !email.length) { + return; + } + [self showTextInputPromptWithMessage:@"Link:" + completionBlock:^(BOOL userPressedOK, NSString *_Nullable link) { + if (!userPressedOK || !link.length) { + return; + } + FIRAuthCredential *credential = [FIREmailAuthProvider credentialWithEmail:email + link:link]; + completion(credential); + }]; + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/Auth/FIRAuth.m b/Firebase/Auth/Source/Auth/FIRAuth.m index ee4435830ef..d0e68716dcf 100644 --- a/Firebase/Auth/Source/Auth/FIRAuth.m +++ b/Firebase/Auth/Source/Auth/FIRAuth.m @@ -43,6 +43,7 @@ #import "FIRAuthOperationType.h" #import "FIRAuthSettings.h" #import "FIRAuthStoredUserManager.h" +#import "FIRAuthWebUtils.h" #import "FIRUser_Internal.h" #import "FirebaseAuth.h" #import "FIRAuthBackend.h" @@ -678,10 +679,10 @@ - (void)internalSignInAndRetrieveDataWithEmail:(nonnull NSString *)email kInvalidEmailSignInLinkExceptionMessage]; return; } - NSDictionary *queryItems = FIRAuthParseURL(link); + NSDictionary *queryItems = [FIRAuthWebUtils parseURL:link]; if (![queryItems count]) { NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link]; - queryItems = FIRAuthParseURL(urlComponents.query); + queryItems = [FIRAuthWebUtils parseURL:urlComponents.query]; } NSString *actionCode = queryItems[@"oobCode"]; @@ -1198,13 +1199,13 @@ - (BOOL)isSignInWithEmailLink:(NSString *)link { if (link.length == 0) { return NO; } - NSDictionary *queryItems = FIRAuthParseURL(link); + NSDictionary *queryItems = [FIRAuthWebUtils parseURL:link]; if (![queryItems count]) { NSURLComponents *urlComponents = [NSURLComponents componentsWithString:link]; if (!urlComponents.query) { return NO; } - queryItems = FIRAuthParseURL(urlComponents.query); + queryItems = [FIRAuthWebUtils parseURL:urlComponents.query]; } if (![queryItems count]) { @@ -1220,34 +1221,6 @@ - (BOOL)isSignInWithEmailLink:(NSString *)link { return NO; } -/** @fn FIRAuthParseURL:NSString - @brief Parses an incoming URL into all available query items. - @param urlString The url to be parsed. - @return A dictionary of available query items in the target URL. - */ -static NSDictionary *FIRAuthParseURL(NSString *urlString) { - NSString *linkURL = [NSURLComponents componentsWithString:urlString].query; - if (!linkURL) { - return @{}; - } - 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; -} - - (FIRAuthStateDidChangeListenerHandle)addAuthStateDidChangeListener: (FIRAuthStateDidChangeListenerBlock)listener { __block BOOL firstInvocation = YES; diff --git a/Firebase/Auth/Source/User/FIRUser.m b/Firebase/Auth/Source/User/FIRUser.m index 553613b6ad4..b5e7cb2ce17 100644 --- a/Firebase/Auth/Source/User/FIRUser.m +++ b/Firebase/Auth/Source/User/FIRUser.m @@ -30,10 +30,12 @@ #import "FIRAuthBackend.h" #import "FIRAuthRequestConfiguration.h" #import "FIRAuthTokenResult_Internal.h" +#import "FIRAuthWebUtils.h" #import "FIRDeleteAccountRequest.h" #import "FIRDeleteAccountResponse.h" #import "FIREmailAuthProvider.h" #import "FIREmailPasswordAuthCredential.h" +#import "FIREmailLinkSignInRequest.h" #import "FIRGameCenterAuthCredential.h" #import "FIRGetAccountInfoRequest.h" #import "FIRGetAccountInfoResponse.h" @@ -1026,15 +1028,68 @@ - (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential } FIREmailPasswordAuthCredential *emailPasswordCredential = (FIREmailPasswordAuthCredential *)credential; - [self updateEmail:emailPasswordCredential.email - password:emailPasswordCredential.password - callback:^(NSError *error) { - if (error) { - callInMainThreadWithAuthDataResultAndError(completion, nil, error); - } else { - callInMainThreadWithAuthDataResultAndError(completion, result, nil); - } - }]; + if (emailPasswordCredential.password) { + [self updateEmail:emailPasswordCredential.email + password:emailPasswordCredential.password + callback:^(NSError *error) { + if (error) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + } else { + callInMainThreadWithAuthDataResultAndError(completion, result, nil); + } + }]; + } else { + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + NSDictionary *queryItems = [FIRAuthWebUtils parseURL:emailPasswordCredential.link]; + if (![queryItems count]) { + NSURLComponents *urlComponents = [NSURLComponents componentsWithString:emailPasswordCredential.link]; + queryItems = [FIRAuthWebUtils parseURL:urlComponents.query]; + } + NSString *actionCode = queryItems[@"oobCode"]; + FIRAuthRequestConfiguration *requestConfiguration = self.auth.requestConfiguration; + FIREmailLinkSignInRequest *request = + [[FIREmailLinkSignInRequest alloc] initWithEmail:emailPasswordCredential.email + oobCode:actionCode + requestConfiguration:requestConfiguration]; + request.IDToken = accessToken; + [FIRAuthBackend emailLinkSignin:request + callback:^(FIREmailLinkSignInResponse *_Nullable response, + NSError *_Nullable error) { + if (error){ + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + } else { + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + + FIRGetAccountInfoRequest *getAccountInfoRequest = + [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken + requestConfiguration:requestConfiguration]; + [FIRAuthBackend getAccountInfo:getAccountInfoRequest + callback:^(FIRGetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + self.anonymous = NO; + [self updateWithGetAccountInfoResponse:response]; + if (![self updateKeychain:&error]) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + callInMainThreadWithAuthDataResultAndError(completion, result, nil); + }]; + }]; + } + }]; + }]; + } return; } diff --git a/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h b/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h index 52bf7f4cc7b..ebf464da349 100644 --- a/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h +++ b/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.h @@ -89,6 +89,13 @@ typedef void (^FIRFetchAuthDomainCallback)(NSString *_Nullable authDomain, */ + (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument; +/** @fn parseURL: + @brief Parses an incoming URL into all available query items. + @param urlString The url to be parsed. + @return A dictionary of available query items in the target URL. + */ ++ (NSDictionary *)parseURL:(NSString *)urlString; + @end NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m b/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m index be96b97f860..78d1df4cf1d 100644 --- a/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m +++ b/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m @@ -169,6 +169,29 @@ + (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument { return [resultString stringByRemovingPercentEncoding]; } ++ (NSDictionary *)parseURL:(NSString *)urlString { + NSString *linkURL = [NSURLComponents componentsWithString:urlString].query; + if (!linkURL) { + return @{}; + } + 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; +} + @end NS_ASSUME_NONNULL_END From a5ef8f2b86001f49106d746c9734d7d7d20f06ba Mon Sep 17 00:00:00 2001 From: Chuan Ren Date: Fri, 17 May 2019 18:15:26 -0700 Subject: [PATCH 2/2] =?UTF-8?q?Upgraded=20user=20won=E2=80=99t=20be=20anon?= =?UTF-8?q?ymous?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Example/Auth/Tests/FIRUserTests.m | 58 ------------------------------- 1 file changed, 58 deletions(-) diff --git a/Example/Auth/Tests/FIRUserTests.m b/Example/Auth/Tests/FIRUserTests.m index 596e6ee0bf5..361ad3a41f7 100644 --- a/Example/Auth/Tests/FIRUserTests.m +++ b/Example/Auth/Tests/FIRUserTests.m @@ -1627,64 +1627,6 @@ - (void)testlinkAndRetrieveDataErrorAutoSignOut { OCMVerifyAll(_mockBackend); } -/** @fn testLinkingAnonymousAccountsUpdatesIsAnonymous - @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion: - invocation for email credential. - */ -- (void)testLinkingAnonymousAccountsUpdatesIsAnonymous { - FIRAuthCredential *linkEmailCredential = - [FIREmailAuthProvider credentialWithEmail:kEmail - link:@"https://google.com?oobCode=aCode&mode=signIn"]; - - id (^mockUserInfoWithDisplayName)(NSString *, BOOL) = ^(NSString *displayName, - BOOL hasProviders) { - NSArray *providers = hasProviders ? @[ @{ - @"providerId": FIREmailAuthProviderID, - @"email": kEmail - } ] : @[]; - FIRGetAccountInfoResponseUser *responseUser = - [[FIRGetAccountInfoResponseUser alloc] initWithDictionary:@{ - @"providerUserInfo": providers, - @"localId": kLocalID, - @"displayName": displayName, - @"email": kEmail - }]; - return responseUser; - }; - XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName, NO); - - [self signInAnonymouslyWithMockGetAccountInfoResponse:userInfoResponse - completion:^(FIRUser *user) { - // Pretend that the display name and providers on the server have been updated. - // Get account info is expected to be invoked twice. - id updatedMockUser = mockUserInfoWithDisplayName(kNewDisplayName, YES); - [self expectGetAccountInfoWithMockUserInfoResponse:updatedMockUser]; - [self expectGetAccountInfoWithMockUserInfoResponse:updatedMockUser]; - OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]]) - .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request, - FIRSetAccountInfoResponseCallback callback) { - id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]); - OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail); - OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName); - callback(mockSetAccountInfoResponse, nil); - }); - XCTAssertTrue(user.isAnonymous); - - [user linkWithCredential:linkEmailCredential - completion:^(FIRAuthDataResult *_Nullable linkAuthResult, - NSError *_Nullable error) { - XCTAssertTrue([NSThread isMainThread]); - XCTAssertNil(error); - XCTAssertEqualObjects(user.email, kEmail); - XCTAssertFalse(user.isAnonymous); - [expectation fulfill]; - }]; - }]; - [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; - OCMVerifyAll(_mockBackend); -} - /** @fn testlinkEmailAndRetrieveDataSuccess @brief Tests the flow of a successful @c linkAndRetrieveDataWithCredential:completion: invocation for email credential.