From 83b519f0a53a2efc946b32b1708d882a34f30d3a Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Tue, 24 Apr 2018 16:19:11 -0700 Subject: Remove GTM dependency from FirebaseAuth (#1175) --- .../AuthProviders/Phone/FIRPhoneAuthProvider.m | 80 +++++++----- Firebase/Auth/Source/FIRAuthWebUtils.h | 80 ++++++++++++ Firebase/Auth/Source/FIRAuthWebUtils.m | 134 +++++++++++++++++++++ .../Auth/Source/RPCs/FIRVerifyAssertionRequest.m | 30 ++--- 4 files changed, 277 insertions(+), 47 deletions(-) create mode 100644 Firebase/Auth/Source/FIRAuthWebUtils.h create mode 100644 Firebase/Auth/Source/FIRAuthWebUtils.m (limited to 'Firebase/Auth') diff --git a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m index a44a340..948a515 100644 --- a/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m +++ b/Firebase/Auth/Source/AuthProviders/Phone/FIRPhoneAuthProvider.m @@ -16,8 +16,6 @@ #import "FIRPhoneAuthProvider.h" -#import - #import #import "FIRPhoneAuthCredential_Internal.h" #import @@ -31,6 +29,7 @@ #import "FIRAuthNotificationManager.h" #import "FIRAuthErrorUtils.h" #import "FIRAuthBackend.h" +#import "FIRAuthWebUtils.h" #import "FirebaseAuthVersion.h" #import #import "FIRGetProjectConfigRequest.h" @@ -78,7 +77,7 @@ static NSString *const kAuthTypeVerifyApp = @"verifyApp"; /** @var kReCAPTCHAURLStringFormat @brief The format of the URL used to open the reCAPTCHA page during app verification. */ -NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@"; +NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?"; @implementation FIRPhoneAuthProvider { @@ -244,15 +243,22 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@"; @return The reCAPTCHA token if successful. */ - (NSString *)reCAPTCHATokenForURL:(NSURL *)URL error:(NSError **)error { - NSDictionary *URLQueryItems = - [NSDictionary gtm_dictionaryWithHttpArgumentsString:URL.query]; - NSURL *deepLinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]]; - URLQueryItems = - [NSDictionary gtm_dictionaryWithHttpArgumentsString:deepLinkURL.query]; - if (URLQueryItems[@"recaptchaToken"]) { - return URLQueryItems[@"recaptchaToken"]; + NSURLComponents *actualURLComponents = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + NSArray *queryItems = [actualURLComponents queryItems]; + NSString *deepLinkURL = [FIRAuthWebUtils queryItemValue:@"deep_link_id" from:queryItems]; + NSData *errorData; + if (deepLinkURL) { + actualURLComponents = [NSURLComponents componentsWithString:deepLinkURL]; + queryItems = [actualURLComponents queryItems]; + NSString *recaptchaToken = [FIRAuthWebUtils queryItemValue:@"recaptchaToken" from:queryItems]; + if (recaptchaToken) { + return recaptchaToken; + } + NSString *firebaseError = [FIRAuthWebUtils queryItemValue:@"firebaseError" from:queryItems]; + errorData = [firebaseError dataUsingEncoding:NSUTF8StringEncoding]; + } else { + errorData = nil; } - NSData *errorData = [URLQueryItems[@"firebaseError"] dataUsingEncoding:NSUTF8StringEncoding]; NSError *jsonError; NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData options:0 @@ -298,13 +304,19 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@"; if (!([[expectedURLComponents URL] isEqual:[actualURLComponents URL]])) { return NO; } - NSDictionary *URLQueryItems = - [NSDictionary gtm_dictionaryWithHttpArgumentsString:URL.query]; - NSURL *deeplinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]]; - NSDictionary *deeplinkQueryItems = - [NSDictionary gtm_dictionaryWithHttpArgumentsString:deeplinkURL.query]; - if ([deeplinkQueryItems[@"authType"] isEqualToString:kAuthTypeVerifyApp] && - [deeplinkQueryItems[@"eventId"] isEqualToString:eventID]) { + actualURLComponents = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + NSArray *queryItems = [actualURLComponents queryItems]; + NSString *deepLinkURL = [FIRAuthWebUtils queryItemValue:@"deep_link_id" from:queryItems]; + if (deepLinkURL == nil) { + return NO; + } + NSURLComponents *deepLinkURLComponents = [NSURLComponents componentsWithString:deepLinkURL]; + NSArray *deepLinkQueryItems = [deepLinkURLComponents queryItems]; + + NSString *deepLinkAuthType = [FIRAuthWebUtils queryItemValue:@"authType" from:deepLinkQueryItems]; + NSString *deepLinkEventID = [FIRAuthWebUtils queryItemValue:@"eventId" from:deepLinkQueryItems]; + if ([deepLinkAuthType isEqualToString:kAuthTypeVerifyApp] && + [deepLinkEventID isEqualToString:eventID]) { return YES; } return NO; @@ -444,27 +456,29 @@ NSString *const kReCAPTCHAURLStringFormat = @"https://%@/__/auth/handler?%@"; return; } NSString *bundleID = [NSBundle mainBundle].bundleIdentifier; - NSString *clienID = self->_auth.app.options.clientID; + NSString *clientID = self->_auth.app.options.clientID; NSString *apiKey = self->_auth.requestConfiguration.APIKey; - NSMutableDictionary *urlArguments = [[NSMutableDictionary alloc] initWithDictionary: @{ - @"apiKey" : apiKey, - @"authType" : kAuthTypeVerifyApp, - @"ibi" : bundleID ?: @"", - @"clientId" : clienID, - @"v" : [FIRAuthBackend authUserAgent], - @"eventId" : eventID, - }]; + NSMutableArray *queryItems = [@[ + [NSURLQueryItem queryItemWithName:@"apiKey" value:apiKey], + [NSURLQueryItem queryItemWithName:@"authType" value:kAuthTypeVerifyApp], + [NSURLQueryItem queryItemWithName:@"ibi" value:bundleID ?: @""], + [NSURLQueryItem queryItemWithName:@"clientId" value:clientID], + [NSURLQueryItem queryItemWithName:@"v" value:[FIRAuthBackend authUserAgent]], + [NSURLQueryItem queryItemWithName:@"eventId" value:eventID] + ] mutableCopy + ]; + if (self->_auth.requestConfiguration.languageCode) { - urlArguments[@"hl"] = self->_auth.requestConfiguration.languageCode; + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"hl"value: + self->_auth.requestConfiguration.languageCode]]; } - NSString *argumentsString = [urlArguments gtm_httpArgumentsString]; - NSString *URLString = - [NSString stringWithFormat:kReCAPTCHAURLStringFormat, authDomain, argumentsString]; - completion([NSURL URLWithString:URLString], nil); + NSURLComponents *components = [[NSURLComponents alloc] initWithString: + [NSString stringWithFormat:kReCAPTCHAURLStringFormat, authDomain]]; + [components setQueryItems:queryItems]; + completion([components URL], nil); }]; } - /** @fn fetchAuthDomainWithCompletion:completion: @brief Fetches the auth domain associated with the Firebase Project. @param completion The callback invoked after the auth domain has been constructed or an error diff --git a/Firebase/Auth/Source/FIRAuthWebUtils.h b/Firebase/Auth/Source/FIRAuthWebUtils.h new file mode 100644 index 0000000..fe1c29a --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthWebUtils.h @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthRequestConfiguration; + +NS_ASSUME_NONNULL_BEGIN + +/** @typedef FIRFetchAuthDomainCallback + @brief The callback invoked at the end of the flow to fetch the Auth domain. + @param authDomain The Auth domain. + @param error The error that occured while fetching the auth domain, if any. + */ +typedef void (^FIRFetchAuthDomainCallback)(NSString *_Nullable authDomain, + NSError *_Nullable error); + +/** @class FIRAuthURLUtils + @brief A utility class used to facilitate the creation of auth related URLs. + */ +@interface FIRAuthWebUtils : NSObject + +/** @fn randomStringWithLength: + @brief Generates a random string of a specified length. + */ ++ (NSString *)randomStringWithLength:(NSUInteger)length; + +/** @fn isCallbackSchemeRegisteredForCustomURLScheme: + @brief Checks whether or not the provided custom URL scheme has been registered by the app. + @param URLScheme The custom URL scheme to be checked against all custom URL schemes registered by the app. + @return whether or not the provided custom URL scheme has been registered by the app. + */ ++ (BOOL)isCallbackSchemeRegisteredForCustomURLScheme:(NSString *)URLScheme; + +/** @fn isExpectedCallbackURL:eventID:authType + @brief Parses a URL into all available query items. + @param URL The actual callback URL. + @param eventID The expected event ID. + @param authType The expected auth type. + @param callbackScheme The expected callback custom scheme. + @return Whether or not the actual callback URL matches the expected callback URL. + */ ++ (BOOL)isExpectedCallbackURL:(nullable NSURL *)URL + eventID:(NSString *)eventID + authType:(NSString *)authType + callbackScheme:(NSString *)callbackScheme; + +/** @fn fetchAuthDomainWithCompletion:completion: + @brief Fetches the auth domain associated with the Firebase Project. + @param completion The callback invoked after the auth domain has been constructed or an error + has been encountered. + */ ++ (void)fetchAuthDomainWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + completion:(FIRFetchAuthDomainCallback)completion; + +/** @fn queryItemValue:from: + @brief Utility function to get a value from a NSURLQueryItem array. + @param name The key. + @param queryList The NSURLQueryItem array. + @return The value for the key. + */ + ++ (NSString *)queryItemValue:(NSString *)name from:(NSArray *)queryList; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Firebase/Auth/Source/FIRAuthWebUtils.m b/Firebase/Auth/Source/FIRAuthWebUtils.m new file mode 100644 index 0000000..d694a06 --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthWebUtils.m @@ -0,0 +1,134 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRAuthWebUtils.h" + +#import + +#import "FIRAuthBackend.h" +#import "FIRAuthErrorUtils.h" +#import "FIRGetProjectConfigRequest.h" +#import "FIRGetProjectConfigResponse.h" + +/** @var kAuthDomainSuffix + @brief The suffix of the auth domain pertiaining to a given Firebase project. + */ +static NSString *const kAuthDomainSuffix = @"firebaseapp.com"; + +@implementation FIRAuthWebUtils + ++ (NSString *)randomStringWithLength:(NSUInteger)length { + NSMutableString *randomString = [[NSMutableString alloc] init]; + for (int i=0; i < length; i++) { + [randomString appendString: + [NSString stringWithFormat:@"%c", 'a' + arc4random_uniform('z' - 'a' + 1)]]; + } + return randomString; +} + ++ (BOOL)isCallbackSchemeRegisteredForCustomURLScheme:(NSString *)URLScheme { + NSString *expectedCustomScheme = [URLScheme lowercaseString]; + NSArray *urlTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"]; + for (NSDictionary *urlType in urlTypes) { + NSArray *urlTypeSchemes = urlType[@"CFBundleURLSchemes"]; + for (NSString *urlTypeScheme in urlTypeSchemes) { + if ([urlTypeScheme.lowercaseString isEqualToString:expectedCustomScheme]) { + return YES; + } + } + } + return NO; +} + ++ (BOOL)isExpectedCallbackURL:(NSURL *)URL + eventID:(NSString *)eventID + authType:(NSString *)authType + callbackScheme:(NSString *)callbackScheme { + if (!URL) { + return NO; + } + NSURLComponents *actualURLComponents = + [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO]; + actualURLComponents.query = nil; + actualURLComponents.fragment = nil; + + NSURLComponents *expectedURLComponents = [NSURLComponents new]; + expectedURLComponents.scheme = callbackScheme; + expectedURLComponents.host = @"firebaseauth"; + expectedURLComponents.path = @"/link"; + + if (!([[expectedURLComponents URL] isEqual:[actualURLComponents URL]])) { + return NO; + } + NSDictionary *URLQueryItems = + [NSDictionary gtm_dictionaryWithHttpArgumentsString:URL.query]; + NSURL *deeplinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]]; + NSDictionary *deeplinkQueryItems = + [NSDictionary gtm_dictionaryWithHttpArgumentsString:deeplinkURL.query]; + if ([deeplinkQueryItems[@"authType"] isEqualToString:authType] && + [deeplinkQueryItems[@"eventId"] isEqualToString:eventID]) { + return YES; + } + return NO; +} + ++ (void)fetchAuthDomainWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + completion:(FIRFetchAuthDomainCallback)completion { + FIRGetProjectConfigRequest *request = + [[FIRGetProjectConfigRequest alloc] initWithRequestConfiguration:requestConfiguration]; + + [FIRAuthBackend getProjectConfig:request + callback:^(FIRGetProjectConfigResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + completion(nil, error); + return; + } + NSString *authDomain; + for (NSString *domain in response.authorizedDomains) { + NSInteger index = domain.length - kAuthDomainSuffix.length; + if (index >= 2) { + if ([domain hasSuffix:kAuthDomainSuffix] && domain.length >= kAuthDomainSuffix.length + 2) { + authDomain = domain; + break; + } + } + } + if (!authDomain.length) { + completion(nil, [FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:response]); + return; + } + completion(authDomain, nil); + }]; +} + +/** @fn queryItemValue:from: + @brief Utility function to get a value from a NSURLQueryItem array. + @param name The key. + @param queryList The NSURLQueryItem array. + @return The value for the key. + */ + ++ (NSString *)queryItemValue:(NSString *)name from:(NSArray *)queryList { + for (NSURLQueryItem *item in queryList) { + if ([item.name isEqualToString:name]) { + return item.value; + } + } + return nil; +} + +@end \ No newline at end of file diff --git a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.m b/Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.m index 2251c42..274fd07 100644 --- a/Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.m +++ b/Firebase/Auth/Source/RPCs/FIRVerifyAssertionRequest.m @@ -16,9 +16,6 @@ #import "FIRVerifyAssertionRequest.h" -#import -#import - /** @var kVerifyAssertionEndpoint @brief The "verifyAssertion" endpoint. */ @@ -95,16 +92,19 @@ static NSString *const kReturnSecureTokenKey = @"returnSecureToken"; } - (nullable id)unencodedHTTPRequestBodyWithError:(NSError *_Nullable *_Nullable)error { - NSMutableDictionary *postBody = [@{ - kProviderIDKey : _providerID, - } mutableCopy]; + NSURLComponents *components = [[NSURLComponents alloc] init]; + NSMutableArray *queryItems = [@[[NSURLQueryItem queryItemWithName:kProviderIDKey + value:_providerID]] + mutableCopy]; if (_providerIDToken) { - postBody[kProviderIDTokenKey] = _providerIDToken; + [queryItems addObject:[NSURLQueryItem queryItemWithName:kProviderIDTokenKey + value:_providerIDToken]]; } if (_providerAccessToken) { - postBody[kProviderAccessTokenKey] = _providerAccessToken; + [queryItems addObject:[NSURLQueryItem queryItemWithName:kProviderAccessTokenKey + value:_providerAccessToken]]; } if (!_providerIDToken && !_providerAccessToken) { @@ -113,17 +113,19 @@ static NSString *const kReturnSecureTokenKey = @"returnSecureToken"; } if (_providerOAuthTokenSecret) { - postBody[kProviderOAuthTokenSecretKey] = _providerOAuthTokenSecret; + [queryItems addObject:[NSURLQueryItem queryItemWithName:kProviderOAuthTokenSecretKey + value:_providerOAuthTokenSecret]]; } if (_inputEmail) { - postBody[kIdentifierKey] = _inputEmail; + [queryItems addObject:[NSURLQueryItem queryItemWithName:kIdentifierKey + value:_inputEmail]]; } - + [components setQueryItems:queryItems]; NSMutableDictionary *body = [@{ - kRequestURIKey : @"http://localhost", // Unused by server, but required - kPostBodyKey : [postBody gtm_httpArgumentsString] - } mutableCopy]; + kRequestURIKey : @"http://localhost", // Unused by server, but required + kPostBodyKey : [components query] + } mutableCopy]; if (_pendingIDToken) { body[kPendingIDTokenKey] = _pendingIDToken; -- cgit v1.2.3