Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sign in with Apple Display Name API and unit test #10068

Merged
merged 25 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
62e4fb6
Add api addtion for SIWA and add Function for Unit test
aiwenisevan Aug 3, 2022
47fd1b5
Adding Unit test for apple signIn flow in FIROAuthProviderTests and F…
aiwenisevan Aug 4, 2022
8578f96
remove AuthSample.xcodeproj fiels and application.plist file under Sa…
aiwenisevan Aug 9, 2022
8ca23e4
Revert "remove AuthSample.xcodeproj fiels and application.plist file …
aiwenisevan Aug 9, 2022
8c53928
remove changes in AuthSample 1/5
aiwenisevan Aug 9, 2022
32723a4
remove changes in AuthSample 2/5
aiwenisevan Aug 9, 2022
ecea19e
remove changes in AuthSample 3/5
aiwenisevan Aug 9, 2022
b004ef5
remove changes in AuthSample 4/5
aiwenisevan Aug 9, 2022
e1450ea
remove changes in AuthSample 5/5
aiwenisevan Aug 9, 2022
1b890f4
overwrite with master firles to remove white space changes in the two…
aiwenisevan Aug 9, 2022
9e4c88e
Delete Application.plist file
aiwenisevan Aug 9, 2022
66f2db7
Clean extra methods in FIROAuthCredentialInternal and related files t…
aiwenisevan Aug 9, 2022
f2853ae
Add displayName in FIRVerifyAssertionRequest
aiwenisevan Aug 10, 2022
a26395d
Fix issues in comments, merge GoogleAuthprovider, fix files in tests
aiwenisevan Aug 12, 2022
2dac690
Merge branch 'master' into apiAdditionforSIWA
aiwenisevan Aug 12, 2022
f640537
fix issues
aiwenisevan Aug 12, 2022
1d8d3c8
Clang-format all files
aiwenisevan Aug 12, 2022
c3389c6
Adding comment for testSignInWithCredentialSuccess
aiwenisevan Aug 12, 2022
cfe6f4e
Adding comments for testSignInWithCredentialSuccess
aiwenisevan Aug 12, 2022
d38c34e
Merge branch 'master' into apiAdditionforSIWA
rosalyntan Feb 16, 2023
3dcc540
Updates to reflect approved API.
rosalyntan Feb 16, 2023
9c54381
Address review comments.
rosalyntan Feb 21, 2023
b7d0afe
Update unit tests.
rosalyntan Feb 21, 2023
05a9c50
Update changelog.
rosalyntan Mar 3, 2023
b062ec1
Remove JSON pretty-print and add one more unit test.
rosalyntan Mar 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ - (instancetype)initWithProviderID:(NSString *)providerID
rawNonce:(nullable NSString *)rawNonce
accessToken:(nullable NSString *)accessToken
secret:(nullable NSString *)secret
displayName:(nullable NSString *)displayName
pendingToken:(nullable NSString *)pendingToken {
self = [super initWithProvider:providerID];
if (self) {
Expand All @@ -53,6 +54,7 @@ - (instancetype)initWithProviderID:(NSString *)providerID
_accessToken = accessToken;
_pendingToken = pendingToken;
_secret = secret;
_displayName = displayName;
}
return self;
}
Expand All @@ -65,6 +67,7 @@ - (instancetype)initWithProviderID:(NSString *)providerID
rawNonce:nil
accessToken:nil
secret:nil
displayName:nil
pendingToken:nil];
if (self) {
_OAuthResponseURLString = OAuthResponseURLString;
Expand All @@ -81,6 +84,7 @@ - (nullable instancetype)initWithVerifyAssertionResponse:(FIRVerifyAssertionResp
rawNonce:nil
accessToken:response.oauthAccessToken
secret:response.oauthSecretToken
displayName:nil
pendingToken:response.pendingToken];
}
return nil;
Expand All @@ -94,6 +98,7 @@ - (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request {
request.sessionID = _sessionID;
request.providerOAuthTokenSecret = _secret;
request.pendingToken = _pendingToken;
request.displayName = _displayName;
}

#pragma mark - NSSecureCoding
Expand All @@ -108,11 +113,13 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
NSString *accessToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"accessToken"];
NSString *pendingToken = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"pendingToken"];
NSString *secret = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"secret"];
NSString *displayName = [aDecoder decodeObjectOfClass:[NSString class] forKey:@"displayName"];
self = [self initWithProviderID:self.provider
IDToken:IDToken
rawNonce:rawNonce
accessToken:accessToken
secret:secret
displayName:displayName
pendingToken:pendingToken];
return self;
}
Expand All @@ -123,6 +130,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.accessToken forKey:@"accessToken"];
[aCoder encodeObject:self.pendingToken forKey:@"pendingToken"];
[aCoder encodeObject:self.secret forKey:@"secret"];
[aCoder encodeObject:self.displayName forKey:@"displayName"];
}

