FIROAuthProvider.m 19 KB

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