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

Generic IDP support #2405

Merged
merged 8 commits into from
Feb 23, 2019
125 changes: 123 additions & 2 deletions Example/Auth/Sample/MainViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@
*/
static NSString *const kSignInGoogleButtonText = @"Sign in with Google";

/** @var kSignInGoogleProviderButtonText
@brief The text of the Google provider version of "Sign In With Provider" button.
*/
static NSString *const kSignInGoogleProviderButtonText = @"Sign in with provider:(Google)";

/** @var kSignInWithEmailLink
@brief The text of the "Sign in with Email Link" button.
*/
Expand All @@ -135,6 +140,16 @@
static NSString *const kSignInGoogleAndRetrieveDataButtonText =
@"Sign in with Google and retrieve data";

/** @var kSignInGoogleHeadfulLite
@brief The text of the "Sign in with Google (headful lite)" button.
*/
static NSString *const kSignInGoogleHeadfulLite = @"Sign in with Google (headful lite)";

/** @var kSignInMicrosoftHeadfulLite
@brief The text of the "Sign in with Microsoft (headful lite)" button.
*/
static NSString *const kSignInMicrosoftHeadfulLite = @"Sign in with Microsoft (headful lite)";

/** @var kSignInFacebookButtonText
@brief The text of the "Facebook SignIn" button.
*/
Expand Down Expand Up @@ -819,6 +834,8 @@ - (void)updateTable {
action:^{ [weakSelf createUserAuthDataResult]; }],
[StaticContentTableViewCell cellWithTitle:kSignInGoogleButtonText
action:^{ [weakSelf signInGoogle]; }],
[StaticContentTableViewCell cellWithTitle:kSignInGoogleProviderButtonText
action:^{ [weakSelf signInGoogleProvider]; }],
[StaticContentTableViewCell cellWithTitle:kSignInWithEmailLink
action:^{ [weakSelf signInWithEmailLink]; }],
[StaticContentTableViewCell cellWithTitle:kVerifyEmailLinkAccount
Expand All @@ -827,6 +844,10 @@ - (void)updateTable {
action:^{ [weakSelf sendEmailSignInLink]; }],
[StaticContentTableViewCell cellWithTitle:kSignInGoogleAndRetrieveDataButtonText
action:^{ [weakSelf signInGoogleAndRetrieveData]; }],
[StaticContentTableViewCell cellWithTitle:kSignInGoogleHeadfulLite
action:^{ [weakSelf signInGoogleHeadfulLite]; }],
[StaticContentTableViewCell cellWithTitle:kSignInMicrosoftHeadfulLite
action:^{ [weakSelf signInMicrosoftHeadfulLite]; }],
[StaticContentTableViewCell cellWithTitle:kSignInFacebookButtonText
action:^{ [weakSelf signInFacebook]; }],
[StaticContentTableViewCell cellWithTitle:kSignInFacebookAndRetrieveDataButtonText
Expand Down Expand Up @@ -1760,6 +1781,106 @@ - (void)signInGoogleAndRetrieveData {
[self signinWithProvider:[AuthProviders google] retrieveData:YES];
}

- (void)signInGoogleProvider {
FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:FIRGoogleAuthProviderID];
provider.customParameters = @{
@"prompt" : @"consent",
};
provider.scopes = @[ @"profile", @"email", @"https://www.googleapis.com/auth/plus.me" ];
[self showSpinner:^{
[[AppManager auth] signInWithProvider:provider
UIDelegate:nil
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"sign-in with provider (Google) failed" error:error];
} else if (authResult.additionalUserInfo) {
[self logSuccess:[self stringWithAdditionalUserInfo:authResult.additionalUserInfo]];
if (_isNewUserToggleOn) {
NSString *newUserString = authResult.additionalUserInfo.newUser ?
@"New user" : @"Existing user";
[self showMessagePromptWithTitle:@"New or Existing"
message:newUserString
showCancelButton:NO
completion:nil];
}
}
[self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
}];
}];
}];
}

