FIROAuthProvider.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. /*
  2. * Copyright 2017 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/Public/FirebaseAuth/FIROAuthProvider.h"
  17. #include <CommonCrypto/CommonCrypto.h>
  18. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRFacebookAuthProvider.h"
  19. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIROAuthCredential.h"
  20. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  21. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  22. #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h"
  23. #import "FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential_Internal.h"
  24. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
  25. #import "FirebaseAuth/Sources/Backend/FIRAuthRequestConfiguration.h"
  26. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
  27. #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
  28. #if TARGET_OS_IOS
  29. #import "FirebaseAuth/Sources/Utilities/FIRAuthURLPresenter.h"
  30. #endif
  31. NS_ASSUME_NONNULL_BEGIN
  32. /** @typedef FIRHeadfulLiteURLCallBack
  33. @brief The callback invoked at the end of the flow to fetch a headful-lite URL.
  34. @param headfulLiteURL The headful lite URL.
  35. @param error The error that occurred while fetching the headful-lite, if any.
  36. */
  37. typedef void (^FIRHeadfulLiteURLCallBack)(NSURL *_Nullable headfulLiteURL,
  38. NSError *_Nullable error);
  39. /** @var kHeadfulLiteURLStringFormat
  40. @brief The format of the URL used to open the headful lite page during sign-in.
  41. */
  42. NSString *const kHeadfulLiteURLStringFormat = @"https://%@/__/auth/handler?%@";
  43. /** @var kHeadfulLiteEmulatorURLStringFormat
  44. @brief The format of the URL used to open the emulated headful lite page during sign-in.
  45. */
  46. NSString *const kHeadfulLiteEmulatorURLStringFormat = @"http://%@/emulator/auth/handler?%@";
  47. /** @var kauthTypeSignInWithRedirect
  48. @brief The auth type to be specified in the sign-in request with redirect request and response.
  49. */
  50. static NSString *const kAuthTypeSignInWithRedirect = @"signInWithRedirect";
  51. /** @var kCustomUrlSchemePrefix
  52. @brief The prefix to append to the Firebase app ID custom callback scheme..
  53. */
  54. static NSString *const kCustomUrlSchemePrefix = @"app-";
  55. @implementation FIROAuthProvider {
  56. /** @var _auth
  57. @brief The auth instance used for launching the URL presenter.
  58. */
  59. FIRAuth *_auth;
  60. /** @var _callbackScheme
  61. @brief The callback URL scheme used for headful-lite sign-in.
  62. */
  63. NSString *_callbackScheme;
  64. /** @var _usingClientIDScheme
  65. @brief True if the reverse client ID is registered as a custom URL scheme, and false
  66. otherwise.
  67. */
  68. BOOL _usingClientIDScheme;
  69. }
  70. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  71. IDToken:(NSString *)IDToken
  72. accessToken:(nullable NSString *)accessToken {
  73. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  74. IDToken:IDToken
  75. rawNonce:nil
  76. accessToken:accessToken
  77. secret:nil
  78. pendingToken:nil];
  79. }
  80. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  81. accessToken:(NSString *)accessToken {
  82. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  83. IDToken:nil
  84. rawNonce:nil
  85. accessToken:accessToken
  86. secret:nil
  87. pendingToken:nil];
  88. }
  89. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  90. IDToken:(NSString *)IDToken
  91. rawNonce:(nullable NSString *)rawNonce
  92. accessToken:(nullable NSString *)accessToken {
  93. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  94. IDToken:IDToken
  95. rawNonce:rawNonce
  96. accessToken:accessToken
  97. secret:nil
  98. pendingToken:nil];
  99. }
  100. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  101. IDToken:(NSString *)IDToken
  102. rawNonce:(nullable NSString *)rawNonce {
  103. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  104. IDToken:IDToken
  105. rawNonce:rawNonce
  106. accessToken:nil
  107. secret:nil
  108. pendingToken:nil];
  109. }
  110. + (FIROAuthCredential *)credentialWithProviderID:(NSString *)providerID
  111. IDToken:(NSString *)IDToken
  112. rawNonce:(nullable NSString *)rawNonce
  113. accessToken:(nullable NSString *)accessToken
  114. displayName:(nonnull NSString *)displayName{
  115. return [[FIROAuthCredential alloc] initWithProviderID:providerID
  116. IDToken:IDToken
  117. rawNonce:rawNonce
  118. accessToken:accessToken
  119. secret:nil
  120. displayName:displayName
  121. pendingToken:nil];
  122. }
  123. + (instancetype)providerWithProviderID:(NSString *)providerID {
  124. return [[self alloc] initWithProviderID:providerID auth:[FIRAuth auth]];
  125. }
  126. + (instancetype)providerWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth {
  127. return [[self alloc] initWithProviderID:providerID auth:auth];
  128. }
  129. #if TARGET_OS_IOS
  130. - (void)getCredentialWithUIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  131. completion:(nullable FIRAuthCredentialCallback)completion {
  132. if (![FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:self->_callbackScheme]) {
  133. [NSException raise:NSInternalInconsistencyException
  134. format:@"Please register custom URL scheme '%@' in the app's Info.plist file.",
  135. self->_callbackScheme];
  136. }
  137. __weak __typeof__(self) weakSelf = self;
  138. __weak FIRAuth *weakAuth = _auth;
  139. __weak NSString *weakProviderID = _providerID;
  140. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  141. FIRAuthCredentialCallback callbackOnMainThread =
  142. ^(FIRAuthCredential *_Nullable credential, NSError *_Nullable error) {
  143. if (completion) {
  144. dispatch_async(dispatch_get_main_queue(), ^{
  145. completion(credential, error);
  146. });
  147. }
  148. };
  149. NSString *eventID = [FIRAuthWebUtils randomStringWithLength:10];
  150. NSString *sessionID = [FIRAuthWebUtils randomStringWithLength:10];
  151. __strong __typeof__(self) strongSelf = weakSelf;
  152. [strongSelf
  153. getHeadFulLiteURLWithEventID:eventID
  154. sessionID:sessionID
  155. completion:^(NSURL *_Nullable headfulLiteURL, NSError *_Nullable error) {
  156. if (error) {
  157. callbackOnMainThread(nil, error);
  158. return;
  159. }
  160. FIRAuthURLCallbackMatcher callbackMatcher =
  161. ^BOOL(NSURL *_Nullable callbackURL) {
  162. return [FIRAuthWebUtils
  163. isExpectedCallbackURL:callbackURL
  164. eventID:eventID
  165. authType:kAuthTypeSignInWithRedirect
  166. callbackScheme:strongSelf->_callbackScheme];
  167. };
  168. __strong FIRAuth *strongAuth = weakAuth;
  169. [strongAuth.authURLPresenter
  170. presentURL:headfulLiteURL
  171. UIDelegate:UIDelegate
  172. callbackMatcher:callbackMatcher
  173. completion:^(NSURL *_Nullable callbackURL,
  174. NSError *_Nullable error) {
  175. if (error) {
  176. callbackOnMainThread(nil, error);
  177. return;
  178. }
  179. NSString *OAuthResponseURLString =
  180. [strongSelf OAuthResponseForURL:callbackURL
  181. error:&error];
  182. if (error) {
  183. callbackOnMainThread(nil, error);
  184. return;
  185. }
  186. __strong NSString *strongProviderID = weakProviderID;
  187. FIROAuthCredential *credential = [[FIROAuthCredential alloc]
  188. initWithProviderID:strongProviderID
  189. sessionID:sessionID
  190. OAuthResponseURLString:OAuthResponseURLString];
  191. callbackOnMainThread(credential, nil);
  192. }];
  193. }];
  194. });
  195. }
  196. #endif // TARGET_OS_IOS
  197. #pragma mark - Internal Methods
  198. /** @fn initWithProviderID:auth:
  199. @brief returns an instance of @c FIROAuthProvider associated with the provided auth instance.
  200. @param auth The Auth instance to be associated with the OAuthProvider instance.
  201. @return An Instance of @c FIROAuthProvider.
  202. */
  203. - (nullable instancetype)initWithProviderID:(NSString *)providerID auth:(FIRAuth *)auth {
  204. if (!auth.requestConfiguration.emulatorHostAndPort) {
  205. NSAssert(![providerID isEqual:FIRFacebookAuthProviderID],
  206. @"Sign in with Facebook is not supported via generic IDP; the Facebook TOS "
  207. "dictate that you must use the Facebook iOS SDK for Facebook login.");
  208. NSAssert(![providerID isEqual:@"apple.com"],
  209. @"Sign in with Apple is not supported via generic IDP; You must use the Apple iOS SDK"
  210. " for Sign in with Apple.");
  211. }
  212. self = [super init];
  213. if (self) {
  214. _auth = auth;
  215. _providerID = providerID;
  216. if (_auth.app.options.clientID) {
  217. NSString *reverseClientIDScheme =
  218. [[[_auth.app.options.clientID componentsSeparatedByString:@"."]
  219. reverseObjectEnumerator].allObjects componentsJoinedByString:@"."];
  220. if ([FIRAuthWebUtils isCallbackSchemeRegisteredForCustomURLScheme:reverseClientIDScheme]) {
  221. _callbackScheme = reverseClientIDScheme;
  222. _usingClientIDScheme = YES;
  223. }
  224. }
  225. if (!_usingClientIDScheme) {
  226. _callbackScheme = [kCustomUrlSchemePrefix
  227. stringByAppendingString:[_auth.app.options.googleAppID
  228. stringByReplacingOccurrencesOfString:@":"
  229. withString:@"-"]];
  230. }
  231. }
  232. return self;
  233. }
  234. /** @fn OAuthResponseForURL:error:
  235. @brief Parses the redirected URL and returns a string representation of the OAuth response URL.
  236. @param URL The url to be parsed for an OAuth response URL.
  237. @param error The error that occurred if any.
  238. @return The OAuth response if successful.
  239. */
  240. - (nullable NSString *)OAuthResponseForURL:(NSURL *)URL error:(NSError *_Nullable *_Nullable)error {
  241. NSDictionary<NSString *, NSString *> *URLQueryItems =
  242. [FIRAuthWebUtils dictionaryWithHttpArgumentsString:URL.query];
  243. NSURL *deepLinkURL = [NSURL URLWithString:URLQueryItems[@"deep_link_id"]];
  244. URLQueryItems = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:deepLinkURL.query];
  245. NSString *queryItemLink = URLQueryItems[@"link"];
  246. if (queryItemLink) {
  247. return queryItemLink;
  248. }
  249. if (!error) {
  250. return nil;
  251. }
  252. NSData *errorData = [URLQueryItems[@"firebaseError"] dataUsingEncoding:NSUTF8StringEncoding];
  253. NSError *jsonError;
  254. NSDictionary *errorDict = [NSJSONSerialization JSONObjectWithData:errorData
  255. options:0
  256. error:&jsonError];
  257. if (jsonError) {
  258. *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError];
  259. return nil;
  260. }
  261. *error = [FIRAuthErrorUtils URLResponseErrorWithCode:errorDict[@"code"]
  262. message:errorDict[@"message"]];
  263. if (!*error) {
  264. NSString *reason;
  265. if (errorDict[@"code"] && errorDict[@"message"]) {
  266. reason = [NSString stringWithFormat:@"[%@] - %@", errorDict[@"code"], errorDict[@"message"]];
  267. }
  268. *error = [FIRAuthErrorUtils webSignInUserInteractionFailureWithReason:reason];
  269. }
  270. return nil;
  271. }
  272. /** @fn getHeadFulLiteURLWithEventID:completion:
  273. @brief Constructs a URL used for opening a headful-lite flow using a given event
  274. ID and session ID.
  275. @param eventID The event ID used for this purpose.
  276. @param sessionID The session ID used when completing the headful lite flow.
  277. @param completion The callback invoked after the URL has been constructed or an error
  278. has been encountered.
  279. */
  280. - (void)getHeadFulLiteURLWithEventID:(NSString *)eventID
  281. sessionID:(NSString *)sessionID
  282. completion:(FIRHeadfulLiteURLCallBack)completion {
  283. __weak __typeof__(self) weakSelf = self;
  284. [FIRAuthWebUtils
  285. fetchAuthDomainWithRequestConfiguration:_auth.requestConfiguration
  286. completion:^(NSString *_Nullable authDomain,
  287. NSError *_Nullable error) {
  288. if (error) {
  289. if (completion) {
  290. completion(nil, error);
  291. }
  292. return;
  293. }
  294. __strong __typeof__(self) strongSelf = weakSelf;
  295. NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
  296. NSString *clientID = strongSelf->_auth.app.options.clientID;
  297. NSString *appID = strongSelf->_auth.app.options.googleAppID;
  298. NSString *apiKey =
  299. strongSelf->_auth.requestConfiguration.APIKey;
  300. NSString *tenantID = strongSelf->_auth.tenantID;
  301. NSMutableDictionary *urlArguments = [@{
  302. @"apiKey" : apiKey,
  303. @"authType" : kAuthTypeSignInWithRedirect,
  304. @"ibi" : bundleID ?: @"",
  305. @"sessionId" : [strongSelf hashforString:sessionID],
  306. @"v" : [FIRAuthBackend authUserAgent],
  307. @"eventId" : eventID,
  308. @"providerId" : strongSelf->_providerID,
  309. } mutableCopy];
  310. if (strongSelf->_usingClientIDScheme) {
  311. urlArguments[@"clientId"] = clientID;
  312. } else {
  313. urlArguments[@"appId"] = appID;
  314. }
  315. if (tenantID) {
  316. urlArguments[@"tid"] = tenantID;
  317. }
  318. if (strongSelf.scopes.count) {
  319. urlArguments[@"scopes"] =
  320. [strongSelf.scopes componentsJoinedByString:@","];
  321. }
  322. if (strongSelf.customParameters.count) {
  323. NSString *customParameters =
  324. [strongSelf customParametersStringWithError:&error];
  325. if (error) {
  326. completion(nil, error);
  327. return;
  328. }
  329. if (customParameters) {
  330. urlArguments[@"customParameters"] = customParameters;
  331. }
  332. }
  333. if (strongSelf->_auth.requestConfiguration.languageCode) {
  334. urlArguments[@"hl"] =
  335. strongSelf->_auth.requestConfiguration.languageCode;
  336. }
  337. NSString *argumentsString = [strongSelf
  338. httpArgumentsStringForArgsDictionary:urlArguments];
  339. NSString *URLString;
  340. if (strongSelf->_auth.requestConfiguration
  341. .emulatorHostAndPort) {
  342. URLString = [NSString
  343. stringWithFormat:kHeadfulLiteEmulatorURLStringFormat,
  344. authDomain, argumentsString];
  345. } else {
  346. URLString =
  347. [NSString stringWithFormat:kHeadfulLiteURLStringFormat,
  348. authDomain, argumentsString];
  349. }
  350. if (completion) {
  351. NSCharacterSet *set =
  352. [NSCharacterSet URLFragmentAllowedCharacterSet];
  353. completion(
  354. [NSURL
  355. URLWithString:
  356. [URLString
  357. stringByAddingPercentEncodingWithAllowedCharacters:
  358. set]],
  359. nil);
  360. }
  361. }];
  362. }
  363. /** @fn customParametersString
  364. @brief Returns a JSON string representation of the custom parameters dictionary corresponding
  365. to the OAuthProvider.
  366. @return The JSON string representation of the custom parameters dictionary corresponding
  367. to the OAuthProvider.
  368. */
  369. - (nullable NSString *)customParametersStringWithError:(NSError *_Nullable *_Nullable)error {
  370. if (!_customParameters.count) {
  371. return nil;
  372. }
  373. if (!error) {
  374. return nil;
  375. }
  376. NSError *jsonError;
  377. NSData *customParametersJSONData = [NSJSONSerialization dataWithJSONObject:_customParameters
  378. options:0
  379. error:&jsonError];
  380. if (jsonError) {
  381. *error = [FIRAuthErrorUtils JSONSerializationErrorWithUnderlyingError:jsonError];
  382. return nil;
  383. }
  384. NSString *customParamsRawJSON = [[NSString alloc] initWithData:customParametersJSONData
  385. encoding:NSUTF8StringEncoding];
  386. return customParamsRawJSON;
  387. }
  388. /** @fn hashforString:
  389. @brief Returns the SHA256 hash representation of a given string object.
  390. @param string The string for which a SHA256 hash is desired.
  391. @return An hexadecimal string representation of the SHA256 hash.
  392. */
  393. - (NSString *)hashforString:(NSString *)string {
  394. NSData *sessionIDData = [string dataUsingEncoding:NSUTF8StringEncoding];
  395. NSMutableData *hashOutputData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
  396. if (CC_SHA256(sessionIDData.bytes, (CC_LONG)[sessionIDData length],
  397. hashOutputData.mutableBytes)) {
  398. }
  399. return [self hexStringFromData:hashOutputData];
  400. ;
  401. }
  402. /** @fn hexStringFromData:
  403. @brief Returns the hexadecimal string representation of an NSData object.
  404. @param data The NSData object for which a hexadecical string is desired.
  405. @return The hexadecimal string representation of the supplied NSData object.
  406. */
  407. - (NSString *)hexStringFromData:(NSData *)data {
  408. const unsigned char *dataBuffer = (const unsigned char *)[data bytes];
  409. NSMutableString *string = [[NSMutableString alloc] init];
  410. for (unsigned int i = 0; i < data.length; i++) {
  411. [string appendFormat:@"%02lx", (unsigned long)dataBuffer[i]];
  412. }
  413. return [string copy];
  414. }
  415. - (NSString *)httpArgumentsStringForArgsDictionary:(NSDictionary *)argsDictionary {
  416. NSMutableArray *arguments = [NSMutableArray arrayWithCapacity:argsDictionary.count];
  417. NSString *key;
  418. for (key in argsDictionary) {
  419. NSString *description = [argsDictionary[key] description];
  420. [arguments
  421. addObject:[NSString
  422. stringWithFormat:@"%@=%@",
  423. [FIRAuthWebUtils stringByUnescapingFromURLArgument:key],
  424. [FIRAuthWebUtils
  425. stringByUnescapingFromURLArgument:description]]];
  426. }
  427. return [arguments componentsJoinedByString:@"&"];
  428. }
  429. @end
  430. NS_ASSUME_NONNULL_END