FIRAuthWebUtils.m 8.2 KB

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