/** @fn signInGoogleHeadfulLite
@brief Invoked when "Sign in with Google (headful-lite)" row is pressed.
*/
- (void)signInGoogleHeadfulLite {
FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:FIRGoogleAuthProviderID];
provider.customParameters = @{
@"prompt" : @"consent",
};
provider.scopes = @[ @"profile", @"email", @"https://www.googleapis.com/auth/plus.me" ];
[self showSpinner:^{
[provider getCredentialWithUIDelegate:nil completion:^(FIRAuthCredential *_Nullable credential,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with Google failed" error:error];
return;
}
[[AppManager auth] signInAndRetrieveDataWithCredential:credential
completion:^(FIRAuthDataResult *_Nullable
authResult,
NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"sign-in with Google failed" error:error];
return;
} else {
[self logSuccess:@"sign-in with Google (headful-lite) succeeded."];
}
[self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
}];
}];
}];
}];
}

/** @fn signInMicrosoftHeadfulLite
@brief Invoked when "Sign in with Microsoft (headful-lite)" row is pressed.
*/
- (void)signInMicrosoftHeadfulLite {
FIROAuthProvider *provider = [FIROAuthProvider providerWithProviderID:FIRMicrosoftAuthProviderID];
provider.customParameters = @{
@"prompt" : @"consent",
@"login_hint" : @"tu8731@gmail.com",
};
provider.scopes = @[ @"user.readwrite,calendars.read" ];
[self showSpinner:^{
[provider getCredentialWithUIDelegate:nil completion:^(FIRAuthCredential *_Nullable credential,
NSError *_Nullable error) {
if (error) {
[self logFailure:@"sign-in with Microsoft failed" error:error];
return;
}
[[AppManager auth] signInAndRetrieveDataWithCredential:credential
completion:^(FIRAuthDataResult *_Nullable
authResult,
NSError *_Nullable error) {
[self hideSpinner:^{
if (error) {
[self logFailure:@"sign-in with Microsoft failed" error:error];
return;
} else {
[self logSuccess:@"sign-in with Microsoft (headful-lite) succeeded."];
}
[self showTypicalUIForUserUpdateResultsWithTitle:@"Sign-In Error" error:error];
}];
}];
}];
}];
}