@end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ NS_ASSUME_NONNULL_BEGIN
@param accessToken The access token associated with the credential being created.
@param secret The secret associated with the credential being created.
@param pendingToken The pending token associated with the credential being created.
@param displayName The displayName associated with the credential being created.
*/
- (instancetype)initWithProviderID:(NSString *)providerID
IDToken:(nullable NSString *)IDToken
rawNonce:(nullable NSString *)rawNonce
accessToken:(nullable NSString *)accessToken
secret:(nullable NSString *)secret
displayName:(nullable NSString *)displayName
pendingToken:(nullable NSString *)pendingToken NS_DESIGNATED_INITIALIZER;

/** @fn initWithProviderId:sessionID:OAuthResponseURLString:
Expand Down
18 changes: 18 additions & 0 deletions FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
rawNonce:nil
accessToken:accessToken
secret:nil
displayName:nil
pendingToken:nil];
}

Expand All @@ -98,6 +99,7 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
rawNonce:nil
accessToken:accessToken
secret:nil
displayName:nil
pendingToken:nil];
}

Expand All @@ -110,6 +112,7 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
rawNonce:rawNonce
accessToken:accessToken
secret:nil
displayName:nil
pendingToken:nil];
}

Expand All @@ -121,6 +124,21 @@ + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
rawNonce:rawNonce
accessToken:nil
secret:nil
displayName:nil
pendingToken:nil];
}

+ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
IDToken:(NSString *)IDToken
rawNonce:(nullable NSString *)rawNonce
accessToken:(nullable NSString *)accessToken
displayName:(nonnull NSString *)displayName {
return [[FIROAuthCredential alloc] initWithProviderID:providerID
IDToken:IDToken
rawNonce:rawNonce
accessToken:accessToken
secret:nil
displayName:displayName
pendingToken:nil];
}

Expand Down
5 changes: 5 additions & 0 deletions FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic, assign) BOOL autoCreate;

/** @property displayName
@brief A displayName linked to its provider
*/
@property(nonatomic, copy, nullable) NSString *displayName;
rosalyntan marked this conversation as resolved.
Show resolved Hide resolved

/** @fn initWithEndpoint:requestConfiguration:
@brief Please use initWithProviderID:requestConfifuration instead.
*/
Expand Down
10 changes: 10 additions & 0 deletions FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@
*/
static NSString *const kReturnSecureTokenKey = @"returnSecureToken";

/** @var kDisplayName
@brief The key for the "displayName" value in the request.
*/
static NSString *const kDisplayNameKey = @"displayName";

/** @var kReturnIDPCredentialKey
rosalyntan marked this conversation as resolved.
Show resolved Hide resolved
@brief The key for the "returnIdpCredential" value in the request.
*/
Expand Down Expand Up @@ -171,6 +176,11 @@ - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)
if (_sessionID) {
body[kSessionIDKey] = _sessionID;
}

if (_displayName) {
body[kDisplayNameKey] = _displayName;
}

if (self.tenantID) {
body[kTenantIDKey] = self.tenantID;
}
Expand Down
5 changes: 5 additions & 0 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FIROAuthCredential.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ NS_SWIFT_NAME(OAuthCredential)
*/
@property(nonatomic, readonly, nullable) NSString *secret;

/** @property displayName
@brief The displayName associated with this credential.
*/
@property(nonatomic, readonly, nullable) NSString *displayName;

/** @fn init
@brief This class is not supposed to be instantiated directly.
*/
Expand Down
18 changes: 18 additions & 0 deletions FirebaseAuth/Sources/Public/FirebaseAuth/FIROAuthProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,24 @@ NS_SWIFT_NAME(OAuthProvider)
IDToken:(NSString *)IDToken
rawNonce:(nullable NSString *)rawNonce;

