Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions FirebaseAuth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [fixed] Check if the reverse client ID is configured as a custom URL scheme before setting it as the callback scheme. (#7211).

# 7.3.0
- [fixed] Catalyst browser issue with `verifyPhoneNumber` API. (#7049)

Expand Down
21 changes: 17 additions & 4 deletions FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ @implementation FIROAuthProvider {
@brief The callback URL scheme used for headful-lite sign-in.
*/
NSString *_callbackScheme;

/** @var _usingClientIDScheme
@brief True if the reverse client ID is registered as a custom URL scheme, and false
otherwise.
*/
BOOL _usingClientIDScheme;
}

+ (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
Expand Down Expand Up @@ -216,9 +222,16 @@ - (nullable instancetype)initWithProviderID:(NSString *)providerID auth:(FIRAuth
_auth = auth;
_providerID = providerID;
if (_auth.app.options.clientID) {
_callbackScheme = [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
} else {
NSString *reverseClientIDScheme =
[[[_auth.app.options.clientID componentsSeparatedByString:@"."]
reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
if ([FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:reverseClientIDScheme]) {
_callbackScheme = reverseClientIDScheme;
_usingClientIDScheme = YES;
}
}

if (!_usingClientIDScheme) {
_callbackScheme = [kCustomUrlSchemePrefix
stringByAppendingString:[_auth.app.options.googleAppID
stringByReplacingOccurrencesOfString:@":"
Expand Down Expand Up @@ -304,7 +317,7 @@ - (void)getHeadFulLiteURLWithEventID:(NSString *)eventID
@"eventId" : eventID,
@"providerId" : strongSelf->_providerID,
} mutableCopy];
if (clientID) {
if (strongSelf->_usingClientIDScheme) {
urlArguments[@"clientId"] = clientID;
} else {
urlArguments[@"appId"] = appID;
Expand Down
20 changes: 16 additions & 4 deletions FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ @implementation FIRPhoneAuthProvider {
@brief The callback URL scheme used for reCAPTCHA fallback.
*/
NSString *_callbackScheme;

/** @var _usingClientIDScheme
@brief True if the reverse client ID is registered as a custom URL scheme, and false
otherwise.
*/
BOOL _usingClientIDScheme;
}

/** @fn initWithAuth:
Expand All @@ -116,9 +122,15 @@ - (nullable instancetype)initWithAuth:(FIRAuth *)auth {
if (self) {
_auth = auth;
if (_auth.app.options.clientID) {
_callbackScheme = [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
} else {
NSString *reverseClientIDScheme =
[[[_auth.app.options.clientID componentsSeparatedByString:@"."]
reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
if ([FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:reverseClientIDScheme]) {
_callbackScheme = reverseClientIDScheme;
_usingClientIDScheme = YES;
}
}
if (!_usingClientIDScheme) {
_callbackScheme = [kCustomUrlSchemePrefix
stringByAppendingString:[_auth.app.options.googleAppID
stringByReplacingOccurrencesOfString:@":"
Expand Down Expand Up @@ -699,7 +711,7 @@ - (void)reCAPTCHAURLWithEventID:(NSString *)eventID completion:(FIRReCAPTCHAURLC
value:[FIRAuthBackend authUserAgent]],
[NSURLQueryItem queryItemWithName:@"eventId" value:eventID]
] mutableCopy];
if (clientID) {
if (self->_usingClientIDScheme) {
[queryItems
addObject:[NSURLQueryItem queryItemWithName:@"clientId"
value:clientID]];
Expand Down
152 changes: 128 additions & 24 deletions FirebaseAuth/Tests/Unit/FIROAuthProviderTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,16 @@ - (void)testObtainingOAuthProvider {
@brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
*/
- (void)testGetCredentialWithUIDelegateWithClientID {
OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(
^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
Expand Down Expand Up @@ -371,16 +371,16 @@ - (void)testGetCredentialWithUIDelegateWithClientID {
cancelation.
*/
- (void)testGetCredentialWithUIDelegateUserCancellationWithClientID {
OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(
^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
Expand Down Expand Up @@ -471,16 +471,16 @@ - (void)testGetCredentialWithUIDelegateUserCancellationWithClientID {
failed network request within the web context.
*/
- (void)testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID {
OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(
^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
Expand Down Expand Up @@ -569,16 +569,16 @@ - (void)testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID {
internal error within the web context.
*/
- (void)testGetCredentialWithUIDelegateInternalErrorWithClientID {
OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(
^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
Expand Down Expand Up @@ -668,16 +668,16 @@ - (void)testGetCredentialWithUIDelegateInternalErrorWithClientID {
use of an invalid client ID.
*/
- (void)testGetCredentialWithUIDelegateInvalidClientID {
OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(
^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
Expand Down Expand Up @@ -767,16 +767,16 @@ - (void)testGetCredentialWithUIDelegateInvalidClientID {
unknown error.
*/
- (void)testGetCredentialWithUIDelegateUnknownErrorWithClientID {
OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(
^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
Expand Down Expand Up @@ -866,15 +866,119 @@ - (void)testGetCredentialWithUIDelegateUnknownErrorWithClientID {
@brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
*/
- (void)testGetCredentialWithUIDelegateWithFirebaseAppID {
id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(
^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
XCTAssertNotNil(request);
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
kFakeAuthorizedDomain
]);
callback(mockGetProjectConfigResponse, nil);
});
});

id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));

// Expect view controller presentation by UIDelegate.
OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
UIDelegate:mockUIDelegate
callbackMatcher:OCMOCK_ANY
completion:OCMOCK_ANY])
.andDo(^(NSInvocation *invocation) {
__unsafe_unretained id unretainedArgument;
// Indices 0 and 1 indicate the hidden arguments self and _cmd.
// `presentURL` is at index 2.
[invocation getArgument:&unretainedArgument atIndex:2];
NSURL *presentURL = unretainedArgument;
XCTAssertEqualObjects(presentURL.scheme, @"https");
XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
XCTAssertEqualObjects(params[@"appId"], kFakeFirebaseAppID);
XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
XCTAssertNotNil(params[@"v"]);
// `callbackMatcher` is at index 4
[invocation getArgument:&unretainedArgument atIndex:4];
FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
NSMutableString *redirectURL = [NSMutableString
stringWithString:[kFakeEncodedFirebaseAppID
stringByAppendingString:kFakeRedirectURLResponseURL]];
// Add fake OAuthResponse to callback.
[redirectURL appendString:kFakeOAuthResponseURL];
// Verify that the URL is rejected by the callback matcher without the event ID.
XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
[redirectURL appendString:@"%26eventId%3D"];
[redirectURL appendString:params[@"eventId"]];
NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
// Verify that the URL is accepted by the callback matcher with the matching event ID.
XCTAssertTrue(callbackMatcher([originalComponents URL]));
NSURLComponents *components = [originalComponents copy];
components.query = @"https";
XCTAssertFalse(callbackMatcher([components URL]));
components = [originalComponents copy];
components.host = @"badhost";
XCTAssertFalse(callbackMatcher([components URL]));
components = [originalComponents copy];
components.path = @"badpath";
XCTAssertFalse(callbackMatcher([components URL]));
components = [originalComponents copy];
components.query = @"badquery";
XCTAssertFalse(callbackMatcher([components URL]));

// `completion` is at index 5
[invocation getArgument:&unretainedArgument atIndex:5];
FIRAuthURLPresentationCompletion completion = unretainedArgument;
dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
completion(originalComponents.URL, nil);
});
});

XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
[_provider
getCredentialWithUIDelegate:mockUIDelegate
completion:^(FIRAuthCredential *_Nullable credential,
NSError *_Nullable error) {
XCTAssertTrue([NSThread isMainThread]);
XCTAssertNil(error);
XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
XCTAssertEqualObjects(kFakeOAuthResponseURL,
OAuthCredential.OAuthResponseURLString);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
OCMVerifyAll(_mockBackend);
}

/** @fn testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent
@brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion: when the
client ID is present in the plist file, but the encoded app ID is the registered custom URL
scheme.
*/
- (void)testGetCredentialWithUIDelegateWithFirebaseAppIDWhileClientIdPresent {
id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeEncodedFirebaseAppID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
.andCallBlock2(
^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
Expand Down Expand Up @@ -968,19 +1072,19 @@ - (void)testGetCredentialWithUIDelegateWithFirebaseAppID {
emulator.
*/
- (void)testGetCredentialWithUIDelegateUseEmulator {
OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
NSString *emulatorHostAndPort =
[NSString stringWithFormat:@"%@:%@", kFakeEmulatorHost, kFakeEmulatorPort];
OCMStub([_mockRequestConfiguration emulatorHostAndPort]).andReturn(emulatorHostAndPort);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

id mockBundle = OCMClassMock([NSBundle class]);
OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
@{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
]);
OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);

OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
NSString *emulatorHostAndPort =
[NSString stringWithFormat:@"%@:%@", kFakeEmulatorHost, kFakeEmulatorPort];
OCMStub([_mockRequestConfiguration emulatorHostAndPort]).andReturn(emulatorHostAndPort);
_provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];

id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));

// Expect view controller presentation by UIDelegate.
Expand Down
Loading