/** @fn signInFacebook
@brief Invoked when "Sign in with Facebook" row is pressed.
*/
Expand Down Expand Up @@ -3262,7 +3383,7 @@ - (void)linkPhoneNumber:(NSString *_Nullable)phoneNumber
// provided credential.
[self showSpinner:^{
FIRPhoneAuthCredential *credential =
error.userInfo[FIRAuthUpdatedCredentialKey];
error.userInfo[FIRAuthErrorUserInfoUpdatedCredentialKey];
[[AppManager auth] signInWithCredential:credential
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
Expand Down Expand Up @@ -3356,7 +3477,7 @@ - (void)signInWithGitHub {
if (!userPressedOK || !accessToken.length) {
return;
}
FIRAuthCredential *credential =
FIROAuthCredential *credential =
[FIROAuthProvider credentialWithProviderID:FIRGitHubAuthProviderID accessToken:accessToken];
if (credential) {
[[AppManager auth] signInWithCredential:credential
Expand Down
167 changes: 166 additions & 1 deletion Example/Auth/Tests/FIRAuthTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@

#import <FirebaseAuthInterop/FIRAuthInterop.h>
#import <FirebaseCore/FIRAppInternal.h>

#import <FirebaseAuth/FIREmailAuthProvider.h>
#import <FirebaseAuth/FIRGoogleAuthProvider.h>
#import <FirebaseAuth/FIRAdditionalUserInfo.h>

#import <FirebaseCore/FIRComponent.h>
#import <FirebaseCore/FIRLibrary.h>

Expand All @@ -41,7 +46,7 @@
#import "FIRGetAccountInfoResponse.h"
#import "FIRGetOOBConfirmationCodeRequest.h"
#import "FIRGetOOBConfirmationCodeResponse.h"
#import "FIRGoogleAuthProvider.h"
#import "FIROAuthProvider.h"
#import "FIRSecureTokenRequest.h"
#import "FIRSecureTokenResponse.h"
#import "FIRResetPasswordRequest.h"
Expand All @@ -59,11 +64,13 @@
#import "FIRVerifyPhoneNumberRequest.h"
#import "FIRVerifyPhoneNumberResponse.h"
#import "FIRApp+FIRAuthUnitTests.h"
#import "OAuth/FIROAuthCredential_Internal.h"
#import "OCMStubRecorder+FIRAuthUnitTests.h"
#import <OCMock/OCMock.h>
#import "FIRActionCodeSettings.h"

#if TARGET_OS_IOS
#import "FIRAuthUIDelegate.h"
#import "FIRPhoneAuthCredential.h"
#import "FIRPhoneAuthProvider.h"
#endif
Expand Down Expand Up @@ -163,6 +170,21 @@
*/
static NSString *const kVerificationID = @"55432";

/** @var kOAuthRequestURI
@brief Fake OAuthRequest URI for testing.
*/
static NSString *const kOAuthRequestURI = @"requestURI";

/** @var kOAuthSessionID
@brief Fake session ID for testing.
*/
static NSString *const kOAuthSessionID = @"sessionID";

/** @var kFakeWebSignInUserInteractionFailureReason
@brief Fake reason for FIRAuthErrorCodeWebSignInUserInteractionFailure error while testing.
*/
static NSString *const kFakeWebSignInUserInteractionFailureReason = @"fake_reason";

/** @var kContinueURL
@brief Fake string value of continue url.
*/
Expand Down Expand Up @@ -1116,6 +1138,91 @@ - (void)testSignInWithEmailCredentialEmptyPassword {
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
}

#if TARGET_OS_IOS
/** @fn testSignInWithProviderSuccess
@brief Tests a successful @c signInWithProvider:UIDelegate:completion: call with an OAuth
provider configured for Google.
*/
- (void)testSignInWithProviderSuccess {
OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
FIRVerifyAssertionResponseCallback callback) {
XCTAssertEqualObjects(request.APIKey, kAPIKey);
XCTAssertEqualObjects(request.providerID, FIRGoogleAuthProviderID);
XCTAssertTrue(request.returnSecureToken);
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
id mockVerifyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
OCMStub([mockVerifyAssertionResponse federatedID]).andReturn(kGoogleID);
OCMStub([mockVerifyAssertionResponse providerID]).andReturn(FIRGoogleAuthProviderID);
OCMStub([mockVerifyAssertionResponse localID]).andReturn(kLocalID);
OCMStub([mockVerifyAssertionResponse displayName]).andReturn(kGoogleDisplayName);
[self stubTokensWithMockResponse:mockVerifyAssertionResponse];
callback(mockVerifyAssertionResponse, nil);
});
});
[self expectGetAccountInfoGoogle];
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signOut:NULL];
id mockProvider = OCMClassMock([FIROAuthProvider class]);
OCMExpect([mockProvider getCredentialWithUIDelegate:[OCMArg any] completion:[OCMArg any]])
.andCallBlock2(^(id<FIRAuthUIDelegate> delegate, FIRAuthCredentialCallback callback) {
dispatch_async(FIRAuthGlobalWorkQueue(), ^(){
FIROAuthCredential *credential =
[[FIROAuthCredential alloc] initWithProviderID:FIRGoogleAuthProviderID
sessionID:kOAuthSessionID
OAuthResponseURLString:kOAuthRequestURI];
callback(credential, nil);
});
});
[[FIRAuth auth] signInWithProvider:mockProvider
UIDelegate:nil
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
[self assertUserGoogle:authResult.user];
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
OCMVerifyAll(_mockBackend);
}

/** @fn testSignInWithProviderFailure
@brief Tests a failed @c signInWithProvider:UIDelegate:completion: call with the error code
FIRAuthErrorCodeWebSignInUserInteractionFailure.
*/
- (void)testSignInWithProviderFailure {
OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
.andDispatchError2([FIRAuthErrorUtils webSignInUserInteractionFailureWithReason:
kFakeWebSignInUserInteractionFailureReason]);
[[FIRAuth auth] signOut:NULL];
id mockProvider = OCMClassMock([FIROAuthProvider class]);
OCMExpect([mockProvider getCredentialWithUIDelegate:[OCMArg any] completion:[OCMArg any]])
.andCallBlock2(^(id<FIRAuthUIDelegate> delegate, FIRAuthCredentialCallback callback) {
dispatch_async(FIRAuthGlobalWorkQueue(), ^(){
FIROAuthCredential *credential =
[[FIROAuthCredential alloc] initWithProviderID:FIRGoogleAuthProviderID
sessionID:kOAuthSessionID
OAuthResponseURLString:kOAuthRequestURI];
callback(credential, nil);
});
});
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signInWithProvider:mockProvider
UIDelegate:nil
completion:^(FIRAuthDataResult *_Nullable authResult,
NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
XCTAssertNil(authResult);
XCTAssertEqual(error.code, FIRAuthErrorCodeWebSignInUserInteractionFailure);
XCTAssertEqualObjects(error.userInfo[NSLocalizedFailureReasonErrorKey],
kFakeWebSignInUserInteractionFailureReason);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
OCMVerifyAll(_mockBackend);
}

/** @fn testSignInWithGoogleAccountExistsError
@brief Tests the flow of a failed @c signInWithCredential:completion: with a Google credential
where the backend returns a needs @needConfirmation equal to true. An
Expand Down Expand Up @@ -1195,6 +1302,64 @@ - (void)testSignInWithGoogleCredentialSuccess {
OCMVerifyAll(_mockBackend);
}

/** @fn testSignInWithOAuthCredentialSuccess
@brief Tests the flow of a successful @c signInWithCredential:completion: call with a generic
OAuth credential (In this case, configured for the Google IDP).
*/
- (void)testSignInWithOAuthCredentialSuccess {
OCMExpect([_mockBackend verifyAssertion:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(^(FIRVerifyAssertionRequest *_Nullable request,
FIRVerifyAssertionResponseCallback callback) {
XCTAssertEqualObjects(request.APIKey, kAPIKey);
XCTAssertEqualObjects(request.providerID, FIRGoogleAuthProviderID);
XCTAssertEqualObjects(request.requestURI, kOAuthRequestURI);
XCTAssertEqualObjects(request.sessionID, kOAuthSessionID);
XCTAssertTrue(request.returnSecureToken);
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
id mockVeriyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
OCMStub([mockVeriyAssertionResponse federatedID]).andReturn(kGoogleID);
OCMStub([mockVeriyAssertionResponse providerID]).andReturn(FIRGoogleAuthProviderID);
OCMStub([mockVeriyAssertionResponse localID]).andReturn(kLocalID);
OCMStub([mockVeriyAssertionResponse displayName]).andReturn(kGoogleDisplayName);
[self stubTokensWithMockResponse:mockVeriyAssertionResponse];
callback(mockVeriyAssertionResponse, nil);
});
});
[self expectGetAccountInfoGoogle];
XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[[FIRAuth auth] signOut:NULL];
id mockProvider = OCMClassMock([FIROAuthProvider class]);
OCMExpect([mockProvider getCredentialWithUIDelegate:[OCMArg any] completion:[OCMArg any]])
.andCallBlock2(^(id<FIRAuthUIDelegate> delegate, FIRAuthCredentialCallback callback) {
dispatch_async(FIRAuthGlobalWorkQueue(), ^(){
FIROAuthCredential *credential =
[[FIROAuthCredential alloc] initWithProviderID:FIRGoogleAuthProviderID
sessionID:kOAuthSessionID
OAuthResponseURLString:kOAuthRequestURI];
callback(credential, nil);
});
});
[mockProvider getCredentialWithUIDelegate:nil
completion:^(FIRAuthCredential *_Nullable credential,
NSError *_Nullable error) {
XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
XCTAssertEqualObjects(OAuthCredential.OAuthResponseURLString, kOAuthRequestURI);
XCTAssertEqualObjects(OAuthCredential.sessionID, kOAuthSessionID);
[[FIRAuth auth] signInWithCredential:OAuthCredential completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
[self assertUserGoogle:user];
XCTAssertNil(error);
[expectation fulfill];
}];
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
[self assertUserGoogle:[FIRAuth auth].currentUser];
OCMVerifyAll(_mockBackend);
}
#endif // TARGET_OS_IOS

/** @fn testSignInAndRetrieveDataWithCredentialSuccess
@brief Tests the flow of a successful @c signInAndRetrieveDataWithCredential:completion: call
with an Google Sign-In credential.
Expand Down