FIRAuthWebUtils.m 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FIRAuthWebUtils.h"
  17. #import "FIRAuthBackend.h"
  18. #import "FIRAuthErrorUtils.h"
  19. #import "FIRGetProjectConfigRequest.h"
  20. #import "FIRGetProjectConfigResponse.h"
  21. NS_ASSUME_NONNULL_BEGIN
  22. @implementation FIRAuthWebUtils
  23. + (NSArray<NSString *> *)supportedAuthDomains {
  24. return @[@"firebaseapp.com", @"web.app"];
  25. }
  26. + (NSString *)randomStringWithLength:(NSUInteger)length {
  27. NSMutableString *randomString = [[NSMutableString alloc] init];
  28. for (int i=0; i < length; i++) {
  29. [randomString appendString:
  30. [NSString stringWithFormat:@"%c", 'a' + arc4random_uniform('z' - 'a' + 1)]];
  31. }
  32. return randomString;
  33. }
  34. + (BOOL)isCallbackSchemeRegisteredForCustomURLScheme:(NSString *)URLScheme {
  35. NSString *expectedCustomScheme = [URLScheme lowercaseString];
  36. NSArray *urlTypes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"];
  37. for (NSDictionary *urlType in urlTypes) {
  38. NSArray *urlTypeSchemes = urlType[@"CFBundleURLSchemes"];
  39. for (NSString *urlTypeScheme in urlTypeSchemes) {
  40. if ([urlTypeScheme.lowercaseString isEqualToString:expectedCustomScheme]) {
  41. return YES;
  42. }
  43. }
  44. }
  45. return NO;
  46. }
  47. + (BOOL)isExpectedCallbackURL:(nullable NSURL *)URL
  48. eventID:(NSString *)eventID
  49. authType:(NSString *)authType
  50. callbackScheme:(NSString *)callbackScheme {
  51. if (!URL) {
  52. return NO;
  53. }
  54. NSURLComponents *actualURLComponents =
  55. [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO];
  56. actualURLComponents.query = nil;
  57. actualURLComponents.fragment = nil;
  58. NSURLComponents *expectedURLComponents = [[NSURLComponents alloc] init];
  59. expectedURLComponents.scheme = callbackScheme;
  60. expectedURLComponents.host = @"firebaseauth";
  61. expectedURLComponents.path = @"/link";
  62. if (![expectedURLComponents.URL isEqual:actualURLComponents.URL]) {
  63. return NO;
  64. }
  65. NSDictionary<NSString *, NSString *> *URLQueryItems =
  66. [self dictionaryWithHttpArgumentsString:URL.query];
  67. NSURL *deeplinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]];
  68. NSDictionary<NSString *, NSString *> *deeplinkQueryItems =
  69. [self dictionaryWithHttpArgumentsString:deeplinkURL.query];
  70. if ([deeplinkQueryItems[@"authType"] isEqualToString:authType] &&
  71. [deeplinkQueryItems[@"eventId"] isEqualToString:eventID]) {
  72. return YES;
  73. }
  74. return NO;
  75. }
  76. + (void)fetchAuthDomainWithRequestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration
  77. completion:(FIRFetchAuthDomainCallback)completion {
  78. FIRGetProjectConfigRequest *request =
  79. [[FIRGetProjectConfigRequest alloc] initWithRequestConfiguration:requestConfiguration];
  80. [FIRAuthBackend getProjectConfig:request
  81. callback:^(FIRGetProjectConfigResponse *_Nullable response,
  82. NSError *_Nullable error) {
  83. if (error) {
  84. completion(nil, error);
  85. return;
  86. }
  87. NSString *authDomain;
  88. for (NSString *domain in response.authorizedDomains) {
  89. for (NSString *suportedAuthDomain in [self supportedAuthDomains]) {
  90. NSInteger index = domain.length - suportedAuthDomain.length;
  91. if (index >= 2) {
  92. if ([domain hasSuffix:suportedAuthDomain] && domain.length >= suportedAuthDomain.length + 2) {
  93. authDomain = domain;
  94. break;
  95. }
  96. }
  97. }
  98. }
  99. if (!authDomain.length) {
  100. completion(nil, [FIRAuthErrorUtils unexpectedErrorResponseWithDeserializedResponse:response]);
  101. return;
  102. }
  103. completion(authDomain, nil);
  104. }];
  105. }
  106. /** @fn queryItemValue:from:
  107. @brief Utility function to get a value from a NSURLQueryItem array.
  108. @param name The key.
  109. @param queryList The NSURLQueryItem array.
  110. @return The value for the key.
  111. */
  112. + (nullable NSString *)queryItemValue:(NSString *)name from:(NSArray<NSURLQueryItem *> *)queryList {
  113. for (NSURLQueryItem *item in queryList) {
  114. if ([item.name isEqualToString:name]) {
  115. return item.value;
  116. }
  117. }
  118. return nil;
  119. }
  120. + (NSDictionary *)dictionaryWithHttpArgumentsString:(NSString *)argString {
  121. NSMutableDictionary* ret = [NSMutableDictionary dictionary];
  122. NSArray* components = [argString componentsSeparatedByString:@"&"];
  123. NSString* component;
  124. // Use reverse order so that the first occurrence of a key replaces
  125. // those subsequent.
  126. for (component in [components reverseObjectEnumerator]) {
  127. if (component.length == 0)
  128. continue;
  129. NSRange pos = [component rangeOfString:@"="];
  130. NSString *key;
  131. NSString *val;
  132. if (pos.location == NSNotFound) {
  133. key = [self stringByUnescapingFromURLArgument:component];
  134. val = @"";
  135. } else {
  136. key = [self stringByUnescapingFromURLArgument:[component substringToIndex:pos.location]];
  137. val = [self stringByUnescapingFromURLArgument:
  138. [component substringFromIndex:pos.location + pos.length]];
  139. }
  140. // returns nil on invalid UTF8 and NSMutableDictionary raises an exception when passed nil
  141. // values.
  142. if (!key) key = @"";
  143. if (!val) val = @"";
  144. [ret setObject:val forKey:key];
  145. }
  146. return ret;
  147. }
  148. + (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument {
  149. NSMutableString *resultString = [NSMutableString stringWithString:argument];
  150. [resultString replaceOccurrencesOfString:@"+"
  151. withString:@" "
  152. options:NSLiteralSearch
  153. range:NSMakeRange(0, [resultString length])];
  154. return [resultString stringByRemovingPercentEncoding];
  155. }
  156. + (NSDictionary<NSString *, NSString *> *)parseURL:(NSString *)urlString {
  157. NSString *linkURL = [NSURLComponents componentsWithString:urlString].query;
  158. if (!linkURL) {
  159. return @{};
  160. }
  161. NSArray<NSString *> *URLComponents = [linkURL componentsSeparatedByString:@"&"];
  162. NSMutableDictionary<NSString *, NSString *> *queryItems =
  163. [[NSMutableDictionary alloc] initWithCapacity:URLComponents.count];
  164. for (NSString *component in URLComponents) {
  165. NSRange equalRange = [component rangeOfString:@"="];
  166. if (equalRange.location != NSNotFound) {
  167. NSString *queryItemKey =
  168. [[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding];
  169. NSString *queryItemValue =
  170. [[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding];
  171. if (queryItemKey && queryItemValue) {
  172. queryItems[queryItemKey] = queryItemValue;
  173. }
  174. }
  175. }
  176. return queryItems;
  177. }
  178. @end
  179. NS_ASSUME_NONNULL_END