aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Morgan Chen <morganchen12@gmail.com>2018-06-20 10:14:10 -0700
committerGravatar GitHub <noreply@github.com>2018-06-20 10:14:10 -0700
commita4b7a244e7bc2778e7178f7855d58bb93f0deaf8 (patch)
tree50da184fddea28452b757d5cbb0b49ff1b4e0162
parent468cb9151791a313d44e382976e6aa22fec45db4 (diff)
parent5fa4f5aac467db35654c0e3cf6920738cb4b8753 (diff)
Merge pull request #1383 from morganchen12/auth2
Promote users to non-anonymous status even when linking account w/o pw
-rw-r--r--Example/Auth/Tests/FIRUserTests.m172
-rw-r--r--Firebase/Auth/CHANGELOG.md4
-rw-r--r--Firebase/Auth/Source/FIRUser.m22
3 files changed, 191 insertions, 7 deletions
diff --git a/Example/Auth/Tests/FIRUserTests.m b/Example/Auth/Tests/FIRUserTests.m
index 7a6c165..8bb6786 100644
--- a/Example/Auth/Tests/FIRUserTests.m
+++ b/Example/Auth/Tests/FIRUserTests.m
@@ -26,9 +26,11 @@
#import "FIRAuthGlobalWorkQueue.h"
#import "FIRAuthOperationType.h"
#import "FIRAuthTokenResult.h"
+#import "FIREmailLinkSignInResponse.m"
#import "FIRSecureTokenService.h"
#import "FIRSecureTokenRequest.h"
#import "FIRSecureTokenResponse.h"
+#import "FIRSignUpNewUserResponse.h"
#import "FIRGetAccountInfoRequest.h"
#import "FIRGetAccountInfoResponse.h"
#import "FIRSetAccountInfoRequest.h"
@@ -583,6 +585,57 @@ static const NSTimeInterval kExpectationTimeout = 2;
OCMVerifyAll(_mockBackend);
}
+/** @fn testUpdateEmailWithAuthLinkAccountSuccess
+ @brief Tests a successful @c updateEmail:completion: call updates provider info.
+ */
+- (void)testUpdateEmailWithAuthLinkAccountSuccess {
+ id (^mockUserInfoWithDisplayName)(NSString *) = ^(NSString *displayName) {
+ id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
+ OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
+ OCMStub([mockGetAccountInfoResponseUser email]).andReturn(kEmail);
+ OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(displayName);
+ OCMStub([mockGetAccountInfoResponseUser passwordHash]).andReturn(kPasswordHash);
+ return mockGetAccountInfoResponseUser;
+ };
+ XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+ id userInfoResponse = mockUserInfoWithDisplayName(kGoogleDisplayName);
+ [self signInWithEmailLinkWithMockUserInfoResponse:userInfoResponse
+ completion:^(FIRUser *user) {
+ // Pretend that the display name on the server has been changed since last request.
+ [self
+ expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoWithDisplayName(kNewDisplayName)];
+ OCMExpect([_mockBackend setAccountInfo:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSetAccountInfoRequest *_Nullable request,
+ FIRSetAccountInfoResponseCallback callback) {
+ XCTAssertEqualObjects(request.APIKey, kAPIKey);
+ XCTAssertEqualObjects(request.accessToken, kAccessToken);
+ XCTAssertEqualObjects(request.email, kNewEmail);
+ XCTAssertNil(request.localID);
+ XCTAssertNil(request.displayName);
+ XCTAssertNil(request.photoURL);
+ XCTAssertNil(request.password);
+ XCTAssertNil(request.providers);
+ XCTAssertNil(request.deleteAttributes);
+ XCTAssertNil(request.deleteProviders);
+ dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+ id mockSetAccountInfoResponse = OCMClassMock([FIRSetAccountInfoResponse class]);
+ OCMStub([mockSetAccountInfoResponse email]).andReturn(kNewEmail);
+ OCMStub([mockSetAccountInfoResponse displayName]).andReturn(kNewDisplayName);
+ callback(mockSetAccountInfoResponse, nil);
+ });
+ });
+ [user updateEmail:kNewEmail completion:^(NSError *_Nullable error) {
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(user.email, kNewEmail);
+ XCTAssertEqualObjects(user.displayName, kNewDisplayName);
+ XCTAssertFalse(user.isAnonymous);
+ [expectation fulfill];
+ }];
+ }];
+ [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+ OCMVerifyAll(_mockBackend);
+}
+
/** @fn testUpdateEmailFailure
@brief Tests the flow of a failed @c updateEmail:completion: call.
*/
@@ -1572,6 +1625,64 @@ static const NSTimeInterval kExpectationTimeout = 2;
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 linkAndRetrieveDataWithCredential: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.
@@ -2222,6 +2333,34 @@ static const NSTimeInterval kExpectationTimeout = 2;
OCMVerifyAll(_mockBackend);
}
+/** @fn signInAnonymouslyWithMockGetAccountInfoResponse:completion:
+ @brief Signs in with an anonymous account with mocked backend end calls.
+ @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object.
+ @param completion The completion block that takes the newly signed-in user as the only
+ parameter.
+ */
+- (void)signInAnonymouslyWithMockGetAccountInfoResponse:(id)mockUserInfoResponse
+ completion:(void (^)(FIRUser *user))completion {
+ OCMExpect([_mockBackend signUpNewUser:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIRSignUpNewUserRequest *_Nullable request,
+ FIRSignupNewUserCallback callback) {
+ id mockSignUpResponse = OCMClassMock([FIRSignUpNewUserResponse class]);
+ OCMStub([mockSignUpResponse IDToken]).andReturn(kAccessToken);
+ OCMStub([mockSignUpResponse approximateExpirationDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
+ OCMStub([mockSignUpResponse refreshToken]).andReturn(kRefreshToken);
+ callback(mockSignUpResponse, nil);
+ });
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInAnonymouslyWithCompletion:^(FIRAuthDataResult *_Nullable result,
+ NSError *_Nullable error) {
+ XCTAssertNotNil(result.user);
+ XCTAssertNil(error);
+ completion(result.user);
+ }];
+}
+
/** @fn signInWithEmailPasswordWithMockGetAccountInfoResponse:completion:
@brief Signs in with an email and password account with mocked backend end calls.
@param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object.
@@ -2229,7 +2368,7 @@ static const NSTimeInterval kExpectationTimeout = 2;
parameter.
*/
- (void)signInWithEmailPasswordWithMockUserInfoResponse:(id)mockUserInfoResponse
- completion:(void (^)(FIRUser *user))completion {
+ completion:(void (^)(FIRUser *user))completion {
OCMExpect([_mockBackend verifyPassword:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(^(FIRVerifyPasswordRequest *_Nullable request,
FIRVerifyPasswordResponseCallback callback) {
@@ -2239,7 +2378,7 @@ static const NSTimeInterval kExpectationTimeout = 2;
OCMStub([mockVeriyPasswordResponse approximateExpirationDate])
.andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
OCMStub([mockVeriyPasswordResponse refreshToken]).andReturn(kRefreshToken);
- callback(mockVeriyPasswordResponse, nil);
+ callback(mockVeriyPasswordResponse, nil);
});
});
[self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse];
@@ -2253,6 +2392,35 @@ static const NSTimeInterval kExpectationTimeout = 2;
}];
}
+/** @fn signInWithEmailLinkWithMockGetAccountInfoResponse:completion:
+ @brief Signs in with an email link auth account with mocked backend end calls.
+ @param mockUserInfoResponse A mocked FIRGetAccountInfoResponseUser object.
+ @param completion The completion block that takes the newly signed-in user as the only
+ parameter.
+ */
+- (void)signInWithEmailLinkWithMockUserInfoResponse:(id)mockUserInfoResponse
+ completion:(void (^)(FIRUser *user))completion {
+ OCMExpect([_mockBackend emailLinkSignin:[OCMArg any] callback:[OCMArg any]])
+ .andCallBlock2(^(FIREmailLinkSignInRequest *_Nullable request,
+ FIREmailLinkSigninResponseCallback callback) {
+ id mockVerifyLinkResponse = OCMClassMock([FIREmailLinkSignInResponse class]);
+ OCMStub([mockVerifyLinkResponse IDToken]).andReturn(kAccessToken);
+ OCMStub([mockVerifyLinkResponse approximateExpirationDate])
+ .andReturn([NSDate dateWithTimeIntervalSinceNow:kAccessTokenTimeToLive]);
+ OCMStub([mockVerifyLinkResponse refreshToken]).andReturn(kRefreshToken);
+ callback(mockVerifyLinkResponse, nil);
+ });
+ [self expectGetAccountInfoWithMockUserInfoResponse:mockUserInfoResponse];
+ [[FIRAuth auth] signOut:NULL];
+ [[FIRAuth auth] signInWithEmail:kEmail
+ link:@"https://www.google.com?oobCode=aCode&mode=signIn"
+ completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) {
+ XCTAssertNotNil(result.user);
+ XCTAssertNil(error);
+ completion(result.user);
+ }];
+}
+
/** @fn expectGetAccountInfoWithMockUserInfoResponse:
@brief Expects a GetAccountInfo request on the mock backend and calls back with provided
fake account data.
diff --git a/Firebase/Auth/CHANGELOG.md b/Firebase/Auth/CHANGELOG.md
index 292f8bd..2669fc0 100644
--- a/Firebase/Auth/CHANGELOG.md
+++ b/Firebase/Auth/CHANGELOG.md
@@ -1,3 +1,7 @@
+# v5.0.2
+- Fix an issue where anonymous accounts weren't correctly promoted to
+ non-anonymous when linked with passwordless email auth accounts.
+
# v5.0.1
- Restore 4.x level of support for extensions (#1357).
diff --git a/Firebase/Auth/Source/FIRUser.m b/Firebase/Auth/Source/FIRUser.m
index 04aa861..3f5bf35 100644
--- a/Firebase/Auth/Source/FIRUser.m
+++ b/Firebase/Auth/Source/FIRUser.m
@@ -541,7 +541,7 @@ static void callInMainThreadWithAuthDataResultAndError(
- (void)updateEmail:(nullable NSString *)email
password:(nullable NSString *)password
callback:(nonnull FIRUserProfileChangeCallback)callback {
- if (password && ![password length]){
+ if (password && ![password length]) {
callback([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]);
return;
}
@@ -561,11 +561,9 @@ static void callInMainThreadWithAuthDataResultAndError(
return;
}
if (email) {
- self->_email = email;
+ self->_email = [email copy];
}
- if (self->_email && password) {
- self->_anonymous = NO;
- self->_hasEmailPasswordCredential = YES;
+ if (self->_email) {
if (!hadEmailPasswordCredential) {
// The list of providers need to be updated for the newly added email-password provider.
[self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
@@ -586,6 +584,20 @@ static void callInMainThreadWithAuthDataResultAndError(
callback(error);
return;
}
+ for (FIRGetAccountInfoResponseUser *userAccountInfo in response.users) {
+ // Set the account to non-anonymous if there are any providers, even if
+ // they're not email/password ones.
+ if (userAccountInfo.providerUserInfo.count > 0) {
+ self->_anonymous = NO;
+ }
+ for (FIRGetAccountInfoResponseProviderUserInfo *providerUserInfo in
+ userAccountInfo.providerUserInfo) {
+ if ([providerUserInfo.providerID isEqualToString:FIREmailAuthProviderID]) {
+ self->_hasEmailPasswordCredential = YES;
+ break;
+ }
+ }
+ }
[self updateWithGetAccountInfoResponse:response];
if (![self updateKeychain:&error]) {
callback(error);