FIRAuthWebUtils.m 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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 "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
  17. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
  18. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigRequest.h"
  19. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetProjectConfigResponse.h"
  20. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.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
  30. appendString:[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 = [NSURLComponents componentsWithURL:URL
  55. 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
  81. getProjectConfig:request
  82. callback:^(FIRGetProjectConfigResponse *_Nullable response,
  83. NSError *_Nullable error) {
  84. if (error) {
  85. completion(nil, error);
  86. return;
  87. }
  88. // Look up an authorized domain ends with one of the supportedAuthDomains.
  89. // The sequence of supportedAuthDomains matters. ("firebaseapp.com", "web.app")
  90. // The searching ends once the first valid suportedAuthDomain is found.
  91. NSString *authDomain;
  92. for (NSString *domain in response.authorizedDomains) {
  93. for (NSString *suportedAuthDomain in [self supportedAuthDomains]) {
  94. NSInteger index = domain.length - suportedAuthDomain.length;
  95. if (index >= 2) {
  96. if ([domain hasSuffix:suportedAuthDomain] &&
  97. domain.length >= suportedAuthDomain.length + 2) {
  98. authDomain = domain;
  99. break;
  100. }
  101. }
  102. }
  103. if (authDomain != nil) {
  104. break;
  105. }
  106. }
  107. if (!authDomain.length) {
  108. completion(nil, [FIRAuthErrorUtils
  109. unexpectedErrorResponseWithDeserializedResponse:response]);
  110. return;
  111. }
  112. completion(authDomain, nil);
  113. }];
  114. }
  115. /** @fn queryItemValue:from:
  116. @brief Utility function to get a value from a NSURLQueryItem array.
  117. @param name The key.
  118. @param queryList The NSURLQueryItem array.
  119. @return The value for the key.
  120. */
  121. + (nullable NSString *)queryItemValue:(NSString *)name from:(NSArray<NSURLQueryItem *> *)queryList {
  122. for (NSURLQueryItem *item in queryList) {
  123. if ([item.name isEqualToString:name]) {
  124. return item.value;
  125. }
  126. }
  127. return nil;
  128. }
  129. + (NSDictionary *)dictionaryWithHttpArgumentsString:(NSString *)argString {
  130. NSMutableDictionary *ret = [NSMutableDictionary dictionary];
  131. NSArray *components = [argString componentsSeparatedByString:@"&"];
  132. NSString *component;
  133. // Use reverse order so that the first occurrence of a key replaces
  134. // those subsequent.
  135. for (component in [components reverseObjectEnumerator]) {
  136. if (component.length == 0) continue;
  137. NSRange pos = [component rangeOfString:@"="];
  138. NSString *key;
  139. NSString *val;
  140. if (pos.location == NSNotFound) {
  141. key = [self stringByUnescapingFromURLArgument:component];
  142. val = @"";
  143. } else {
  144. key = [self stringByUnescapingFromURLArgument:[component substringToIndex:pos.location]];
  145. val = [self stringByUnescapingFromURLArgument:[component substringFromIndex:pos.location +
  146. pos.length]];
  147. }
  148. // returns nil on invalid UTF8 and NSMutableDictionary raises an exception when passed nil
  149. // values.
  150. if (!key) key = @"";
  151. if (!val) val = @"";
  152. [ret setObject:val forKey:key];
  153. }
  154. return ret;
  155. }
  156. + (NSString *)stringByUnescapingFromURLArgument:(NSString *)argument {
  157. NSMutableString *resultString = [NSMutableString stringWithString:argument];
  158. [resultString replaceOccurrencesOfString:@"+"
  159. withString:@" "
  160. options:NSLiteralSearch
  161. range:NSMakeRange(0, [resultString length])];
  162. return [resultString stringByRemovingPercentEncoding];
  163. }
  164. + (NSDictionary<NSString *, NSString *> *)parseURL:(NSString *)urlString {
  165. NSString *linkURL = [NSURLComponents componentsWithString:urlString].query;
  166. if (!linkURL) {
  167. return @{};
  168. }
  169. NSArray<NSString *> *URLComponents = [linkURL componentsSeparatedByString:@"&"];
  170. NSMutableDictionary<NSString *, NSString *> *queryItems =
  171. [[NSMutableDictionary alloc] initWithCapacity:URLComponents.count];
  172. for (NSString *component in URLComponents) {
  173. NSRange equalRange = [component rangeOfString:@"="];
  174. if (equalRange.location != NSNotFound) {
  175. NSString *queryItemKey =
  176. [[component substringToIndex:equalRange.location] stringByRemovingPercentEncoding];
  177. NSString *queryItemValue =
  178. [[component substringFromIndex:equalRange.location + 1] stringByRemovingPercentEncoding];
  179. if (queryItemKey && queryItemValue) {
  180. queryItems[queryItemKey] = queryItemValue;
  181. }
  182. }
  183. }
  184. return queryItems;
  185. }
  186. @end
  187. NS_ASSUME_NONNULL_END