/* * Copyright 2017 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. */ #include #import "FIROAuthProvider.h" #import "FIRApp.h" #import "FIRAuthBackend.h" #import "FIRAuth_Internal.h" #import "FIRAuthErrorUtils.h" #import "FIRAuthGlobalWorkQueue.h" #import "FIRAuthRequestConfiguration.h" #import "FIRAuthWebUtils.h" #import "FIROAuthCredential_Internal.h" #import "FIROAuthCredential.h" #import "FIROptions.h" #if TARGET_OS_IOS #import "FIRAuthURLPresenter.h" #endif NS_ASSUME_NONNULL_BEGIN /** @typedef FIRHeadfulLiteURLCallBack @brief The callback invoked at the end of the flow to fetch a headful-lite URL. @param headfulLiteURL The headful lite URL. @param error The error that occured while fetching the headful-lite, if any. */ typedef void (^FIRHeadfulLiteURLCallBack)(NSURL *_Nullable headfulLiteURL, NSError *_Nullable error); /** @var kHeadfulLiteURLStringFormat @brief The format of the URL used to open the headful lite page during sign-in. */ NSString *const kHeadfulLiteURLStringFormat = @"https://%@/__/auth/handler?%@"; /** @var kauthTypeSignInWithRedirect @brief The auth type to be specified in the sign-in request with redirect request and response. */ static NSString *const kAuthTypeSignInWithRedirect = @"signInWithRedirect"; @implementation FIROAuthProvider { /** @var _auth @brief The auth instance used for launching the URL presenter. */ FIRAuth *_auth; /** @var _callbackScheme @brief The callback URL scheme used for headful-lite sign-in. */ NSString *_callbackScheme; } + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID IDToken:(NSString *)IDToken accessToken:(nullable NSString *)accessToken pendingToken:(nullable NSString *)pendingToken { return [[FIROAuthCredential alloc] initWithProviderID:providerID IDToken:IDToken accessToken:accessToken pendingToken:pendingToken]; } + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID IDToken:(NSString *)IDToken accessToken:(nullable NSString *)accessToken { return [[FIROAuthCredential alloc] initWithProviderID:providerID IDToken:IDToken accessToken:accessToken pendingToken:nil]; } + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID accessToken:(NSString *)accessToken { return [[FIROAuthCredential alloc] initWithProviderID:providerID IDToken:nil accessToken:accessToken pendingToken:nil]; } + (instancetype)providerWithProviderID:(NSString *)providerID { return [[self alloc]initWithProviderID:providerID auth:[FIRAuth auth]]; } + (instancetype)providerWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth { return [[self alloc] initWithProviderID:providerID auth:auth]; } #if TARGET_OS_IOS - (void)getCredentialWithUIDelegate:(nullable id)UIDelegate completion:(nullable FIRAuthCredentialCallback)completion { if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:self->_callbackScheme]) { [NSException raise:NSInternalInconsistencyException format:@"Please register custom URL scheme '%@' in the app's Info.plist file.", self->_callbackScheme]; } __weak __typeof__(self) weakSelf = self; __weak FIRAuth *weakAuth = _auth; __weak NSString *weakProviderID = _providerID; dispatch_async(FIRAuthGlobalWorkQueue(), ^{ FIRAuthCredentialCallback callbackOnMainThread = ^(FIRAuthCredential *_Nullable credential, NSError *_Nullable error) { if (completion) { dispatch_async(dispatch_get_main_queue(), ^{ completion(credential, error); }); } }; NSString *eventID = [FIRAuthWebUtils randomStringWithLength:10]; NSString *sessionID = [FIRAuthWebUtils randomStringWithLength:10]; __strong __typeof__(self) strongSelf = weakSelf; [strongSelf getHeadFulLiteURLWithEventID:eventID sessionID:sessionID completion:^(NSURL *_Nullable headfulLiteURL, NSError *_Nullable error) { if (error) { callbackOnMainThread(nil, error); return; } FIRAuthURLCallbackMatcher callbackMatcher = ^BOOL(NSURL *_Nullable callbackURL) { return [FIRAuthWebUtils isExpectedCallbackURL:callbackURL eventID:eventID authType:kAuthTypeSignInWithRedirect callbackScheme:strongSelf->_callbackScheme]; }; __strong FIRAuth *strongAuth = weakAuth; [strongAuth.authURLPresenter presentURL:headfulLiteURL UIDelegate:UIDelegate callbackMatcher:callbackMatcher completion:^(NSURL *_Nullable callbackURL, NSError *_Nullable error) { if (error) { callbackOnMainThread(nil, error); return; } NSString *OAuthResponseURLString = [strongSelf OAuthResponseForURL:callbackURL error:&error]; if (error) { callbackOnMainThread(nil, error); return; } __strong NSString *strongProviderID = weakProviderID; FIROAuthCredential *credential = [[FIROAuthCredential alloc] initWithProviderID:strongProviderID sessionID:sessionID OAuthResponseURLString:OAuthResponseURLString]; callbackOnMainThread(credential, nil); }]; }]; }); } #endif // TARGET_OS_IOS #pragma mark - Internal Methods /** @fn initWithProviderID:auth: @brief returns an instance of @c FIROAuthProvider assocaited with the provided auth instance. @param auth The Auth instance to be associated with the OAuthProvider instance. @return An Instance of @c FIROAuthProvider. */ - (nullable instancetype)initWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth { self = [super init]; if (self) { _auth = auth; _providerID = providerID; _callbackScheme = [[[_auth.app.options.clientID componentsSeparatedByString:@"."] reverseObjectEnumerator].allObjects componentsJoinedByString:@"."]; } return self; } /** @fn OAuthResponseForURL:error: @brief Parses the redirected URL and returns a string representation of the OAuth response URL. @param URL The url to be parsed for an OAuth response URL. @param error The error that occurred if any. @return The OAuth response if successful. */ - (nullable NSString *)OAuthResponseForURL:(NSURL *)URL error:(NSError *_Nullable *_Nullable)error { NSDictionary *URLQueryItems = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:URL.query]; NSURL *deepLinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]]; URLQueryItems = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:deepLinkURL.query]; NSString *queryItemLink = URLQueryItems[@"link"]; if (queryItemLink) { return queryItemLink; } if (!error) { return nil; } NSData *errorData = [URLQueryItems[@"firebaseError"] dataUsingEncoding:NSUTF8StringEncoding]; NSError *jsonError; NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData options:0 error:&jsonError]; if (jsonError) { *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError]; return nil; } *error = [FIRAuthErrorUtils URLResponseErrorWithCode:errorDict[@"code"] message:errorDict[@"message"]]; if (!*error) { NSString *reason; if(errorDict[@"code"] && errorDict[@"message"]) { reason = [NSString stringWithFormat:@"[%@] - %@",errorDict[@"code"], errorDict[@"message"]]; } *error = [FIRAuthErrorUtils webSignInUserInteractionFailureWithReason:reason]; } return nil; } /** @fn getHeadFulLiteURLWithEventID:completion: @brief Constructs a URL used for opening a headful-lite flow using a given event ID and session ID. @param eventID The event ID used for this purpose. @param sessionID The session ID used when completing the headful lite flow. @param completion The callback invoked after the URL has been constructed or an error has been encountered. */ - (void)getHeadFulLiteURLWithEventID:(NSString *)eventID sessionID:(NSString *)sessionID completion:(FIRHeadfulLiteURLCallBack)completion { __weak __typeof__(self) weakSelf = self; [FIRAuthWebUtils fetchAuthDomainWithRequestConfiguration:_auth.requestConfiguration completion:^(NSString *_Nullable authDomain, NSError *_Nullable error) { if (error) { if (completion) { completion(nil, error); } return; } __strong __typeof__(self) strongSelf = weakSelf; NSString *bundleID = [NSBundle mainBundle].bundleIdentifier; NSString *clienID = strongSelf->_auth.app.options.clientID; NSString *apiKey = strongSelf->_auth.requestConfiguration.APIKey; NSMutableDictionary *urlArguments = [@{ @"apiKey" : apiKey, @"authType" : @"signInWithRedirect", @"ibi" : bundleID ?: @"", @"clientId" : clienID, @"sessionId" : [strongSelf hashforString:sessionID], @"v" : [FIRAuthBackend authUserAgent], @"eventId" : eventID, @"providerId" : strongSelf->_providerID, } mutableCopy]; if (strongSelf.scopes.count) { urlArguments[@"scopes"] = [strongSelf.scopes componentsJoinedByString:@","]; } if (strongSelf.customParameters.count) { NSString *customParameters = [strongSelf customParametersStringWithError:&error]; if (error) { completion(nil, error); return; } if (customParameters) { urlArguments[@"customParameters"] = customParameters; } } if (strongSelf->_auth.requestConfiguration.languageCode) { urlArguments[@"hl"] = strongSelf->_auth.requestConfiguration.languageCode; } NSString *argumentsString = [strongSelf httpArgumentsStringForArgsDictionary:urlArguments]; NSString *URLString = [NSString stringWithFormat:kHeadfulLiteURLStringFormat, authDomain, argumentsString]; if (completion) { NSCharacterSet *set = [NSCharacterSet URLFragmentAllowedCharacterSet]; completion([NSURL URLWithString: [URLString stringByAddingPercentEncodingWithAllowedCharacters:set]], nil); } }]; } /** @fn customParametersString @brief Returns a JSON string representation of the custom parameters dictionary corresponding to the OAuthProvider. @return The JSON string representation of the custom parameters dictionary corresponding to the OAuthProvider. */ - (nullable NSString *)customParametersStringWithError:(NSError *_Nullable *_Nullable)error { if (!_customParameters.count) { return nil; } if (!error) { return nil; } NSError *jsonError; NSData *customParametersJSONData = [NSJSONSerialization dataWithJSONObject:_customParameters options:0 error:&jsonError]; if (jsonError) { *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError]; return nil; } NSString *customParamsRawJSON = [[NSString alloc] initWithData:customParametersJSONData encoding:NSUTF8StringEncoding]; return customParamsRawJSON; } /** @fn hashforString: @brief Returns the SHA256 hash representaion of a given string object. @param string The string for which a SHA256 hash is desired. @return An hexadecimal string representation of the SHA256 hash. */ - (NSString *)hashforString:(NSString *)string { NSData *sessionIDData = [string dataUsingEncoding:NSUTF8StringEncoding]; NSMutableData *hashOutputData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; if (CC_SHA256(sessionIDData.bytes, (CC_LONG)[sessionIDData length], hashOutputData.mutableBytes)) { } return [self hexStringFromData:hashOutputData];; } /** @fn hexStringFromData: @brief Returns the hexadecimal string representation of an NSData object. @param data The NSData object for which a hexadecical string is desired. @return The hexadecimal string representation of the supplied NSData object. */ - (NSString *)hexStringFromData:(NSData *)data { const unsigned char *dataBuffer = (const unsigned char *)[data bytes]; NSMutableString *string = [[NSMutableString alloc] init]; for (unsigned int i = 0; i < data.length; i++){ [string appendFormat:@"%02lx", (unsigned long)dataBuffer[i]]; } return [string copy]; } - (NSString *)httpArgumentsStringForArgsDictionary:(NSDictionary *)argsDictionary { NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:argsDictionary.count]; NSString* key; for (key in argsDictionary) { NSString *description = [argsDictionary[key] description]; [arguments addObject:[NSString stringWithFormat:@"%@=%@", [FIRAuthWebUtils stringByUnescapingFromURLArgument:key], [FIRAuthWebUtils stringByUnescapingFromURLArgument:description]]] ; } return [arguments componentsJoinedByString:@"&"]; } @end NS_ASSUME_NONNULL_END