/** @fn credentialWithProviderID:IDToken:rawNonce:accessToken:
aiwenisevan marked this conversation as resolved.
Show resolved Hide resolved
@brief Creates an `AuthCredential` for that OAuth 2 provider identified by provider ID, ID
token, raw nonce, and access token.

@param providerID The provider ID associated with the Auth credential being created.
@param IDToken The IDToken associated with the Auth credential being created.
@param rawNonce The raw nonce associated with the Auth credential being created.
@param accessToken The access token associated with the Auth credential be created, if
available.
@param displayName The displayName associated with the Auth credential being created.
@return A `AuthCredential` for the specified provider ID, ID token and access token.
*/
+ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
IDToken:(NSString *)IDToken
rawNonce:(nullable NSString *)rawNonce
accessToken:(nullable NSString *)accessToken
rosalyntan marked this conversation as resolved.
Show resolved Hide resolved
displayName:(NSString *)displayName;

/** @fn init
@brief This class is not meant to be initialized.
*/
Expand Down
4 changes: 3 additions & 1 deletion FirebaseAuth/Tests/Sample/Sample/MainViewController+OAuth.m
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,12 @@ - (void)reauthenticateWithApple {
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
ASAuthorizationAppleIDCredential* appleIDCredential = authorization.credential;
NSString *IDToken = [NSString stringWithUTF8String:[appleIDCredential.identityToken bytes]];
NSString *displayName = [appleIDCredential.fullName.givenName stringByAppendingFormat:@" %@", appleIDCredential.fullName.familyName];
FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com"
IDToken:IDToken
rawNonce:self.appleRawNonce
accessToken:nil];
accessToken:nil
displayName:displayName];

if ([appleIDCredential.state isEqualToString:@"signIn"]) {
[FIRAuth.auth signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
Expand Down
148 changes: 148 additions & 0 deletions FirebaseAuth/Tests/Unit/FIRAuthTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,36 @@
*/
static NSString *const kGoogleIDToken = @"GOOGLE_ID_TOKEN";

/** @var kAppleAuthProviderID
@brief The provider ID for Apple Sign-In.
*/
static NSString *const kAppleAuthProviderID = @"apple.com";

/** @var kAppleUD
@brief The fake user ID under Apple Sign-In.
*/
static NSString *const kAppleID = @"APPLE_ID";

/** @var kAppleEmail
@brief The fake user email under Apple Sign-In.
*/
static NSString *const kAppleEmail = @"user@icloud.com";

/** @var kAppleDisplayName
@brief The fake user display name under Apple Sign-In.
*/
static NSString *const kAppleDisplayName = @"Apple Doe";

/** @var kAppleAccessToken
@brief The fake access token from Apple Sign-In.
*/
static NSString *const kAppleAccessToken = @"Apple_ACCESS_TOKEN";

/** @var kAppleIDToken
@brief The fake ID token from Apple Sign-In.
*/
static NSString *const kAppleIDToken = @"APPLE_ID_TOKEN";

/** @var kCustomToken
@brief The fake custom token to sign in.
*/
Expand Down Expand Up @@ -321,6 +351,23 @@ + (NSDictionary *)googleProfile {
return kGoogleProfile;
}

/** @fn appleProfile
@brief The fake user profile under additional user data in @c FIRVerifyAssertionResponse.
*/
+ (NSDictionary *)appleProfile {
static NSDictionary *kAppleProfile = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
kAppleProfile = @{
@"iss" : @"https://accounts.apple.com\\",
@"email" : kAppleEmail,
@"given_name" : @"User",
@"family_name" : @"Doe"
};
});
return kAppleProfile;
}

