FIROAuthProvider.m 19 KB

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