FIRUser.m 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650
  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/FIRAuth.h"
  17. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIREmailAuthProvider.h"
  18. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRFederatedAuthProvider.h"
  19. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  20. #import "FirebaseAuth/Sources/Auth/FIRAuthDataResult_Internal.h"
  21. #import "FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.h"
  22. #import "FirebaseAuth/Sources/Auth/FIRAuthOperationType.h"
  23. #import "FirebaseAuth/Sources/Auth/FIRAuthSerialTaskQueue.h"
  24. #import "FirebaseAuth/Sources/Auth/FIRAuthTokenResult_Internal.h"
  25. #import "FirebaseAuth/Sources/Auth/FIRAuth_Internal.h"
  26. #import "FirebaseAuth/Sources/AuthProvider/Email/FIREmailPasswordAuthCredential.h"
  27. #import "FirebaseAuth/Sources/AuthProvider/FIRAuthCredential_Internal.h"
  28. #import "FirebaseAuth/Sources/AuthProvider/GameCenter/FIRGameCenterAuthCredential.h"
  29. #import "FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthCredential_Internal.h"
  30. #import "FirebaseAuth/Sources/Backend/FIRAuthBackend.h"
  31. #import "FirebaseAuth/Sources/Backend/FIRAuthRequestConfiguration.h"
  32. #import "FirebaseAuth/Sources/Backend/RPC/FIRDeleteAccountRequest.h"
  33. #import "FirebaseAuth/Sources/Backend/RPC/FIRDeleteAccountResponse.h"
  34. #import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInRequest.h"
  35. #import "FirebaseAuth/Sources/Backend/RPC/FIREmailLinkSignInResponse.h"
  36. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoRequest.h"
  37. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetAccountInfoResponse.h"
  38. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeRequest.h"
  39. #import "FirebaseAuth/Sources/Backend/RPC/FIRGetOOBConfirmationCodeResponse.h"
  40. #import "FirebaseAuth/Sources/Backend/RPC/FIRSetAccountInfoRequest.h"
  41. #import "FirebaseAuth/Sources/Backend/RPC/FIRSetAccountInfoResponse.h"
  42. #import "FirebaseAuth/Sources/Backend/RPC/FIRSignInWithGameCenterRequest.h"
  43. #import "FirebaseAuth/Sources/Backend/RPC/FIRSignInWithGameCenterResponse.h"
  44. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionRequest.h"
  45. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyAssertionResponse.h"
  46. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyCustomTokenRequest.h"
  47. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyCustomTokenResponse.h"
  48. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyPasswordRequest.h"
  49. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyPasswordResponse.h"
  50. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyPhoneNumberRequest.h"
  51. #import "FirebaseAuth/Sources/Backend/RPC/FIRVerifyPhoneNumberResponse.h"
  52. #import "FirebaseAuth/Sources/MultiFactor/FIRMultiFactor+Internal.h"
  53. #import "FirebaseAuth/Sources/SystemService/FIRSecureTokenService.h"
  54. #import "FirebaseAuth/Sources/User/FIRAdditionalUserInfo_Internal.h"
  55. #import "FirebaseAuth/Sources/User/FIRUserInfoImpl.h"
  56. #import "FirebaseAuth/Sources/User/FIRUserMetadata_Internal.h"
  57. #import "FirebaseAuth/Sources/User/FIRUser_Internal.h"
  58. #import "FirebaseAuth/Sources/Utilities/FIRAuthErrorUtils.h"
  59. #import "FirebaseAuth/Sources/Utilities/FIRAuthWebUtils.h"
  60. #if TARGET_OS_IOS
  61. #import "FirebaseAuth/Sources/Public/FirebaseAuth/FIRPhoneAuthProvider.h"
  62. #import "FirebaseAuth/Sources/AuthProvider/Phone/FIRPhoneAuthCredential_Internal.h"
  63. #endif
  64. NS_ASSUME_NONNULL_BEGIN
  65. /** @var kUserIDCodingKey
  66. @brief The key used to encode the user ID for NSSecureCoding.
  67. */
  68. static NSString *const kUserIDCodingKey = @"userID";
  69. /** @var kHasEmailPasswordCredentialCodingKey
  70. @brief The key used to encode the hasEmailPasswordCredential property for NSSecureCoding.
  71. */
  72. static NSString *const kHasEmailPasswordCredentialCodingKey = @"hasEmailPassword";
  73. /** @var kAnonymousCodingKey
  74. @brief The key used to encode the anonymous property for NSSecureCoding.
  75. */
  76. static NSString *const kAnonymousCodingKey = @"anonymous";
  77. /** @var kEmailCodingKey
  78. @brief The key used to encode the email property for NSSecureCoding.
  79. */
  80. static NSString *const kEmailCodingKey = @"email";
  81. /** @var kPhoneNumberCodingKey
  82. @brief The key used to encode the phoneNumber property for NSSecureCoding.
  83. */
  84. static NSString *const kPhoneNumberCodingKey = @"phoneNumber";
  85. /** @var kEmailVerifiedCodingKey
  86. @brief The key used to encode the isEmailVerified property for NSSecureCoding.
  87. */
  88. static NSString *const kEmailVerifiedCodingKey = @"emailVerified";
  89. /** @var kDisplayNameCodingKey
  90. @brief The key used to encode the displayName property for NSSecureCoding.
  91. */
  92. static NSString *const kDisplayNameCodingKey = @"displayName";
  93. /** @var kPhotoURLCodingKey
  94. @brief The key used to encode the photoURL property for NSSecureCoding.
  95. */
  96. static NSString *const kPhotoURLCodingKey = @"photoURL";
  97. /** @var kProviderDataKey
  98. @brief The key used to encode the providerData instance variable for NSSecureCoding.
  99. */
  100. static NSString *const kProviderDataKey = @"providerData";
  101. /** @var kAPIKeyCodingKey
  102. @brief The key used to encode the APIKey instance variable for NSSecureCoding.
  103. */
  104. static NSString *const kAPIKeyCodingKey = @"APIKey";
  105. /** @var kFirebaseAppIDCodingKey
  106. @brief The key used to encode the appID instance variable for NSSecureCoding.
  107. */
  108. static NSString *const kFirebaseAppIDCodingKey = @"firebaseAppID";
  109. /** @var kTokenServiceCodingKey
  110. @brief The key used to encode the tokenService instance variable for NSSecureCoding.
  111. */
  112. static NSString *const kTokenServiceCodingKey = @"tokenService";
  113. /** @var kMetadataCodingKey
  114. @brief The key used to encode the metadata instance variable for NSSecureCoding.
  115. */
  116. static NSString *const kMetadataCodingKey = @"metadata";
  117. static NSString *const kMultiFactorCodingKey = @"multiFactor";
  118. /** @var kTenantIDKey
  119. @brief The key used to encode the tenantID instance variable for NSSecureCoding.
  120. */
  121. static NSString *const kTenantIDCodingKey = @"tenantID";
  122. /** @var kMissingUsersErrorMessage
  123. @brief The error message when there is no users array in the getAccountInfo response.
  124. */
  125. static NSString *const kMissingUsersErrorMessage = @"users";
  126. /** @typedef CallbackWithError
  127. @brief The type for a callback block that only takes an error parameter.
  128. */
  129. typedef void (^CallbackWithError)(NSError *_Nullable);
  130. /** @typedef CallbackWithUserAndError
  131. @brief The type for a callback block that takes a user parameter and an error parameter.
  132. */
  133. typedef void (^CallbackWithUserAndError)(FIRUser *_Nullable, NSError *_Nullable);
  134. /** @typedef CallbackWithUserAndError
  135. @brief The type for a callback block that takes a user parameter and an error parameter.
  136. */
  137. typedef void (^CallbackWithAuthDataResultAndError)(FIRAuthDataResult *_Nullable,
  138. NSError *_Nullable);
  139. /** @var kMissingPasswordReason
  140. @brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown.
  141. @remarks This error message will be localized in the future.
  142. */
  143. static NSString *const kMissingPasswordReason = @"Missing Password";
  144. /** @fn callInMainThreadWithError
  145. @brief Calls a callback in main thread with error.
  146. @param callback The callback to be called in main thread.
  147. @param error The error to pass to callback.
  148. */
  149. static void callInMainThreadWithError(_Nullable CallbackWithError callback,
  150. NSError *_Nullable error) {
  151. if (callback) {
  152. dispatch_async(dispatch_get_main_queue(), ^{
  153. callback(error);
  154. });
  155. }
  156. }
  157. /** @fn callInMainThreadWithUserAndError
  158. @brief Calls a callback in main thread with user and error.
  159. @param callback The callback to be called in main thread.
  160. @param user The user to pass to callback if there is no error.
  161. @param error The error to pass to callback.
  162. */
  163. static void callInMainThreadWithUserAndError(_Nullable CallbackWithUserAndError callback,
  164. FIRUser *_Nonnull user,
  165. NSError *_Nullable error) {
  166. if (callback) {
  167. dispatch_async(dispatch_get_main_queue(), ^{
  168. callback(error ? nil : user, error);
  169. });
  170. }
  171. }
  172. /** @fn callInMainThreadWithUserAndError
  173. @brief Calls a callback in main thread with user and error.
  174. @param callback The callback to be called in main thread.
  175. @param result The result to pass to callback if there is no error.
  176. @param error The error to pass to callback.
  177. */
  178. static void callInMainThreadWithAuthDataResultAndError(
  179. _Nullable CallbackWithAuthDataResultAndError callback,
  180. FIRAuthDataResult *_Nullable result,
  181. NSError *_Nullable error) {
  182. if (callback) {
  183. dispatch_async(dispatch_get_main_queue(), ^{
  184. callback(result, error);
  185. });
  186. }
  187. }
  188. @interface FIRUserProfileChangeRequest ()
  189. /** @fn initWithUser:
  190. @brief Designated initializer.
  191. @param user The user for which we are updating profile information.
  192. */
  193. - (nullable instancetype)initWithUser:(FIRUser *)user NS_DESIGNATED_INITIALIZER;
  194. @end
  195. @interface FIRUser ()
  196. /** @property anonymous
  197. @brief Whether the current user is anonymous.
  198. */
  199. @property(nonatomic, readwrite) BOOL anonymous;
  200. /** @property tenantID
  201. @brief The tenant ID of the current user. nil if none is available.
  202. */
  203. @property(nonatomic, readwrite, nullable) NSString *tenantID;
  204. @end
  205. @implementation FIRUser {
  206. /** @var _hasEmailPasswordCredential
  207. @brief Whether or not the user can be authenticated by using Firebase email and password.
  208. */
  209. BOOL _hasEmailPasswordCredential;
  210. /** @var _providerData
  211. @brief Provider specific user data.
  212. */
  213. NSDictionary<NSString *, FIRUserInfoImpl *> *_providerData;
  214. /** @var _taskQueue
  215. @brief Used to serialize the update profile calls.
  216. */
  217. FIRAuthSerialTaskQueue *_taskQueue;
  218. /** @var _tokenService
  219. @brief A secure token service associated with this user. For performing token exchanges and
  220. refreshing access tokens.
  221. */
  222. FIRSecureTokenService *_tokenService;
  223. }
  224. #pragma mark - Properties
  225. // Explicitly @synthesize because these properties are defined in FIRUserInfo protocol.
  226. @synthesize uid = _userID;
  227. @synthesize displayName = _displayName;
  228. @synthesize photoURL = _photoURL;
  229. @synthesize email = _email;
  230. @synthesize phoneNumber = _phoneNumber;
  231. #pragma mark -
  232. + (void)retrieveUserWithAuth:(FIRAuth *)auth
  233. accessToken:(nullable NSString *)accessToken
  234. accessTokenExpirationDate:(nullable NSDate *)accessTokenExpirationDate
  235. refreshToken:(nullable NSString *)refreshToken
  236. anonymous:(BOOL)anonymous
  237. callback:(FIRRetrieveUserCallback)callback {
  238. FIRSecureTokenService *tokenService =
  239. [[FIRSecureTokenService alloc] initWithRequestConfiguration:auth.requestConfiguration
  240. accessToken:accessToken
  241. accessTokenExpirationDate:accessTokenExpirationDate
  242. refreshToken:refreshToken];
  243. FIRUser *user = [[self alloc] initWithTokenService:tokenService];
  244. user.auth = auth;
  245. user.tenantID = auth.tenantID;
  246. user.requestConfiguration = auth.requestConfiguration;
  247. [user internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  248. if (error) {
  249. callback(nil, error);
  250. return;
  251. }
  252. FIRGetAccountInfoRequest *getAccountInfoRequest =
  253. [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
  254. requestConfiguration:auth.requestConfiguration];
  255. [FIRAuthBackend
  256. getAccountInfo:getAccountInfoRequest
  257. callback:^(FIRGetAccountInfoResponse *_Nullable response, NSError *_Nullable error) {
  258. if (error) {
  259. // No need to sign out user here for errors because the user hasn't been signed in
  260. // yet.
  261. callback(nil, error);
  262. return;
  263. }
  264. user.anonymous = anonymous;
  265. [user updateWithGetAccountInfoResponse:response];
  266. callback(user, nil);
  267. }];
  268. }];
  269. }
  270. - (instancetype)initWithTokenService:(FIRSecureTokenService *)tokenService {
  271. self = [super init];
  272. if (self) {
  273. _providerData = @{};
  274. _taskQueue = [[FIRAuthSerialTaskQueue alloc] init];
  275. _tokenService = tokenService;
  276. }
  277. return self;
  278. }
  279. #pragma mark - NSSecureCoding
  280. + (BOOL)supportsSecureCoding {
  281. return YES;
  282. }
  283. - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
  284. NSString *userID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserIDCodingKey];
  285. BOOL hasAnonymousKey = [aDecoder containsValueForKey:kAnonymousCodingKey];
  286. BOOL anonymous = [aDecoder decodeBoolForKey:kAnonymousCodingKey];
  287. BOOL hasEmailPasswordCredential =
  288. [aDecoder decodeBoolForKey:kHasEmailPasswordCredentialCodingKey];
  289. NSString *displayName = [aDecoder decodeObjectOfClass:[NSString class]
  290. forKey:kDisplayNameCodingKey];
  291. NSURL *photoURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:kPhotoURLCodingKey];
  292. NSString *email = [aDecoder decodeObjectOfClass:[NSString class] forKey:kEmailCodingKey];
  293. NSString *phoneNumber = [aDecoder decodeObjectOfClass:[NSString class]
  294. forKey:kPhoneNumberCodingKey];
  295. BOOL emailVerified = [aDecoder decodeBoolForKey:kEmailVerifiedCodingKey];
  296. NSSet *providerDataClasses =
  297. [NSSet setWithArray:@[ [NSDictionary class], [NSString class], [FIRUserInfoImpl class] ]];
  298. NSDictionary<NSString *, FIRUserInfoImpl *> *providerData =
  299. [aDecoder decodeObjectOfClasses:providerDataClasses forKey:kProviderDataKey];
  300. FIRSecureTokenService *tokenService = [aDecoder decodeObjectOfClass:[FIRSecureTokenService class]
  301. forKey:kTokenServiceCodingKey];
  302. FIRUserMetadata *metadata = [aDecoder decodeObjectOfClass:[FIRUserMetadata class]
  303. forKey:kMetadataCodingKey];
  304. NSString *tenantID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kTenantIDCodingKey];
  305. NSString *APIKey = [aDecoder decodeObjectOfClass:[NSString class] forKey:kAPIKeyCodingKey];
  306. NSString *appID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kFirebaseAppIDCodingKey];
  307. #if TARGET_OS_IOS
  308. FIRMultiFactor *multiFactor = [aDecoder decodeObjectOfClass:[FIRMultiFactor class]
  309. forKey:kMultiFactorCodingKey];
  310. #endif
  311. if (!userID || !tokenService) {
  312. return nil;
  313. }
  314. self = [self initWithTokenService:tokenService];
  315. if (self) {
  316. _userID = userID;
  317. // Previous version of this code didn't save 'anonymous' bit directly but deduced it from
  318. // 'hasEmailPasswordCredential' and 'providerData' instead, so here backward compatibility is
  319. // provided to read old format data.
  320. _anonymous = hasAnonymousKey ? anonymous : (!hasEmailPasswordCredential && !providerData.count);
  321. _hasEmailPasswordCredential = hasEmailPasswordCredential;
  322. _email = email;
  323. _emailVerified = emailVerified;
  324. _displayName = displayName;
  325. _photoURL = photoURL;
  326. _providerData = providerData;
  327. _phoneNumber = phoneNumber;
  328. _metadata = metadata ?: [[FIRUserMetadata alloc] initWithCreationDate:nil lastSignInDate:nil];
  329. _tenantID = tenantID;
  330. // The `heartbeatLogger` and `appCheck` will be set later via a property update.
  331. _requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:APIKey
  332. appID:appID
  333. heartbeatLogger:nil
  334. appCheck:nil];
  335. #if TARGET_OS_IOS
  336. _multiFactor = multiFactor ?: [[FIRMultiFactor alloc] init];
  337. _multiFactor.user = self;
  338. #endif
  339. }
  340. return self;
  341. }
  342. - (void)encodeWithCoder:(NSCoder *)aCoder {
  343. [aCoder encodeObject:_userID forKey:kUserIDCodingKey];
  344. [aCoder encodeBool:self.anonymous forKey:kAnonymousCodingKey];
  345. [aCoder encodeBool:_hasEmailPasswordCredential forKey:kHasEmailPasswordCredentialCodingKey];
  346. [aCoder encodeObject:_providerData forKey:kProviderDataKey];
  347. [aCoder encodeObject:_email forKey:kEmailCodingKey];
  348. [aCoder encodeObject:_phoneNumber forKey:kPhoneNumberCodingKey];
  349. [aCoder encodeBool:_emailVerified forKey:kEmailVerifiedCodingKey];
  350. [aCoder encodeObject:_photoURL forKey:kPhotoURLCodingKey];
  351. [aCoder encodeObject:_displayName forKey:kDisplayNameCodingKey];
  352. [aCoder encodeObject:_metadata forKey:kMetadataCodingKey];
  353. [aCoder encodeObject:_tenantID forKey:kTenantIDCodingKey];
  354. [aCoder encodeObject:_auth.requestConfiguration.APIKey forKey:kAPIKeyCodingKey];
  355. [aCoder encodeObject:_auth.requestConfiguration.appID forKey:kFirebaseAppIDCodingKey];
  356. [aCoder encodeObject:_tokenService forKey:kTokenServiceCodingKey];
  357. #if TARGET_OS_IOS
  358. [aCoder encodeObject:_multiFactor forKey:kMultiFactorCodingKey];
  359. #endif
  360. }
  361. #pragma mark -
  362. - (void)setAuth:(nullable FIRAuth *)auth {
  363. _auth = auth;
  364. _tokenService.requestConfiguration = auth.requestConfiguration;
  365. _requestConfiguration = auth.requestConfiguration;
  366. }
  367. - (NSString *)providerID {
  368. return @"Firebase";
  369. }
  370. - (NSArray<id<FIRUserInfo>> *)providerData {
  371. return _providerData.allValues;
  372. }
  373. /** @fn getAccountInfoRefreshingCache:
  374. @brief Gets the users's account data from the server, updating our local values.
  375. @param callback Invoked when the request to getAccountInfo has completed, or when an error has
  376. been detected. Invoked asynchronously on the auth global work queue in the future.
  377. */
  378. - (void)getAccountInfoRefreshingCache:(void (^)(FIRGetAccountInfoResponseUser *_Nullable user,
  379. NSError *_Nullable error))callback {
  380. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  381. if (error) {
  382. callback(nil, error);
  383. return;
  384. }
  385. FIRGetAccountInfoRequest *getAccountInfoRequest =
  386. [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
  387. requestConfiguration:self->_auth.requestConfiguration];
  388. [FIRAuthBackend
  389. getAccountInfo:getAccountInfoRequest
  390. callback:^(FIRGetAccountInfoResponse *_Nullable response, NSError *_Nullable error) {
  391. if (error) {
  392. [self signOutIfTokenIsInvalidWithError:error];
  393. callback(nil, error);
  394. return;
  395. }
  396. [self updateWithGetAccountInfoResponse:response];
  397. if (![self updateKeychain:&error]) {
  398. callback(nil, error);
  399. return;
  400. }
  401. callback(response.users.firstObject, nil);
  402. }];
  403. }];
  404. }
  405. - (void)updateWithGetAccountInfoResponse:(FIRGetAccountInfoResponse *)response {
  406. FIRGetAccountInfoResponseUser *user = response.users.firstObject;
  407. _userID = user.localID;
  408. _email = user.email;
  409. _emailVerified = user.emailVerified;
  410. _displayName = user.displayName;
  411. _photoURL = user.photoURL;
  412. _phoneNumber = user.phoneNumber;
  413. _hasEmailPasswordCredential = user.passwordHash.length > 0;
  414. _metadata = [[FIRUserMetadata alloc] initWithCreationDate:user.creationDate
  415. lastSignInDate:user.lastLoginDate];
  416. NSMutableDictionary<NSString *, FIRUserInfoImpl *> *providerData =
  417. [NSMutableDictionary dictionary];
  418. for (FIRGetAccountInfoResponseProviderUserInfo *providerUserInfo in user.providerUserInfo) {
  419. FIRUserInfoImpl *userInfo =
  420. [FIRUserInfoImpl userInfoWithGetAccountInfoResponseProviderUserInfo:providerUserInfo];
  421. if (userInfo) {
  422. providerData[providerUserInfo.providerID] = userInfo;
  423. }
  424. }
  425. _providerData = [providerData copy];
  426. #if TARGET_OS_IOS
  427. _multiFactor = [[FIRMultiFactor alloc] initWithMFAEnrollments:user.MFAEnrollments];
  428. _multiFactor.user = self;
  429. #endif
  430. }
  431. /** @fn executeUserUpdateWithChanges:callback:
  432. @brief Performs a setAccountInfo request by mutating the results of a getAccountInfo response,
  433. atomically in regards to other calls to this method.
  434. @param changeBlock A block responsible for mutating a template @c FIRSetAccountInfoRequest
  435. @param callback A block to invoke when the change is complete. Invoked asynchronously on the
  436. auth global work queue in the future.
  437. */
  438. - (void)executeUserUpdateWithChanges:(void (^)(FIRGetAccountInfoResponseUser *,
  439. FIRSetAccountInfoRequest *))changeBlock
  440. callback:(nonnull FIRUserProfileChangeCallback)callback {
  441. [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
  442. [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
  443. NSError *_Nullable error) {
  444. if (error) {
  445. complete();
  446. callback(error);
  447. return;
  448. }
  449. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  450. NSError *_Nullable error) {
  451. if (error) {
  452. complete();
  453. callback(error);
  454. return;
  455. }
  456. FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration;
  457. // Mutate setAccountInfoRequest in block:
  458. FIRSetAccountInfoRequest *setAccountInfoRequest =
  459. [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:configuration];
  460. setAccountInfoRequest.accessToken = accessToken;
  461. changeBlock(user, setAccountInfoRequest);
  462. // Execute request:
  463. [FIRAuthBackend
  464. setAccountInfo:setAccountInfoRequest
  465. callback:^(FIRSetAccountInfoResponse *_Nullable response,
  466. NSError *_Nullable error) {
  467. if (error) {
  468. [self signOutIfTokenIsInvalidWithError:error];
  469. complete();
  470. callback(error);
  471. return;
  472. }
  473. if (response.IDToken && response.refreshToken) {
  474. FIRSecureTokenService *tokenService = [[FIRSecureTokenService alloc]
  475. initWithRequestConfiguration:configuration
  476. accessToken:response.IDToken
  477. accessTokenExpirationDate:response.approximateExpirationDate
  478. refreshToken:response.refreshToken];
  479. [self setTokenService:tokenService
  480. callback:^(NSError *_Nullable error) {
  481. complete();
  482. callback(error);
  483. }];
  484. return;
  485. }
  486. complete();
  487. callback(nil);
  488. }];
  489. }];
  490. }];
  491. }];
  492. }
  493. /** @fn updateKeychain:
  494. @brief Updates the keychain for user token or info changes.
  495. @param error The error if NO is returned.
  496. @return Whether the operation is successful.
  497. */
  498. - (BOOL)updateKeychain:(NSError *_Nullable *_Nullable)error {
  499. return [_auth updateKeychainWithUser:self error:error];
  500. }
  501. /** @fn setTokenService:callback:
  502. @brief Sets a new token service for the @c FIRUser instance.
  503. @param tokenService The new token service object.
  504. @param callback The block to be called in the global auth working queue once finished.
  505. @remarks The method makes sure the token service has access and refresh token and the new tokens
  506. are saved in the keychain before calling back.
  507. */
  508. - (void)setTokenService:(FIRSecureTokenService *)tokenService
  509. callback:(nonnull CallbackWithError)callback {
  510. [tokenService fetchAccessTokenForcingRefresh:NO
  511. callback:^(NSString *_Nullable token,
  512. NSError *_Nullable error, BOOL tokenUpdated) {
  513. if (error) {
  514. callback(error);
  515. return;
  516. }
  517. self->_tokenService = tokenService;
  518. if (![self updateKeychain:&error]) {
  519. callback(error);
  520. return;
  521. }
  522. callback(nil);
  523. }];
  524. }
  525. #pragma mark -
  526. /** @fn updateEmail:password:callback:
  527. @brief Updates email address and/or password for the current user.
  528. @remarks May fail if there is already an email/password-based account for the same email
  529. address.
  530. @param email The email address for the user, if to be updated.
  531. @param password The new password for the user, if to be updated.
  532. @param callback The block called when the user profile change has finished. Invoked
  533. asynchronously on the auth global work queue in the future.
  534. @remarks May fail with a @c FIRAuthErrorCodeRequiresRecentLogin error code.
  535. Call @c reauthentateWithCredential:completion: beforehand to avoid this error case.
  536. */
  537. - (void)updateEmail:(nullable NSString *)email
  538. password:(nullable NSString *)password
  539. callback:(nonnull FIRUserProfileChangeCallback)callback {
  540. if (password && ![password length]) {
  541. callback([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]);
  542. return;
  543. }
  544. BOOL hadEmailPasswordCredential = _hasEmailPasswordCredential;
  545. [self
  546. executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
  547. FIRSetAccountInfoRequest *request) {
  548. if (email) {
  549. request.email = email;
  550. }
  551. if (password) {
  552. request.password = password;
  553. }
  554. }
  555. callback:^(NSError *error) {
  556. if (error) {
  557. callback(error);
  558. return;
  559. }
  560. if (email) {
  561. self->_email = [email copy];
  562. }
  563. if (self->_email) {
  564. if (!hadEmailPasswordCredential) {
  565. // The list of providers need to be updated for the newly added email-password provider.
  566. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  567. NSError *_Nullable error) {
  568. if (error) {
  569. callback(error);
  570. return;
  571. }
  572. FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;
  573. FIRGetAccountInfoRequest *getAccountInfoRequest =
  574. [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
  575. requestConfiguration:requestConfiguration];
  576. [FIRAuthBackend
  577. getAccountInfo:getAccountInfoRequest
  578. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  579. NSError *_Nullable error) {
  580. if (error) {
  581. [self signOutIfTokenIsInvalidWithError:error];
  582. callback(error);
  583. return;
  584. }
  585. for (FIRGetAccountInfoResponseUser *userAccountInfo in response.users) {
  586. // Set the account to non-anonymous if there are any providers, even if
  587. // they're not email/password ones.
  588. if (userAccountInfo.providerUserInfo.count > 0) {
  589. self.anonymous = NO;
  590. }
  591. for (FIRGetAccountInfoResponseProviderUserInfo
  592. *providerUserInfo in userAccountInfo.providerUserInfo) {
  593. if ([providerUserInfo.providerID
  594. isEqualToString:FIREmailAuthProviderID]) {
  595. self->_hasEmailPasswordCredential = YES;
  596. break;
  597. }
  598. }
  599. }
  600. [self updateWithGetAccountInfoResponse:response];
  601. if (![self updateKeychain:&error]) {
  602. callback(error);
  603. return;
  604. }
  605. callback(nil);
  606. }];
  607. }];
  608. return;
  609. }
  610. }
  611. if (![self updateKeychain:&error]) {
  612. callback(error);
  613. return;
  614. }
  615. callback(nil);
  616. }];
  617. }
  618. - (void)updateEmail:(NSString *)email completion:(nullable FIRUserProfileChangeCallback)completion {
  619. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  620. [self updateEmail:email
  621. password:nil
  622. callback:^(NSError *_Nullable error) {
  623. callInMainThreadWithError(completion, error);
  624. }];
  625. });
  626. }
  627. - (void)updatePassword:(NSString *)password
  628. completion:(nullable FIRUserProfileChangeCallback)completion {
  629. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  630. [self updateEmail:nil
  631. password:password
  632. callback:^(NSError *_Nullable error) {
  633. callInMainThreadWithError(completion, error);
  634. }];
  635. });
  636. }
  637. #if TARGET_OS_IOS
  638. /** @fn internalUpdateOrLinkPhoneNumberCredential:completion:
  639. @brief Updates the phone number for the user. On success, the cached user profile data is
  640. updated.
  641. @param phoneAuthCredential The new phone number credential corresponding to the phone number
  642. to be added to the Firebase account, if a phone number is already linked to the account this
  643. new phone number will replace it.
  644. @param isLinkOperation Boolean value indicating whether or not this is a link operation.
  645. @param completion Optionally; the block invoked when the user profile change has finished.
  646. Invoked asynchronously on the global work queue in the future.
  647. */
  648. - (void)internalUpdateOrLinkPhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential
  649. isLinkOperation:(BOOL)isLinkOperation
  650. completion:(FIRUserProfileChangeCallback)completion {
  651. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  652. if (error) {
  653. completion(error);
  654. return;
  655. }
  656. FIRAuthOperationType operation =
  657. isLinkOperation ? FIRAuthOperationTypeLink : FIRAuthOperationTypeUpdate;
  658. FIRVerifyPhoneNumberRequest *request = [[FIRVerifyPhoneNumberRequest alloc]
  659. initWithVerificationID:phoneAuthCredential.verificationID
  660. verificationCode:phoneAuthCredential.verificationCode
  661. operation:operation
  662. requestConfiguration:self->_auth.requestConfiguration];
  663. request.accessToken = accessToken;
  664. [FIRAuthBackend verifyPhoneNumber:request
  665. callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
  666. NSError *_Nullable error) {
  667. if (error) {
  668. [self signOutIfTokenIsInvalidWithError:error];
  669. completion(error);
  670. return;
  671. }
  672. FIRAuthRequestConfiguration *requestConfiguration =
  673. self.auth.requestConfiguration;
  674. // Update the new token and refresh user info again.
  675. self->_tokenService = [[FIRSecureTokenService alloc]
  676. initWithRequestConfiguration:requestConfiguration
  677. accessToken:response.IDToken
  678. accessTokenExpirationDate:response.approximateExpirationDate
  679. refreshToken:response.refreshToken];
  680. // Get account info to update cached user info.
  681. [self getAccountInfoRefreshingCache:^(
  682. FIRGetAccountInfoResponseUser *_Nullable user,
  683. NSError *_Nullable error) {
  684. if (error) {
  685. [self signOutIfTokenIsInvalidWithError:error];
  686. completion(error);
  687. return;
  688. }
  689. self.anonymous = NO;
  690. if (![self updateKeychain:&error]) {
  691. completion(error);
  692. return;
  693. }
  694. completion(nil);
  695. }];
  696. }];
  697. }];
  698. }
  699. - (void)updatePhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential
  700. completion:(nullable FIRUserProfileChangeCallback)completion {
  701. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  702. [self internalUpdateOrLinkPhoneNumberCredential:phoneAuthCredential
  703. isLinkOperation:NO
  704. completion:^(NSError *_Nullable error) {
  705. callInMainThreadWithError(completion, error);
  706. }];
  707. });
  708. }
  709. #endif
  710. - (FIRUserProfileChangeRequest *)profileChangeRequest {
  711. __block FIRUserProfileChangeRequest *result;
  712. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  713. result = [[FIRUserProfileChangeRequest alloc] initWithUser:self];
  714. });
  715. return result;
  716. }
  717. - (void)setDisplayName:(NSString *)displayName {
  718. _displayName = [displayName copy];
  719. }
  720. - (void)setPhotoURL:(NSURL *)photoURL {
  721. _photoURL = [photoURL copy];
  722. }
  723. - (NSString *)rawAccessToken {
  724. return _tokenService.rawAccessToken;
  725. }
  726. - (NSDate *)accessTokenExpirationDate {
  727. return _tokenService.accessTokenExpirationDate;
  728. }
  729. #pragma mark -
  730. - (void)reloadWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
  731. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  732. [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
  733. NSError *_Nullable error) {
  734. callInMainThreadWithError(completion, error);
  735. }];
  736. });
  737. }
  738. #pragma mark -
  739. - (void)reauthenticateWithCredential:(FIRAuthCredential *)credential
  740. completion:(nullable FIRAuthDataResultCallback)completion {
  741. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  742. [self->_auth
  743. internalSignInAndRetrieveDataWithCredential:credential
  744. isReauthentication:YES
  745. callback:^(FIRAuthDataResult *_Nullable authResult,
  746. NSError *_Nullable error) {
  747. if (error) {
  748. // If "user not found" error returned by backend,
  749. // translate to user mismatch error which is more
  750. // accurate.
  751. if (error.code == FIRAuthErrorCodeUserNotFound) {
  752. error = [FIRAuthErrorUtils userMismatchError];
  753. }
  754. callInMainThreadWithAuthDataResultAndError(
  755. completion, authResult, error);
  756. return;
  757. }
  758. if (![authResult.user.uid
  759. isEqual:[self->_auth getUserID]]) {
  760. callInMainThreadWithAuthDataResultAndError(
  761. completion, authResult,
  762. [FIRAuthErrorUtils userMismatchError]);
  763. return;
  764. }
  765. // Successful reauthenticate
  766. [self
  767. setTokenService:authResult.user->_tokenService
  768. callback:^(NSError *_Nullable error) {
  769. callInMainThreadWithAuthDataResultAndError(
  770. completion, authResult, error);
  771. }];
  772. }];
  773. });
  774. }
  775. - (void)reauthenticateWithProvider:(id<FIRFederatedAuthProvider>)provider
  776. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  777. completion:(nullable FIRAuthDataResultCallback)completion {
  778. #if TARGET_OS_IOS
  779. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  780. [provider getCredentialWithUIDelegate:UIDelegate
  781. completion:^(FIRAuthCredential *_Nullable credential,
  782. NSError *_Nullable error) {
  783. if (error) {
  784. completion(nil, error);
  785. return;
  786. }
  787. [self reauthenticateWithCredential:credential
  788. completion:completion];
  789. }];
  790. });
  791. #endif // TARGET_OS_IOS
  792. }
  793. - (nullable NSString *)refreshToken {
  794. __block NSString *result;
  795. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  796. result = self->_tokenService.refreshToken;
  797. });
  798. return result;
  799. }
  800. - (void)getIDTokenWithCompletion:(nullable FIRAuthTokenCallback)completion {
  801. // |getIDTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to
  802. // global work queue here.
  803. [self getIDTokenForcingRefresh:NO completion:completion];
  804. }
  805. - (void)getIDTokenForcingRefresh:(BOOL)forceRefresh
  806. completion:(nullable FIRAuthTokenCallback)completion {
  807. [self getIDTokenResultForcingRefresh:forceRefresh
  808. completion:^(FIRAuthTokenResult *_Nullable tokenResult,
  809. NSError *_Nullable error) {
  810. if (completion) {
  811. dispatch_async(dispatch_get_main_queue(), ^{
  812. completion(tokenResult.token, error);
  813. });
  814. }
  815. }];
  816. }
  817. - (void)getIDTokenResultWithCompletion:(nullable FIRAuthTokenResultCallback)completion {
  818. [self getIDTokenResultForcingRefresh:NO
  819. completion:^(FIRAuthTokenResult *_Nullable tokenResult,
  820. NSError *_Nullable error) {
  821. if (completion) {
  822. dispatch_async(dispatch_get_main_queue(), ^{
  823. completion(tokenResult, error);
  824. });
  825. }
  826. }];
  827. }
  828. - (void)getIDTokenResultForcingRefresh:(BOOL)forceRefresh
  829. completion:(nullable FIRAuthTokenResultCallback)completion {
  830. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  831. [self
  832. internalGetTokenForcingRefresh:forceRefresh
  833. callback:^(NSString *_Nullable token, NSError *_Nullable error) {
  834. FIRAuthTokenResult *tokenResult;
  835. if (token) {
  836. tokenResult = [FIRAuthTokenResult tokenResultWithToken:token];
  837. FIRLogDebug(kFIRLoggerAuth, @"I-AUT000017",
  838. @"Actual token expiration date: %@, current date: %@",
  839. tokenResult.expirationDate, [NSDate date]);
  840. }
  841. if (completion) {
  842. dispatch_async(dispatch_get_main_queue(), ^{
  843. completion(tokenResult, error);
  844. });
  845. }
  846. }];
  847. });
  848. }
  849. /** @fn parseIDToken:error:
  850. @brief Parses the provided IDToken and returns an instance of FIRAuthTokenResult containing
  851. claims obtained from the IDToken.
  852. @param token The raw text of the Firebase IDToken encoded in base64.
  853. @param error An out parameter which would contain any error that occurs during parsing.
  854. @return An instance of FIRAuthTokenResult containing claims obtained from the IDToken.
  855. @remarks IDToken returned from the backend in some cases is of a length that is not a multiple
  856. of 4. In these cases this function pads the token with as many "=" characters as needed and
  857. then attempts to parse the token. If the token cannot be parsed an error is returned via the
  858. "error" out parameter.
  859. */
  860. - (nullable FIRAuthTokenResult *)parseIDToken:(NSString *)token error:(NSError **)error {
  861. // Though this is an internal method, errors returned here are surfaced in user-visible
  862. // callbacks.
  863. if (error) {
  864. *error = nil;
  865. }
  866. NSArray *tokenStringArray = [token componentsSeparatedByString:@"."];
  867. // The JWT should have three parts, though we only use the second in this method.
  868. if (tokenStringArray.count != 3) {
  869. if (error) {
  870. *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil];
  871. }
  872. return nil;
  873. }
  874. // The token payload is always the second index of the array.
  875. NSString *IDToken = tokenStringArray[1];
  876. // Convert the base64URL encoded string to a base64 encoded string.
  877. // Replace "_" with "/"
  878. NSMutableString *tokenPayload = [[IDToken stringByReplacingOccurrencesOfString:@"_"
  879. withString:@"/"] mutableCopy];
  880. // Replace "-" with "+"
  881. [tokenPayload replaceOccurrencesOfString:@"-"
  882. withString:@"+"
  883. options:kNilOptions
  884. range:NSMakeRange(0, tokenPayload.length)];
  885. // Pad the token payload with "=" signs if the payload's length is not a multiple of 4.
  886. while ((tokenPayload.length % 4) != 0) {
  887. [tokenPayload appendFormat:@"="];
  888. }
  889. NSData *decodedTokenPayloadData =
  890. [[NSData alloc] initWithBase64EncodedString:tokenPayload
  891. options:NSDataBase64DecodingIgnoreUnknownCharacters];
  892. if (!decodedTokenPayloadData) {
  893. if (error) {
  894. *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil];
  895. }
  896. return nil;
  897. }
  898. NSError *jsonError = nil;
  899. NSJSONReadingOptions options = NSJSONReadingMutableContainers | NSJSONReadingAllowFragments;
  900. NSDictionary *tokenPayloadDictionary =
  901. [NSJSONSerialization JSONObjectWithData:decodedTokenPayloadData
  902. options:options
  903. error:&jsonError];
  904. if (jsonError != nil) {
  905. if (error) {
  906. *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:jsonError];
  907. }
  908. return nil;
  909. }
  910. if (!tokenPayloadDictionary) {
  911. if (error) {
  912. *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil];
  913. }
  914. return nil;
  915. }
  916. FIRAuthTokenResult *result = [FIRAuthTokenResult tokenResultWithToken:token];
  917. return result;
  918. }
  919. /** @fn internalGetTokenForcingRefresh:callback:
  920. @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  921. @param callback The block to invoke when the token is available. Invoked asynchronously on the
  922. global work thread in the future.
  923. */
  924. - (void)internalGetTokenWithCallback:(nonnull FIRAuthTokenCallback)callback {
  925. [self internalGetTokenForcingRefresh:NO callback:callback];
  926. }
  927. - (void)internalGetTokenForcingRefresh:(BOOL)forceRefresh
  928. callback:(nonnull FIRAuthTokenCallback)callback {
  929. [_tokenService fetchAccessTokenForcingRefresh:forceRefresh
  930. callback:^(NSString *_Nullable token,
  931. NSError *_Nullable error, BOOL tokenUpdated) {
  932. if (error) {
  933. [self signOutIfTokenIsInvalidWithError:error];
  934. callback(nil, error);
  935. return;
  936. }
  937. if (tokenUpdated) {
  938. if (![self updateKeychain:&error]) {
  939. callback(nil, error);
  940. return;
  941. }
  942. }
  943. callback(token, nil);
  944. }];
  945. }
  946. - (void)sendEmailVerificationBeforeUpdatingEmail:(nonnull NSString *)email
  947. completion:(nullable FIRAuthVoidErrorCallback)completion {
  948. [self internalVerifyBeforeUpdateEmailWithNewEmail:email
  949. actionCodeSettings:nil
  950. completion:completion];
  951. }
  952. - (void)sendEmailVerificationBeforeUpdatingEmail:(nonnull NSString *)email
  953. actionCodeSettings:(nonnull FIRActionCodeSettings *)actionCodeSettings
  954. completion:(nullable FIRAuthVoidErrorCallback)completion {
  955. [self internalVerifyBeforeUpdateEmailWithNewEmail:email
  956. actionCodeSettings:actionCodeSettings
  957. completion:completion];
  958. }
  959. - (void)internalVerifyBeforeUpdateEmailWithNewEmail:(NSString *)newEmail
  960. actionCodeSettings:
  961. (nullable FIRActionCodeSettings *)actionCodeSettings
  962. completion:(FIRVerifyBeforeUpdateEmailCallback)completion {
  963. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  964. [self
  965. internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  966. if (error) {
  967. callInMainThreadWithError(completion, error);
  968. return;
  969. }
  970. FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration;
  971. FIRActionCodeSettings *settings = actionCodeSettings;
  972. FIRGetOOBConfirmationCodeRequest *request = [FIRGetOOBConfirmationCodeRequest
  973. verifyBeforeUpdateEmailWithAccessToken:accessToken
  974. newEmail:newEmail
  975. actionCodeSettings:settings
  976. requestConfiguration:configuration];
  977. [FIRAuthBackend
  978. getOOBConfirmationCode:request
  979. callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
  980. NSError *_Nullable error) {
  981. callInMainThreadWithError(completion, error);
  982. }];
  983. }];
  984. });
  985. }
  986. - (void)linkWithCredential:(FIRAuthCredential *)credential
  987. completion:(nullable FIRAuthDataResultCallback)completion {
  988. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  989. if (self->_providerData[credential.provider]) {
  990. callInMainThreadWithAuthDataResultAndError(completion, nil,
  991. [FIRAuthErrorUtils providerAlreadyLinkedError]);
  992. return;
  993. }
  994. FIRAuthDataResult *result = [[FIRAuthDataResult alloc] initWithUser:self
  995. additionalUserInfo:nil];
  996. if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) {
  997. if (self->_hasEmailPasswordCredential) {
  998. callInMainThreadWithAuthDataResultAndError(completion, nil,
  999. [FIRAuthErrorUtils providerAlreadyLinkedError]);
  1000. return;
  1001. }
  1002. FIREmailPasswordAuthCredential *emailPasswordCredential =
  1003. (FIREmailPasswordAuthCredential *)credential;
  1004. if (emailPasswordCredential.password) {
  1005. [self updateEmail:emailPasswordCredential.email
  1006. password:emailPasswordCredential.password
  1007. callback:^(NSError *error) {
  1008. if (error) {
  1009. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  1010. } else {
  1011. callInMainThreadWithAuthDataResultAndError(completion, result, nil);
  1012. }
  1013. }];
  1014. } else {
  1015. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1016. NSError *_Nullable error) {
  1017. NSDictionary<NSString *, NSString *> *queryItems =
  1018. [FIRAuthWebUtils parseURL:emailPasswordCredential.link];
  1019. if (![queryItems count]) {
  1020. NSURLComponents *urlComponents =
  1021. [NSURLComponents componentsWithString:emailPasswordCredential.link];
  1022. queryItems = [FIRAuthWebUtils parseURL:urlComponents.query];
  1023. }
  1024. NSString *actionCode = queryItems[@"oobCode"];
  1025. FIRAuthRequestConfiguration *requestConfiguration = self.auth.requestConfiguration;
  1026. FIREmailLinkSignInRequest *request =
  1027. [[FIREmailLinkSignInRequest alloc] initWithEmail:emailPasswordCredential.email
  1028. oobCode:actionCode
  1029. requestConfiguration:requestConfiguration];
  1030. request.IDToken = accessToken;
  1031. [FIRAuthBackend
  1032. emailLinkSignin:request
  1033. callback:^(FIREmailLinkSignInResponse *_Nullable response,
  1034. NSError *_Nullable error) {
  1035. if (error) {
  1036. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  1037. } else {
  1038. // Update the new token and refresh user info again.
  1039. self->_tokenService = [[FIRSecureTokenService alloc]
  1040. initWithRequestConfiguration:requestConfiguration
  1041. accessToken:response.IDToken
  1042. accessTokenExpirationDate:response.approximateExpirationDate
  1043. refreshToken:response.refreshToken];
  1044. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1045. NSError *_Nullable error) {
  1046. if (error) {
  1047. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  1048. return;
  1049. }
  1050. FIRGetAccountInfoRequest *getAccountInfoRequest =
  1051. [[FIRGetAccountInfoRequest alloc]
  1052. initWithAccessToken:accessToken
  1053. requestConfiguration:requestConfiguration];
  1054. [FIRAuthBackend
  1055. getAccountInfo:getAccountInfoRequest
  1056. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  1057. NSError *_Nullable error) {
  1058. if (error) {
  1059. [self signOutIfTokenIsInvalidWithError:error];
  1060. callInMainThreadWithAuthDataResultAndError(completion, nil,
  1061. error);
  1062. return;
  1063. }
  1064. self.anonymous = NO;
  1065. [self updateWithGetAccountInfoResponse:response];
  1066. if (![self updateKeychain:&error]) {
  1067. callInMainThreadWithAuthDataResultAndError(completion, nil,
  1068. error);
  1069. return;
  1070. }
  1071. callInMainThreadWithAuthDataResultAndError(completion,
  1072. result, nil);
  1073. }];
  1074. }];
  1075. }
  1076. }];
  1077. }];
  1078. }
  1079. return;
  1080. }
  1081. if ([credential isKindOfClass:[FIRGameCenterAuthCredential class]]) {
  1082. FIRGameCenterAuthCredential *gameCenterCredential = (FIRGameCenterAuthCredential *)credential;
  1083. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1084. NSError *_Nullable error) {
  1085. FIRAuthRequestConfiguration *requestConfiguration = self.auth.requestConfiguration;
  1086. FIRSignInWithGameCenterRequest *gameCenterRequest = [[FIRSignInWithGameCenterRequest alloc]
  1087. initWithPlayerID:gameCenterCredential.playerID
  1088. teamPlayerID:gameCenterCredential.teamPlayerID
  1089. gamePlayerID:gameCenterCredential.gamePlayerID
  1090. publicKeyURL:gameCenterCredential.publicKeyURL
  1091. signature:gameCenterCredential.signature
  1092. salt:gameCenterCredential.salt
  1093. timestamp:gameCenterCredential.timestamp
  1094. displayName:gameCenterCredential.displayName
  1095. requestConfiguration:requestConfiguration];
  1096. gameCenterRequest.accessToken = accessToken;
  1097. [FIRAuthBackend
  1098. signInWithGameCenter:gameCenterRequest
  1099. callback:^(FIRSignInWithGameCenterResponse *_Nullable response,
  1100. NSError *_Nullable error) {
  1101. if (error) {
  1102. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  1103. } else {
  1104. // Update the new token and refresh user info again.
  1105. self->_tokenService = [[FIRSecureTokenService alloc]
  1106. initWithRequestConfiguration:requestConfiguration
  1107. accessToken:response.IDToken
  1108. accessTokenExpirationDate:response.approximateExpirationDate
  1109. refreshToken:response.refreshToken];
  1110. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1111. NSError *_Nullable error) {
  1112. if (error) {
  1113. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  1114. return;
  1115. }
  1116. FIRGetAccountInfoRequest *getAccountInfoRequest =
  1117. [[FIRGetAccountInfoRequest alloc]
  1118. initWithAccessToken:accessToken
  1119. requestConfiguration:requestConfiguration];
  1120. [FIRAuthBackend
  1121. getAccountInfo:getAccountInfoRequest
  1122. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  1123. NSError *_Nullable error) {
  1124. if (error) {
  1125. [self signOutIfTokenIsInvalidWithError:error];
  1126. callInMainThreadWithAuthDataResultAndError(completion,
  1127. nil, error);
  1128. return;
  1129. }
  1130. self.anonymous = NO;
  1131. [self updateWithGetAccountInfoResponse:response];
  1132. if (![self updateKeychain:&error]) {
  1133. callInMainThreadWithAuthDataResultAndError(completion,
  1134. nil, error);
  1135. return;
  1136. }
  1137. callInMainThreadWithAuthDataResultAndError(completion,
  1138. result, nil);
  1139. }];
  1140. }];
  1141. }
  1142. }];
  1143. }];
  1144. return;
  1145. }
  1146. #if TARGET_OS_IOS
  1147. if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) {
  1148. FIRPhoneAuthCredential *phoneAuthCredential = (FIRPhoneAuthCredential *)credential;
  1149. [self internalUpdateOrLinkPhoneNumberCredential:phoneAuthCredential
  1150. isLinkOperation:YES
  1151. completion:^(NSError *_Nullable error) {
  1152. if (error) {
  1153. callInMainThreadWithAuthDataResultAndError(
  1154. completion, nil, error);
  1155. } else {
  1156. callInMainThreadWithAuthDataResultAndError(
  1157. completion, result, nil);
  1158. }
  1159. }];
  1160. return;
  1161. }
  1162. #endif
  1163. [self->_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
  1164. CallbackWithAuthDataResultAndError completeWithError =
  1165. ^(FIRAuthDataResult *result, NSError *error) {
  1166. complete();
  1167. callInMainThreadWithAuthDataResultAndError(completion, result, error);
  1168. };
  1169. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1170. NSError *_Nullable error) {
  1171. if (error) {
  1172. completeWithError(nil, error);
  1173. return;
  1174. }
  1175. FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;
  1176. FIRVerifyAssertionRequest *request =
  1177. [[FIRVerifyAssertionRequest alloc] initWithProviderID:credential.provider
  1178. requestConfiguration:requestConfiguration];
  1179. [credential prepareVerifyAssertionRequest:request];
  1180. request.accessToken = accessToken;
  1181. [FIRAuthBackend
  1182. verifyAssertion:request
  1183. callback:^(FIRVerifyAssertionResponse *response, NSError *error) {
  1184. if (error) {
  1185. [self signOutIfTokenIsInvalidWithError:error];
  1186. completeWithError(nil, error);
  1187. return;
  1188. }
  1189. FIRAdditionalUserInfo *additionalUserInfo =
  1190. [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response];
  1191. FIROAuthCredential *updatedOAuthCredential =
  1192. [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:response];
  1193. FIRAuthDataResult *result =
  1194. [[FIRAuthDataResult alloc] initWithUser:self
  1195. additionalUserInfo:additionalUserInfo
  1196. credential:updatedOAuthCredential];
  1197. // Update the new token and refresh user info again.
  1198. self->_tokenService = [[FIRSecureTokenService alloc]
  1199. initWithRequestConfiguration:requestConfiguration
  1200. accessToken:response.IDToken
  1201. accessTokenExpirationDate:response.approximateExpirationDate
  1202. refreshToken:response.refreshToken];
  1203. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1204. NSError *_Nullable error) {
  1205. if (error) {
  1206. completeWithError(nil, error);
  1207. return;
  1208. }
  1209. FIRGetAccountInfoRequest *getAccountInfoRequest =
  1210. [[FIRGetAccountInfoRequest alloc]
  1211. initWithAccessToken:accessToken
  1212. requestConfiguration:requestConfiguration];
  1213. [FIRAuthBackend
  1214. getAccountInfo:getAccountInfoRequest
  1215. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  1216. NSError *_Nullable error) {
  1217. if (error) {
  1218. [self signOutIfTokenIsInvalidWithError:error];
  1219. completeWithError(nil, error);
  1220. return;
  1221. }
  1222. self.anonymous = NO;
  1223. [self updateWithGetAccountInfoResponse:response];
  1224. if (![self updateKeychain:&error]) {
  1225. completeWithError(nil, error);
  1226. return;
  1227. }
  1228. completeWithError(result, nil);
  1229. }];
  1230. }];
  1231. }];
  1232. }];
  1233. }];
  1234. });
  1235. }
  1236. - (void)linkWithProvider:(id<FIRFederatedAuthProvider>)provider
  1237. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  1238. completion:(nullable FIRAuthDataResultCallback)completion {
  1239. #if TARGET_OS_IOS
  1240. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  1241. [provider getCredentialWithUIDelegate:UIDelegate
  1242. completion:^(FIRAuthCredential *_Nullable credential,
  1243. NSError *_Nullable error) {
  1244. if (error) {
  1245. completion(nil, error);
  1246. return;
  1247. }
  1248. [self linkWithCredential:credential completion:completion];
  1249. }];
  1250. });
  1251. #endif // TARGET_OS_IOS
  1252. }
  1253. - (void)unlinkFromProvider:(NSString *)provider
  1254. completion:(nullable FIRAuthResultCallback)completion {
  1255. [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
  1256. CallbackWithError completeAndCallbackWithError = ^(NSError *error) {
  1257. complete();
  1258. callInMainThreadWithUserAndError(completion, self, error);
  1259. };
  1260. [self
  1261. internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  1262. if (error) {
  1263. completeAndCallbackWithError(error);
  1264. return;
  1265. }
  1266. FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;
  1267. FIRSetAccountInfoRequest *setAccountInfoRequest =
  1268. [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:requestConfiguration];
  1269. setAccountInfoRequest.accessToken = accessToken;
  1270. if (!self->_providerData[provider]) {
  1271. completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]);
  1272. return;
  1273. }
  1274. setAccountInfoRequest.deleteProviders = @[ provider ];
  1275. [FIRAuthBackend
  1276. setAccountInfo:setAccountInfoRequest
  1277. callback:^(FIRSetAccountInfoResponse *_Nullable response,
  1278. NSError *_Nullable error) {
  1279. if (error) {
  1280. [self signOutIfTokenIsInvalidWithError:error];
  1281. completeAndCallbackWithError(error);
  1282. return;
  1283. }
  1284. // We can't just use the provider info objects in FIRSetAccountInfoResponse
  1285. // because they don't have localID and email fields. Remove the specific
  1286. // provider manually.
  1287. NSMutableDictionary *mutableProviderData = [self->_providerData mutableCopy];
  1288. [mutableProviderData removeObjectForKey:provider];
  1289. self->_providerData = [mutableProviderData copy];
  1290. if ([provider isEqualToString:FIREmailAuthProviderID]) {
  1291. self->_hasEmailPasswordCredential = NO;
  1292. }
  1293. #if TARGET_OS_IOS
  1294. // After successfully unlinking a phone auth provider, remove the phone number
  1295. // from the cached user info.
  1296. if ([provider isEqualToString:FIRPhoneAuthProviderID]) {
  1297. self->_phoneNumber = nil;
  1298. }
  1299. #endif
  1300. if (response.IDToken && response.refreshToken) {
  1301. FIRSecureTokenService *tokenService = [[FIRSecureTokenService alloc]
  1302. initWithRequestConfiguration:requestConfiguration
  1303. accessToken:response.IDToken
  1304. accessTokenExpirationDate:response.approximateExpirationDate
  1305. refreshToken:response.refreshToken];
  1306. [self setTokenService:tokenService
  1307. callback:^(NSError *_Nullable error) {
  1308. completeAndCallbackWithError(error);
  1309. }];
  1310. return;
  1311. }
  1312. if (![self updateKeychain:&error]) {
  1313. completeAndCallbackWithError(error);
  1314. return;
  1315. }
  1316. completeAndCallbackWithError(nil);
  1317. }];
  1318. }];
  1319. }];
  1320. }
  1321. - (void)sendEmailVerificationWithCompletion:(nullable FIRSendEmailVerificationCallback)completion {
  1322. [self sendEmailVerificationWithNullableActionCodeSettings:nil completion:completion];
  1323. }
  1324. - (void)sendEmailVerificationWithActionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings
  1325. completion:
  1326. (nullable FIRSendEmailVerificationCallback)completion {
  1327. [self sendEmailVerificationWithNullableActionCodeSettings:actionCodeSettings
  1328. completion:completion];
  1329. }
  1330. /** @fn sendEmailVerificationWithNullableActionCodeSettings:completion:
  1331. @brief Initiates email verification for the user.
  1332. @param actionCodeSettings Optionally, a @c FIRActionCodeSettings object containing settings
  1333. related to the handling action codes.
  1334. */
  1335. - (void)sendEmailVerificationWithNullableActionCodeSettings:
  1336. (nullable FIRActionCodeSettings *)actionCodeSettings
  1337. completion:
  1338. (nullable FIRSendEmailVerificationCallback)
  1339. completion {
  1340. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  1341. [self
  1342. internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  1343. if (error) {
  1344. callInMainThreadWithError(completion, error);
  1345. return;
  1346. }
  1347. FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration;
  1348. FIRGetOOBConfirmationCodeRequest *request =
  1349. [FIRGetOOBConfirmationCodeRequest verifyEmailRequestWithAccessToken:accessToken
  1350. actionCodeSettings:actionCodeSettings
  1351. requestConfiguration:configuration];
  1352. [FIRAuthBackend
  1353. getOOBConfirmationCode:request
  1354. callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
  1355. NSError *_Nullable error) {
  1356. [self signOutIfTokenIsInvalidWithError:error];
  1357. callInMainThreadWithError(completion, error);
  1358. }];
  1359. }];
  1360. });
  1361. }
  1362. - (void)deleteWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
  1363. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  1364. [self
  1365. internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  1366. if (error) {
  1367. callInMainThreadWithError(completion, error);
  1368. return;
  1369. }
  1370. FIRDeleteAccountRequest *deleteUserRequest =
  1371. [[FIRDeleteAccountRequest alloc] initWitLocalID:self->_userID
  1372. accessToken:accessToken
  1373. requestConfiguration:self->_auth.requestConfiguration];
  1374. [FIRAuthBackend deleteAccount:deleteUserRequest
  1375. callback:^(NSError *_Nullable error) {
  1376. if (error) {
  1377. callInMainThreadWithError(completion, error);
  1378. return;
  1379. }
  1380. if (![self->_auth signOutByForceWithUserID:self->_userID
  1381. error:&error]) {
  1382. callInMainThreadWithError(completion, error);
  1383. return;
  1384. }
  1385. callInMainThreadWithError(completion, error);
  1386. }];
  1387. }];
  1388. });
  1389. }
  1390. /** @fn signOutIfTokenIsInvalidWithError:
  1391. @brief Signs out this user if the user or the token is invalid.
  1392. @param error The error from the server.
  1393. */
  1394. - (void)signOutIfTokenIsInvalidWithError:(nullable NSError *)error {
  1395. NSInteger errorCode = error.code;
  1396. if (errorCode == FIRAuthErrorCodeUserNotFound || errorCode == FIRAuthErrorCodeUserDisabled ||
  1397. errorCode == FIRAuthErrorCodeInvalidUserToken ||
  1398. errorCode == FIRAuthErrorCodeUserTokenExpired) {
  1399. FIRLogNotice(kFIRLoggerAuth, @"I-AUT000016",
  1400. @"Invalid user token detected, user is automatically signed out.");
  1401. [_auth signOutByForceWithUserID:_userID error:NULL];
  1402. }
  1403. }
  1404. @end
  1405. @implementation FIRUserProfileChangeRequest {
  1406. /** @var _user
  1407. @brief The user associated with the change request.
  1408. */
  1409. FIRUser *_user;
  1410. /** @var _displayName
  1411. @brief The display name value to set if @c _displayNameSet is YES.
  1412. */
  1413. NSString *_displayName;
  1414. /** @var _displayNameSet
  1415. @brief Indicates the display name should be part of the change request.
  1416. */
  1417. BOOL _displayNameSet;
  1418. /** @var _photoURL
  1419. @brief The photo URL value to set if @c _displayNameSet is YES.
  1420. */
  1421. NSURL *_photoURL;
  1422. /** @var _photoURLSet
  1423. @brief Indicates the photo URL should be part of the change request.
  1424. */
  1425. BOOL _photoURLSet;
  1426. /** @var _consumed
  1427. @brief Indicates the @c commitChangesWithCallback: method has already been invoked.
  1428. */
  1429. BOOL _consumed;
  1430. }
  1431. - (nullable instancetype)initWithUser:(FIRUser *)user {
  1432. self = [super init];
  1433. if (self) {
  1434. _user = user;
  1435. }
  1436. return self;
  1437. }
  1438. - (nullable NSString *)displayName {
  1439. return _displayName;
  1440. }
  1441. - (void)setDisplayName:(nullable NSString *)displayName {
  1442. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1443. if (self->_consumed) {
  1444. [NSException
  1445. raise:NSInternalInconsistencyException
  1446. format:@"%@", @"Invalid call to setDisplayName: after commitChangesWithCallback:."];
  1447. return;
  1448. }
  1449. self->_displayNameSet = YES;
  1450. self->_displayName = [displayName copy];
  1451. });
  1452. }
  1453. - (nullable NSURL *)photoURL {
  1454. return _photoURL;
  1455. }
  1456. - (void)setPhotoURL:(nullable NSURL *)photoURL {
  1457. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1458. if (self->_consumed) {
  1459. [NSException raise:NSInternalInconsistencyException
  1460. format:@"%@", @"Invalid call to setPhotoURL: after commitChangesWithCallback:."];
  1461. return;
  1462. }
  1463. self->_photoURLSet = YES;
  1464. self->_photoURL = [photoURL copy];
  1465. });
  1466. }
  1467. /** @fn hasUpdates
  1468. @brief Indicates at least one field has a value which needs to be committed.
  1469. */
  1470. - (BOOL)hasUpdates {
  1471. return _displayNameSet || _photoURLSet;
  1472. }
  1473. - (void)commitChangesWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
  1474. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1475. if (self->_consumed) {
  1476. [NSException raise:NSInternalInconsistencyException
  1477. format:@"%@", @"commitChangesWithCallback: should only be called once."];
  1478. return;
  1479. }
  1480. self->_consumed = YES;
  1481. // Return fast if there is nothing to update:
  1482. if (![self hasUpdates]) {
  1483. callInMainThreadWithError(completion, nil);
  1484. return;
  1485. }
  1486. NSString *displayName = [self->_displayName copy];
  1487. BOOL displayNameWasSet = self->_displayNameSet;
  1488. NSURL *photoURL = [self->_photoURL copy];
  1489. BOOL photoURLWasSet = self->_photoURLSet;
  1490. [self->_user
  1491. executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
  1492. FIRSetAccountInfoRequest *request) {
  1493. if (photoURLWasSet) {
  1494. request.photoURL = photoURL;
  1495. }
  1496. if (displayNameWasSet) {
  1497. request.displayName = displayName;
  1498. }
  1499. }
  1500. callback:^(NSError *_Nullable error) {
  1501. if (error) {
  1502. callInMainThreadWithError(completion, error);
  1503. return;
  1504. }
  1505. if (displayNameWasSet) {
  1506. [self->_user setDisplayName:displayName];
  1507. }
  1508. if (photoURLWasSet) {
  1509. [self->_user setPhotoURL:photoURL];
  1510. }
  1511. if (![self->_user updateKeychain:&error]) {
  1512. callInMainThreadWithError(completion, error);
  1513. return;
  1514. }
  1515. callInMainThreadWithError(completion, nil);
  1516. }];
  1517. });
  1518. }
  1519. @end
  1520. NS_ASSUME_NONNULL_END