Files
firebase-ios-sdk/Firebase/Auth/Source/Utilities/FIRAuthWebUtils.m
T

204 lines
7.3 KiB
Objective-C

/*
* 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 "FIRAuthBackend.h"
#import "FIRAuthErrorUtils.h"
#import "FIRGetProjectConfigRequest.h"
#import "FIRGetProjectConfigResponse.h"
NS_ASSUME_NONNULL_BEGIN
@implementation FIRAuthWebUtils
+ (NSArray<NSString *> *)supportedAuthDomains {
return @[@"firebaseapp.com", @"web.app"];
}
+ (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:(nullable 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 alloc] init];
expectedURLComponents.scheme = callbackScheme;
expectedURLComponents.host = @"firebaseauth";
expectedURLComponents.path = @"/link";
if (![expectedURLComponents.URL isEqual:actualURLComponents.URL]) {
return NO;
}
NSDictionary<NSString *, NSString *> *URLQueryItems =
[self dictionaryWithHttpArgumentsString:URL.query];
NSURL *deeplinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]];
NSDictionary<NSString *, NSString *> *deeplinkQueryItems =
[self 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;
}
// Look up an authorized domain ends with one of the supportedAuthDomains.
// The sequence of supportedAuthDomains matters. ("firebaseapp.com", "web.app")
// The searching ends once the first valid suportedAuthDomain is found.
NSString *authDomain;
for (NSString *domain in response.authorizedDomains) {
for (NSString *suportedAuthDomain in [self supportedAuthDomains]) {
NSInteger index = domain.length - suportedAuthDomain.length;
if (index >= 2) {
if ([domain hasSuffix:suportedAuthDomain] && domain.length >= suportedAuthDomain.length + 2) {
authDomain = domain;
break;
}
}
}
if (authDomain != nil) {
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.
*/
+ (nullable NSString *)queryItemValue:(NSString *)name from:(NSArray<NSURLQueryItem *> *)queryList {
for (NSURLQueryItem *item in queryList) {
if ([item.name isEqualToString:name]) {
return item.value;
}
}
return nil;
}
+ (NSDictionary *)dictionaryWithHttpArgumentsString:(NSString *)argString {
NSMutableDictionary* ret = [NSMutableDictionary dictionary];
NSArray* components = [argString componentsSeparatedByString:@"&"];
NSString* component;
// Use reverse order so that the first occurrence of a key replaces
// those subsequent.
for (component in [components reverseObjectEnumerator]) {
if (component.length == 0)
continue;
NSRange pos = [component rangeOfString:@"="];
NSString *key;
NSString *val;
if (pos.location == NSNotFound) {
key = [self stringByUnescapingFromURLArgument:component];
val = @"";
} else {
key = [self stringByUnescapingFromURLArgument:[component substringToIndex:pos.location]];
val = [self stringByUnescapingFromURLArgument:
[component substringFromIndex:pos.location + pos.length]];
}
// returns nil on invalid UTF8 and NSMutableDictionary raises an exception when passed nil
// values.
if (!key) key = @"";
if (!val) val = @"";
[ret setObject:val forKey:key];
}
return ret;
}
+ (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument {
NSMutableString *resultString = [NSMutableString stringWithString:argument];
[resultString replaceOccurrencesOfString:@"+"
withString:@" "
options:NSLiteralSearch
range:NSMakeRange(0, [resultString length])];
return [resultString stringByRemovingPercentEncoding];
}
+ (NSDictionary<NSString *, NSString *> *)parseURL:(NSString *)urlString {
NSString *linkURL = [NSURLComponents componentsWithString:urlString].query;
if (!linkURL) {
return @{};
}
NSArray<NSString *> *URLComponents = [linkURL componentsSeparatedByString:@"&"];
NSMutableDictionary<NSString *, NSString *> *queryItems =
[[NSMutableDictionary alloc] initWithCapacity:URLComponents.count];
for (NSString *component in URLComponents) {
NSRange equalRange = [component rangeOfString:@"="];
if (equalRange.location != NSNotFound) {
NSString *queryItemKey =
[[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding];
NSString *queryItemValue =
[[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding];
if (queryItemKey && queryItemValue) {
queryItems[queryItemKey] = queryItemValue;
}
}
}
return queryItems;
}
@end
NS_ASSUME_NONNULL_END