- (void)setUp {
[super setUp];

Expand Down Expand Up @@ -1374,6 +1421,60 @@ - (void)testSignInWithGoogleCredentialFailure {
OCMVerifyAll(_mockBackend);
}

/** @fn testSignInWithOAuthCredentialWithDisplayNameSuccess
@brief Tests the flow of a successful @c signInWithCredential:completion: call
with an Apple Sign-In credential.
This method differntiates the testSignInWithCredentialSuccess only in verifying displayName
*/
- (void)testSignInWithOAuthCredentialWithDisplayNameSuccess {
OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
FIRVerifyAssertionResponseCallback callback) {
XCTAssertEqualObjects(request.APIKey, kAPIKey);
XCTAssertEqualObjects(request.providerID, kAppleAuthProviderID);
XCTAssertEqualObjects(request.providerIDToken, kAppleIDToken);
XCTAssertEqualObjects(request.providerAccessToken, kAppleAccessToken);
rosalyntan marked this conversation as resolved.
Show resolved Hide resolved
XCTAssertEqualObjects(request.displayName, kAppleDisplayName);
XCTAssertTrue(request.returnSecureToken);
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
id mockVerifyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
OCMStub([mockVerifyAssertionResponse federatedID]).andReturn(kAppleID);
OCMStub([mockVerifyAssertionResponse providerID]).andReturn(kAppleAuthProviderID);
OCMStub([mockVerifyAssertionResponse localID]).andReturn(kLocalID);
OCMStub([mockVerifyAssertionResponse displayName]).andReturn(kAppleDisplayName);
OCMStub([mockVerifyAssertionResponse profile]).andReturn([[self class] appleProfile]);
OCMStub([mockVerifyAssertionResponse username]).andReturn(kDisplayName);
[self stubTokensWithMockResponse:mockVerifyAssertionResponse];
callback(mockVerifyAssertionResponse, nil);
});
});
[self expectGetAccountInfoApple];
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signOut:NULL];
FIRAuthCredential *appleCredential =
[FIROAuthProvider credentialWithProviderID:kAppleAuthProviderID
IDToken:kAppleIDToken
rawNonce:nil
accessToken:kAppleAccessToken
displayName:kAppleDisplayName];
[[FIRAuth auth]
signInWithCredential:appleCredential
completion:^(FIRAuthDataResult *_Nullable authResult, NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
[self assertUserApple:authResult.user];
XCTAssertEqualObjects(authResult.additionalUserInfo.profile,
[[self class] appleProfile]);
XCTAssertEqualObjects(authResult.additionalUserInfo.username, kDisplayName);
XCTAssertEqualObjects(authResult.additionalUserInfo.providerID,
kAppleAuthProviderID);
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
[self assertUserApple:[FIRAuth auth].currentUser];
OCMVerifyAll(_mockBackend);
}

/** @fn testSignInAnonymouslySuccess
@brief Tests the flow of a successful @c signInAnonymouslyWithCompletion: call.
*/
Expand Down Expand Up @@ -2584,6 +2685,53 @@ - (void)assertUserGoogle:(FIRUser *)user {
XCTAssertEqualObjects(googleUserInfo.email, kGoogleEmail);
}

/** @fn expectGetAccountInfoApple
@brief Expects a GetAccountInfo request on the mock backend and calls back with fake account
data for a Apple Sign-In user.
*/
- (void)expectGetAccountInfoApple {
OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(^(FIRGetAccountInfoRequest *_Nullable request,
FIRGetAccountInfoResponseCallback callback) {
XCTAssertEqualObjects(request.APIKey, kAPIKey);
XCTAssertEqualObjects(request.accessToken, kAccessToken);
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
id mockAppleUserInfo = OCMClassMock([FIRGetAccountInfoResponseProviderUserInfo class]);
OCMStub([mockAppleUserInfo providerID]).andReturn(kAppleAuthProviderID);
OCMStub([mockAppleUserInfo displayName]).andReturn(kAppleDisplayName);
OCMStub([mockAppleUserInfo federatedID]).andReturn(kAppleID);
OCMStub([mockAppleUserInfo email]).andReturn(kAppleEmail);
id mockGetAccountInfoResponseUser = OCMClassMock([FIRGetAccountInfoResponseUser class]);
OCMStub([mockGetAccountInfoResponseUser localID]).andReturn(kLocalID);
OCMStub([mockGetAccountInfoResponseUser displayName]).andReturn(kDisplayName);
OCMStub([mockGetAccountInfoResponseUser providerUserInfo])
.andReturn((@[ mockAppleUserInfo ]));
id mockGetAccountInfoResponse = OCMClassMock([FIRGetAccountInfoResponse class]);
OCMStub([mockGetAccountInfoResponse users]).andReturn(@[
mockGetAccountInfoResponseUser
]);
callback(mockGetAccountInfoResponse, nil);
});
});
}

/** @fn assertUserApple
@brief Asserts the given FIRUser matching the fake data returned by
@c expectGetAccountInfoApple.
@param user The user object to be verified.
*/
- (void)assertUserApple:(FIRUser *)user {
XCTAssertNotNil(user);
XCTAssertEqualObjects(user.uid, kLocalID);
XCTAssertEqualObjects(user.displayName, kDisplayName);
XCTAssertEqual(user.providerData.count, 1u);
id<FIRUserInfo> appleUserInfo = user.providerData[0];
XCTAssertEqualObjects(appleUserInfo.providerID, kAppleAuthProviderID);
XCTAssertEqualObjects(appleUserInfo.uid, kAppleID);
XCTAssertEqualObjects(appleUserInfo.displayName, kAppleDisplayName);
XCTAssertEqualObjects(appleUserInfo.email, kAppleEmail);
}

/** @fn expectGetAccountInfoAnonymous
@brief Expects a GetAccountInfo request on the mock backend and calls back with fake anonymous
account data.
Expand Down
Loading