FIRUser.m 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203
  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 <Foundation/Foundation.h>
  17. #import "FIRUser_Internal.h"
  18. #import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h"
  19. #import "FIREmailAuthProvider.h"
  20. #import "FIRAdditionalUserInfo_Internal.h"
  21. #import "FIRAuth.h"
  22. #import "FIRAuthCredential_Internal.h"
  23. #import "FIRAuthDataResult_Internal.h"
  24. #import "FIRAuthErrorUtils.h"
  25. #import "FIRAuthGlobalWorkQueue.h"
  26. #import "FIRAuthSerialTaskQueue.h"
  27. #import "FIRAuth_Internal.h"
  28. #import "FIRSecureTokenService.h"
  29. #import "FIRUserInfoImpl.h"
  30. #import "FIRAuthBackend.h"
  31. #import "FIRAuthRequestConfiguration.h"
  32. #import "FIRDeleteAccountRequest.h"
  33. #import "FIRDeleteAccountResponse.h"
  34. #import "FIRGetAccountInfoRequest.h"
  35. #import "FIRGetAccountInfoResponse.h"
  36. #import "FIRGetOOBConfirmationCodeRequest.h"
  37. #import "FIRGetOOBConfirmationCodeResponse.h"
  38. #import "FIRSetAccountInfoRequest.h"
  39. #import "FIRSetAccountInfoResponse.h"
  40. #import "FIRVerifyAssertionRequest.h"
  41. #import "FIRVerifyAssertionResponse.h"
  42. #import "FIRVerifyCustomTokenRequest.h"
  43. #import "FIRVerifyCustomTokenResponse.h"
  44. #import "FIRVerifyPasswordRequest.h"
  45. #import "FIRVerifyPasswordResponse.h"
  46. #import "FIRVerifyPhoneNumberRequest.h"
  47. #import "FIRVerifyPhoneNumberResponse.h"
  48. #if TARGET_OS_IOS
  49. #import "FIRPhoneAuthProvider.h"
  50. #import "AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h"
  51. #endif
  52. NS_ASSUME_NONNULL_BEGIN
  53. /** @var kUserIDCodingKey
  54. @brief The key used to encode the user ID for NSSecureCoding.
  55. */
  56. static NSString *const kUserIDCodingKey = @"userID";
  57. /** @var kHasEmailPasswordCredentialCodingKey
  58. @brief The key used to encode the hasEmailPasswordCredential property for NSSecureCoding.
  59. */
  60. static NSString *const kHasEmailPasswordCredentialCodingKey = @"hasEmailPassword";
  61. /** @var kAnonymousCodingKey
  62. @brief The key used to encode the anonymous property for NSSecureCoding.
  63. */
  64. static NSString *const kAnonymousCodingKey = @"anonymous";
  65. /** @var kEmailCodingKey
  66. @brief The key used to encode the email property for NSSecureCoding.
  67. */
  68. static NSString *const kEmailCodingKey = @"email";
  69. /** @var kPhoneNumberCodingKey
  70. @brief The key used to encode the phoneNumber property for NSSecureCoding.
  71. */
  72. static NSString *const kPhoneNumberCodingKey = @"phoneNumber";
  73. /** @var kEmailVerifiedCodingKey
  74. @brief The key used to encode the isEmailVerified property for NSSecureCoding.
  75. */
  76. static NSString *const kEmailVerifiedCodingKey = @"emailVerified";
  77. /** @var kDisplayNameCodingKey
  78. @brief The key used to encode the displayName property for NSSecureCoding.
  79. */
  80. static NSString *const kDisplayNameCodingKey = @"displayName";
  81. /** @var kPhotoURLCodingKey
  82. @brief The key used to encode the photoURL property for NSSecureCoding.
  83. */
  84. static NSString *const kPhotoURLCodingKey = @"photoURL";
  85. /** @var kProviderDataKey
  86. @brief The key used to encode the providerData instance variable for NSSecureCoding.
  87. */
  88. static NSString *const kProviderDataKey = @"providerData";
  89. /** @var kAPIKeyCodingKey
  90. @brief The key used to encode the APIKey instance variable for NSSecureCoding.
  91. */
  92. static NSString *const kAPIKeyCodingKey = @"APIKey";
  93. /** @var kTokenServiceCodingKey
  94. @brief The key used to encode the tokenService instance variable for NSSecureCoding.
  95. */
  96. static NSString *const kTokenServiceCodingKey = @"tokenService";
  97. /** @var kMissingUsersErrorMessage
  98. @brief The error message when there is no users array in the getAccountInfo response.
  99. */
  100. static NSString *const kMissingUsersErrorMessage = @"users";
  101. /** @typedef CallbackWithError
  102. @brief The type for a callback block that only takes an error parameter.
  103. */
  104. typedef void (^CallbackWithError)(NSError *_Nullable);
  105. /** @typedef CallbackWithUserAndError
  106. @brief The type for a callback block that takes a user parameter and an error parameter.
  107. */
  108. typedef void (^CallbackWithUserAndError)(FIRUser *_Nullable, NSError *_Nullable);
  109. /** @typedef CallbackWithUserAndError
  110. @brief The type for a callback block that takes a user parameter and an error parameter.
  111. */
  112. typedef void (^CallbackWithAuthDataResultAndError)(FIRAuthDataResult *_Nullable,
  113. NSError *_Nullable);
  114. /** @var kMissingPasswordReason
  115. @brief The reason why the @c FIRAuthErrorCodeWeakPassword error is thrown.
  116. @remarks This error message will be localized in the future.
  117. */
  118. static NSString *const kMissingPasswordReason = @"Missing Password";
  119. /** @fn callInMainThreadWithError
  120. @brief Calls a callback in main thread with error.
  121. @param callback The callback to be called in main thread.
  122. @param error The error to pass to callback.
  123. */
  124. static void callInMainThreadWithError(_Nullable CallbackWithError callback,
  125. NSError *_Nullable error) {
  126. if (callback) {
  127. dispatch_async(dispatch_get_main_queue(), ^{
  128. callback(error);
  129. });
  130. }
  131. }
  132. /** @fn callInMainThreadWithUserAndError
  133. @brief Calls a callback in main thread with user and error.
  134. @param callback The callback to be called in main thread.
  135. @param user The user to pass to callback if there is no error.
  136. @param error The error to pass to callback.
  137. */
  138. static void callInMainThreadWithUserAndError(_Nullable CallbackWithUserAndError callback,
  139. FIRUser *_Nonnull user,
  140. NSError *_Nullable error) {
  141. if (callback) {
  142. dispatch_async(dispatch_get_main_queue(), ^{
  143. callback(error ? nil : user, error);
  144. });
  145. }
  146. }
  147. /** @fn callInMainThreadWithUserAndError
  148. @brief Calls a callback in main thread with user and error.
  149. @param callback The callback to be called in main thread.
  150. @param result The result to pass to callback if there is no error.
  151. @param error The error to pass to callback.
  152. */
  153. static void callInMainThreadWithAuthDataResultAndError(
  154. _Nullable CallbackWithAuthDataResultAndError callback,
  155. FIRAuthDataResult *_Nullable result,
  156. NSError *_Nullable error) {
  157. if (callback) {
  158. dispatch_async(dispatch_get_main_queue(), ^{
  159. callback(result, error);
  160. });
  161. }
  162. }
  163. @interface FIRUserProfileChangeRequest ()
  164. /** @fn initWithUser:
  165. @brief Designated initializer.
  166. @param user The user for which we are updating profile information.
  167. */
  168. - (nullable instancetype)initWithUser:(FIRUser *)user NS_DESIGNATED_INITIALIZER;
  169. @end
  170. @implementation FIRUser {
  171. /** @var _hasEmailPasswordCredential
  172. @brief Whether or not the user can be authenticated by using Firebase email and password.
  173. */
  174. BOOL _hasEmailPasswordCredential;
  175. /** @var _providerData
  176. @brief Provider specific user data.
  177. */
  178. NSDictionary<NSString *, FIRUserInfoImpl *> *_providerData;
  179. /** @var _taskQueue
  180. @brief Used to serialize the update profile calls.
  181. */
  182. FIRAuthSerialTaskQueue *_taskQueue;
  183. /** @var _tokenService
  184. @brief A secure token service associated with this user. For performing token exchanges and
  185. refreshing access tokens.
  186. */
  187. FIRSecureTokenService *_tokenService;
  188. }
  189. #pragma mark - Properties
  190. // Explicitly @synthesize because these properties are defined in FIRUserInfo protocol.
  191. @synthesize uid = _userID;
  192. @synthesize displayName = _displayName;
  193. @synthesize photoURL = _photoURL;
  194. @synthesize email = _email;
  195. @synthesize phoneNumber = _phoneNumber;
  196. #pragma mark -
  197. + (void)retrieveUserWithAuth:(FIRAuth *)auth
  198. accessToken:(NSString *)accessToken
  199. accessTokenExpirationDate:(NSDate *)accessTokenExpirationDate
  200. refreshToken:(NSString *)refreshToken
  201. anonymous:(BOOL)anonymous
  202. callback:(FIRRetrieveUserCallback)callback {
  203. FIRSecureTokenService *tokenService =
  204. [[FIRSecureTokenService alloc] initWithRequestConfiguration:auth.requestConfiguration
  205. accessToken:accessToken
  206. accessTokenExpirationDate:accessTokenExpirationDate
  207. refreshToken:refreshToken];
  208. FIRUser *user = [[self alloc] initWithTokenService:tokenService];
  209. user.auth = auth;
  210. [user internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  211. if (error) {
  212. callback(nil, error);
  213. return;
  214. }
  215. FIRGetAccountInfoRequest *getAccountInfoRequest =
  216. [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
  217. requestConfiguration:auth.requestConfiguration];
  218. [FIRAuthBackend getAccountInfo:getAccountInfoRequest
  219. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  220. NSError *_Nullable error) {
  221. if (error) {
  222. callback(nil, error);
  223. return;
  224. }
  225. user->_anonymous = anonymous;
  226. [user updateWithGetAccountInfoResponse:response];
  227. callback(user, nil);
  228. }];
  229. }];
  230. }
  231. - (instancetype)initWithTokenService:(FIRSecureTokenService *)tokenService {
  232. self = [super init];
  233. if (self) {
  234. _providerData = @{ };
  235. _taskQueue = [[FIRAuthSerialTaskQueue alloc] init];
  236. _tokenService = tokenService;
  237. }
  238. return self;
  239. }
  240. #pragma mark - NSSecureCoding
  241. + (BOOL)supportsSecureCoding {
  242. return YES;
  243. }
  244. - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
  245. NSString *userID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kUserIDCodingKey];
  246. BOOL hasAnonymousKey = [aDecoder containsValueForKey:kAnonymousCodingKey];
  247. BOOL anonymous = [aDecoder decodeBoolForKey:kAnonymousCodingKey];
  248. BOOL hasEmailPasswordCredential =
  249. [aDecoder decodeBoolForKey:kHasEmailPasswordCredentialCodingKey];
  250. NSString *displayName =
  251. [aDecoder decodeObjectOfClass:[NSString class] forKey:kDisplayNameCodingKey];
  252. NSURL *photoURL =
  253. [aDecoder decodeObjectOfClass:[NSURL class] forKey:kPhotoURLCodingKey];
  254. NSString *email =
  255. [aDecoder decodeObjectOfClass:[NSString class] forKey:kEmailCodingKey];
  256. NSString *phoneNumber =
  257. [aDecoder decodeObjectOfClass:[NSString class] forKey:kPhoneNumberCodingKey];
  258. BOOL emailVerified = [aDecoder decodeBoolForKey:kEmailVerifiedCodingKey];
  259. NSSet *providerDataClasses = [NSSet setWithArray:@[
  260. [NSDictionary class],
  261. [NSString class],
  262. [FIRUserInfoImpl class]
  263. ]];
  264. NSDictionary<NSString *, FIRUserInfoImpl *> *providerData =
  265. [aDecoder decodeObjectOfClasses:providerDataClasses forKey:kProviderDataKey];
  266. FIRSecureTokenService *tokenService =
  267. [aDecoder decodeObjectOfClass:[FIRSecureTokenService class] forKey:kTokenServiceCodingKey];
  268. if (!userID || !tokenService) {
  269. return nil;
  270. }
  271. self = [self initWithTokenService:tokenService];
  272. if (self) {
  273. _userID = userID;
  274. // Previous version of this code didn't save 'anonymous' bit directly but deduced it from
  275. // 'hasEmailPasswordCredential' and 'providerData' instead, so here backward compatibility is
  276. // provided to read old format data.
  277. _anonymous = hasAnonymousKey ? anonymous : (!hasEmailPasswordCredential && !providerData.count);
  278. _hasEmailPasswordCredential = hasEmailPasswordCredential;
  279. _email = email;
  280. _emailVerified = emailVerified;
  281. _displayName = displayName;
  282. _photoURL = photoURL;
  283. _providerData = providerData;
  284. _phoneNumber = phoneNumber;
  285. }
  286. return self;
  287. }
  288. - (void)encodeWithCoder:(NSCoder *)aCoder {
  289. [aCoder encodeObject:_userID forKey:kUserIDCodingKey];
  290. [aCoder encodeBool:_anonymous forKey:kAnonymousCodingKey];
  291. [aCoder encodeBool:_hasEmailPasswordCredential forKey:kHasEmailPasswordCredentialCodingKey];
  292. [aCoder encodeObject:_providerData forKey:kProviderDataKey];
  293. [aCoder encodeObject:_email forKey:kEmailCodingKey];
  294. [aCoder encodeObject:_phoneNumber forKey:kPhoneNumberCodingKey];
  295. [aCoder encodeBool:_emailVerified forKey:kEmailVerifiedCodingKey];
  296. [aCoder encodeObject:_photoURL forKey:kPhotoURLCodingKey];
  297. [aCoder encodeObject:_displayName forKey:kDisplayNameCodingKey];
  298. // The API key is encoded even it is not used in decoding to be compatible with previous versions
  299. // of the library.
  300. [aCoder encodeObject:_auth.requestConfiguration.APIKey forKey:kAPIKeyCodingKey];
  301. [aCoder encodeObject:_tokenService forKey:kTokenServiceCodingKey];
  302. }
  303. #pragma mark -
  304. - (void)setAuth:(nullable FIRAuth *)auth {
  305. _auth = auth;
  306. _tokenService.requestConfiguration = auth.requestConfiguration;
  307. }
  308. - (NSString *)providerID {
  309. return @"Firebase";
  310. }
  311. - (NSArray<id<FIRUserInfo>> *)providerData {
  312. return _providerData.allValues;
  313. }
  314. /** @fn getAccountInfoRefreshingCache:
  315. @brief Gets the users's account data from the server, updating our local values.
  316. @param callback Invoked when the request to getAccountInfo has completed, or when an error has
  317. been detected. Invoked asynchronously on the auth global work queue in the future.
  318. */
  319. - (void)getAccountInfoRefreshingCache:(void(^)(FIRGetAccountInfoResponseUser *_Nullable user,
  320. NSError *_Nullable error))callback {
  321. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  322. if (error) {
  323. callback(nil, error);
  324. return;
  325. }
  326. FIRGetAccountInfoRequest *getAccountInfoRequest =
  327. [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
  328. requestConfiguration:_auth.requestConfiguration];
  329. [FIRAuthBackend getAccountInfo:getAccountInfoRequest
  330. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  331. NSError *_Nullable error) {
  332. if (error) {
  333. callback(nil, error);
  334. return;
  335. }
  336. [self updateWithGetAccountInfoResponse:response];
  337. if (![self updateKeychain:&error]) {
  338. callback(nil, error);
  339. return;
  340. }
  341. callback(response.users.firstObject, nil);
  342. }];
  343. }];
  344. }
  345. - (void)updateWithGetAccountInfoResponse:(FIRGetAccountInfoResponse *)response {
  346. FIRGetAccountInfoResponseUser *user = response.users.firstObject;
  347. _userID = user.localID;
  348. _email = user.email;
  349. _emailVerified = user.emailVerified;
  350. _displayName = user.displayName;
  351. _photoURL = user.photoURL;
  352. _phoneNumber = user.phoneNumber;
  353. _hasEmailPasswordCredential = user.passwordHash.length > 0;
  354. NSMutableDictionary<NSString *, FIRUserInfoImpl *> *providerData =
  355. [NSMutableDictionary dictionary];
  356. for (FIRGetAccountInfoResponseProviderUserInfo *providerUserInfo in user.providerUserInfo) {
  357. FIRUserInfoImpl *userInfo =
  358. [FIRUserInfoImpl userInfoWithGetAccountInfoResponseProviderUserInfo:providerUserInfo];
  359. if (userInfo) {
  360. providerData[providerUserInfo.providerID] = userInfo;
  361. }
  362. }
  363. _providerData = [providerData copy];
  364. }
  365. /** @fn executeUserUpdateWithChanges:callback:
  366. @brief Performs a setAccountInfo request by mutating the results of a getAccountInfo response,
  367. atomically in regards to other calls to this method.
  368. @param changeBlock A block responsible for mutating a template @c FIRSetAccountInfoRequest
  369. @param callback A block to invoke when the change is complete. Invoked asynchronously on the
  370. auth global work queue in the future.
  371. */
  372. - (void)executeUserUpdateWithChanges:(void(^)(FIRGetAccountInfoResponseUser *,
  373. FIRSetAccountInfoRequest *))changeBlock
  374. callback:(nonnull FIRUserProfileChangeCallback)callback {
  375. [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
  376. [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
  377. NSError *_Nullable error) {
  378. if (error) {
  379. complete();
  380. callback(error);
  381. return;
  382. }
  383. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  384. NSError *_Nullable error) {
  385. if (error) {
  386. complete();
  387. callback(error);
  388. return;
  389. }
  390. FIRAuthRequestConfiguration *configuration = _auth.requestConfiguration;
  391. // Mutate setAccountInfoRequest in block:
  392. FIRSetAccountInfoRequest *setAccountInfoRequest =
  393. [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:configuration];
  394. setAccountInfoRequest.accessToken = accessToken;
  395. changeBlock(user, setAccountInfoRequest);
  396. // Execute request:
  397. [FIRAuthBackend setAccountInfo:setAccountInfoRequest
  398. callback:^(FIRSetAccountInfoResponse *_Nullable response,
  399. NSError *_Nullable error) {
  400. if (error) {
  401. complete();
  402. callback(error);
  403. return;
  404. }
  405. if (response.IDToken && response.refreshToken) {
  406. FIRSecureTokenService *tokenService = [[FIRSecureTokenService alloc]
  407. initWithRequestConfiguration:configuration
  408. accessToken:response.IDToken
  409. accessTokenExpirationDate:response.approximateExpirationDate
  410. refreshToken:response.refreshToken];
  411. [self setTokenService:tokenService callback:^(NSError *_Nullable error) {
  412. complete();
  413. callback(error);
  414. }];
  415. return;
  416. }
  417. complete();
  418. callback(nil);
  419. }];
  420. }];
  421. }];
  422. }];
  423. }
  424. /** @fn updateKeychain:
  425. @brief Updates the keychain for user token or info changes.
  426. @param error The error if NO is returned.
  427. @return Whether the operation is successful.
  428. */
  429. - (BOOL)updateKeychain:(NSError *_Nullable *_Nullable)error {
  430. return [_auth updateKeychainWithUser:self error:error];
  431. }
  432. /** @fn setTokenService:callback:
  433. @brief Sets a new token service for the @c FIRUser instance.
  434. @param tokenService The new token service object.
  435. @param callback The block to be called in the global auth working queue once finished.
  436. @remarks The method makes sure the token service has access and refresh token and the new tokens
  437. are saved in the keychain before calling back.
  438. */
  439. - (void)setTokenService:(FIRSecureTokenService *)tokenService
  440. callback:(nonnull CallbackWithError)callback {
  441. [tokenService fetchAccessTokenForcingRefresh:NO
  442. callback:^(NSString *_Nullable token,
  443. NSError *_Nullable error,
  444. BOOL tokenUpdated) {
  445. if (error) {
  446. callback(error);
  447. return;
  448. }
  449. _tokenService = tokenService;
  450. if (![self updateKeychain:&error]) {
  451. callback(error);
  452. return;
  453. }
  454. [_auth notifyListenersOfAuthStateChangeWithUser:self token:token];
  455. callback(nil);
  456. }];
  457. }
  458. #pragma mark -
  459. /** @fn updateEmail:password:callback:
  460. @brief Updates email address and/or password for the current user.
  461. @remarks May fail if there is already an email/password-based account for the same email
  462. address.
  463. @param email The email address for the user, if to be updated.
  464. @param password The new password for the user, if to be updated.
  465. @param callback The block called when the user profile change has finished. Invoked
  466. asynchronously on the auth global work queue in the future.
  467. @remarks May fail with a @c FIRAuthErrorCodeRequiresRecentLogin error code.
  468. Call @c reauthentateWithCredential:completion: beforehand to avoid this error case.
  469. */
  470. - (void)updateEmail:(nullable NSString *)email
  471. password:(nullable NSString *)password
  472. callback:(nonnull FIRUserProfileChangeCallback)callback {
  473. if (password && ![password length]){
  474. callback([FIRAuthErrorUtils weakPasswordErrorWithServerResponseReason:kMissingPasswordReason]);
  475. return;
  476. }
  477. BOOL hadEmailPasswordCredential = _hasEmailPasswordCredential;
  478. [self executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
  479. FIRSetAccountInfoRequest *request) {
  480. if (email) {
  481. request.email = email;
  482. }
  483. if (password) {
  484. request.password = password;
  485. }
  486. }
  487. callback:^(NSError *error) {
  488. if (error) {
  489. callback(error);
  490. return;
  491. }
  492. if (email) {
  493. _email = email;
  494. }
  495. if (_email && password) {
  496. _anonymous = NO;
  497. _hasEmailPasswordCredential = YES;
  498. if (!hadEmailPasswordCredential) {
  499. // The list of providers need to be updated for the newly added email-password provider.
  500. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  501. NSError *_Nullable error) {
  502. if (error) {
  503. callback(error);
  504. return;
  505. }
  506. FIRAuthRequestConfiguration *requestConfiguration = _auth.requestConfiguration;
  507. FIRGetAccountInfoRequest *getAccountInfoRequest =
  508. [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
  509. requestConfiguration:requestConfiguration];
  510. [FIRAuthBackend getAccountInfo:getAccountInfoRequest
  511. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  512. NSError *_Nullable error) {
  513. if (error) {
  514. callback(error);
  515. return;
  516. }
  517. [self updateWithGetAccountInfoResponse:response];
  518. if (![self updateKeychain:&error]) {
  519. callback(error);
  520. return;
  521. }
  522. callback(nil);
  523. }];
  524. }];
  525. return;
  526. }
  527. }
  528. if (![self updateKeychain:&error]) {
  529. callback(error);
  530. return;
  531. }
  532. callback(nil);
  533. }];
  534. }
  535. - (void)updateEmail:(NSString *)email completion:(nullable FIRUserProfileChangeCallback)completion {
  536. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  537. [self updateEmail:email password:nil callback:^(NSError *_Nullable error) {
  538. callInMainThreadWithError(completion, error);
  539. }];
  540. });
  541. }
  542. - (void)updatePassword:(NSString *)password
  543. completion:(nullable FIRUserProfileChangeCallback)completion {
  544. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  545. [self updateEmail:nil password:password callback:^(NSError *_Nullable error){
  546. callInMainThreadWithError(completion, error);
  547. }];
  548. });
  549. }
  550. #if TARGET_OS_IOS
  551. /** @fn internalUpdatePhoneNumberCredential:completion:
  552. @brief Updates the phone number for the user. On success, the cached user profile data is
  553. updated.
  554. @param phoneAuthCredential The new phone number credential corresponding to the phone number
  555. to be added to the firebaes account, if a phone number is already linked to the account this
  556. new phone number will replace it.
  557. @param completion Optionally; the block invoked when the user profile change has finished.
  558. Invoked asynchronously on the global work queue in the future.
  559. */
  560. - (void)internalUpdatePhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential
  561. completion:(FIRUserProfileChangeCallback)completion {
  562. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  563. NSError *_Nullable error) {
  564. if (error) {
  565. completion(error);
  566. return;
  567. }
  568. FIRVerifyPhoneNumberRequest *request = [[FIRVerifyPhoneNumberRequest alloc]
  569. initWithVerificationID:phoneAuthCredential.verificationID
  570. verificationCode:phoneAuthCredential.verificationCode
  571. requestConfiguration:_auth.requestConfiguration];
  572. request.accessToken = accessToken;
  573. [FIRAuthBackend verifyPhoneNumber:request
  574. callback:^(FIRVerifyPhoneNumberResponse *_Nullable response,
  575. NSError *_Nullable error) {
  576. if (error) {
  577. completion(error);;
  578. return;
  579. }
  580. // Get account info to update cached user info.
  581. [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
  582. NSError *_Nullable error) {
  583. if (![self updateKeychain:&error]) {
  584. completion(error);
  585. return;
  586. }
  587. completion(nil);
  588. }];
  589. }];
  590. }];
  591. }
  592. - (void)updatePhoneNumberCredential:(FIRPhoneAuthCredential *)phoneAuthCredential
  593. completion:(nullable FIRUserProfileChangeCallback)completion {
  594. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  595. [self internalUpdatePhoneNumberCredential:phoneAuthCredential
  596. completion:^(NSError *_Nullable error) {
  597. callInMainThreadWithError(completion, error);
  598. }];
  599. });
  600. }
  601. #endif
  602. - (FIRUserProfileChangeRequest *)profileChangeRequest {
  603. __block FIRUserProfileChangeRequest *result;
  604. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  605. result = [[FIRUserProfileChangeRequest alloc] initWithUser:self];
  606. });
  607. return result;
  608. }
  609. - (void)setDisplayName:(NSString *)displayName {
  610. _displayName = [displayName copy];
  611. }
  612. - (void)setPhotoURL:(NSURL *)photoURL {
  613. _photoURL = [photoURL copy];
  614. }
  615. - (NSString *)rawAccessToken {
  616. return _tokenService.rawAccessToken;
  617. }
  618. - (NSDate *)accessTokenExpirationDate {
  619. return _tokenService.accessTokenExpirationDate;
  620. }
  621. #pragma mark -
  622. - (void)reloadWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
  623. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  624. [self getAccountInfoRefreshingCache:^(FIRGetAccountInfoResponseUser *_Nullable user,
  625. NSError *_Nullable error) {
  626. callInMainThreadWithError(completion, error);
  627. }];
  628. });
  629. }
  630. #pragma mark -
  631. - (void)reauthenticateWithCredential:(FIRAuthCredential *)credential
  632. completion:(nullable FIRUserProfileChangeCallback)completion {
  633. FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable authResult,
  634. NSError *_Nullable error) {
  635. completion(error);
  636. };
  637. [self reauthenticateAndRetrieveDataWithCredential:credential completion:callback];
  638. }
  639. - (void)
  640. reauthenticateAndRetrieveDataWithCredential:(FIRAuthCredential *) credential
  641. completion:(nullable FIRAuthDataResultCallback) completion {
  642. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  643. [_auth internalSignInAndRetrieveDataWithCredential:credential
  644. isReauthentication:YES
  645. callback:^(FIRAuthDataResult *_Nullable authResult,
  646. NSError *_Nullable error) {
  647. if (error) {
  648. // If "user not found" error returned by backend, translate to user mismatch error which is
  649. // more accurate.
  650. if (error.code == FIRAuthErrorCodeUserNotFound) {
  651. error = [FIRAuthErrorUtils userMismatchError];
  652. }
  653. callInMainThreadWithAuthDataResultAndError(completion, authResult, error);
  654. return;
  655. }
  656. if (![authResult.user.uid isEqual:[_auth getUID]]) {
  657. callInMainThreadWithAuthDataResultAndError(completion, authResult,
  658. [FIRAuthErrorUtils userMismatchError]);
  659. return;
  660. }
  661. // Successful reauthenticate
  662. [self setTokenService:authResult.user->_tokenService callback:^(NSError *_Nullable error) {
  663. callInMainThreadWithAuthDataResultAndError(completion, authResult, error);
  664. }];
  665. }];
  666. });
  667. }
  668. - (nullable NSString *)refreshToken {
  669. __block NSString *result;
  670. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  671. result = _tokenService.refreshToken;
  672. });
  673. return result;
  674. }
  675. - (void)getIDTokenWithCompletion:(nullable FIRAuthTokenCallback)completion {
  676. // |getTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to
  677. // global work queue here.
  678. [self getIDTokenForcingRefresh:NO completion:completion];
  679. }
  680. - (void)getTokenWithCompletion:(nullable FIRAuthTokenCallback)completion {
  681. [self getIDTokenWithCompletion:completion];
  682. }
  683. - (void)getIDTokenForcingRefresh:(BOOL)forceRefresh
  684. completion:(nullable FIRAuthTokenCallback)completion {
  685. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  686. [self internalGetTokenForcingRefresh:forceRefresh
  687. callback:^(NSString *_Nullable token, NSError *_Nullable error) {
  688. if (completion) {
  689. dispatch_async(dispatch_get_main_queue(), ^{
  690. completion(token, error);
  691. });
  692. }
  693. }];
  694. });
  695. }
  696. - (void)getTokenForcingRefresh:(BOOL)forceRefresh
  697. completion:(nullable FIRAuthTokenCallback)completion {
  698. [self getIDTokenForcingRefresh:forceRefresh completion:completion];
  699. }
  700. /** @fn internalGetTokenForcingRefresh:callback:
  701. @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired.
  702. @param callback The block to invoke when the token is available. Invoked asynchronously on the
  703. global work thread in the future.
  704. */
  705. - (void)internalGetTokenWithCallback:(nonnull FIRAuthTokenCallback)callback {
  706. [self internalGetTokenForcingRefresh:NO callback:callback];
  707. }
  708. - (void)internalGetTokenForcingRefresh:(BOOL)forceRefresh
  709. callback:(nonnull FIRAuthTokenCallback)callback {
  710. [_tokenService fetchAccessTokenForcingRefresh:forceRefresh
  711. callback:^(NSString *_Nullable token,
  712. NSError *_Nullable error,
  713. BOOL tokenUpdated) {
  714. if (error) {
  715. callback(nil, error);
  716. return;
  717. }
  718. if (tokenUpdated) {
  719. if (![self updateKeychain:&error]) {
  720. callback(nil, error);
  721. return;
  722. }
  723. [_auth notifyListenersOfAuthStateChangeWithUser:self token:token];
  724. }
  725. callback(token, nil);
  726. }];
  727. }
  728. - (void)linkWithCredential:(FIRAuthCredential *)credential
  729. completion:(nullable FIRAuthResultCallback)completion {
  730. FIRAuthDataResultCallback callback = ^(FIRAuthDataResult *_Nullable authResult,
  731. NSError *_Nullable error) {
  732. completion(authResult.user, error);
  733. };
  734. [self linkAndRetrieveDataWithCredential:credential completion:callback];
  735. }
  736. - (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential
  737. completion:(nullable FIRAuthDataResultCallback)completion {
  738. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  739. if (_providerData[credential.provider]) {
  740. callInMainThreadWithAuthDataResultAndError(completion,
  741. nil,
  742. [FIRAuthErrorUtils providerAlreadyLinkedError]);
  743. return;
  744. }
  745. FIRAuthDataResult *result =
  746. [[FIRAuthDataResult alloc] initWithUser:self additionalUserInfo:nil];
  747. if ([credential isKindOfClass:[FIREmailPasswordAuthCredential class]]) {
  748. if (_hasEmailPasswordCredential) {
  749. callInMainThreadWithAuthDataResultAndError(completion,
  750. nil,
  751. [FIRAuthErrorUtils providerAlreadyLinkedError]);
  752. return;
  753. }
  754. FIREmailPasswordAuthCredential *emailPasswordCredential =
  755. (FIREmailPasswordAuthCredential *)credential;
  756. [self updateEmail:emailPasswordCredential.email
  757. password:emailPasswordCredential.password
  758. callback:^(NSError *error) {
  759. if (error) {
  760. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  761. } else {
  762. callInMainThreadWithAuthDataResultAndError(completion, result, nil);
  763. }
  764. }];
  765. return;
  766. }
  767. #if TARGET_OS_IOS
  768. if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) {
  769. FIRPhoneAuthCredential *phoneAuthCredential = (FIRPhoneAuthCredential *)credential;
  770. [self internalUpdatePhoneNumberCredential:phoneAuthCredential
  771. completion:^(NSError *_Nullable error) {
  772. if (error){
  773. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  774. } else {
  775. callInMainThreadWithAuthDataResultAndError(completion, result, nil);
  776. }
  777. }];
  778. return;
  779. }
  780. #endif
  781. [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
  782. CallbackWithAuthDataResultAndError completeWithError =
  783. ^(FIRAuthDataResult *result, NSError *error) {
  784. complete();
  785. callInMainThreadWithAuthDataResultAndError(completion, result, error);
  786. };
  787. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  788. NSError *_Nullable error) {
  789. if (error) {
  790. completeWithError(nil, error);
  791. return;
  792. }
  793. FIRAuthRequestConfiguration *requestConfiguration = _auth.requestConfiguration;
  794. FIRVerifyAssertionRequest *request =
  795. [[FIRVerifyAssertionRequest alloc] initWithProviderID:credential.provider
  796. requestConfiguration:requestConfiguration];
  797. [credential prepareVerifyAssertionRequest:request];
  798. request.accessToken = accessToken;
  799. [FIRAuthBackend verifyAssertion:request
  800. callback:^(FIRVerifyAssertionResponse *response, NSError *error) {
  801. if (error) {
  802. completeWithError(nil, error);
  803. return;
  804. }
  805. FIRAdditionalUserInfo *additionalUserInfo =
  806. [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response];
  807. FIRAuthDataResult *result =
  808. [[FIRAuthDataResult alloc] initWithUser:self additionalUserInfo:additionalUserInfo];
  809. // Update the new token and refresh user info again.
  810. _tokenService = [[FIRSecureTokenService alloc]
  811. initWithRequestConfiguration:requestConfiguration
  812. accessToken:response.IDToken
  813. accessTokenExpirationDate:response.approximateExpirationDate
  814. refreshToken:response.refreshToken];
  815. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  816. NSError *_Nullable error) {
  817. if (error) {
  818. completeWithError(nil, error);
  819. return;
  820. }
  821. FIRGetAccountInfoRequest *getAccountInfoRequest =
  822. [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken
  823. requestConfiguration:requestConfiguration];
  824. [FIRAuthBackend getAccountInfo:getAccountInfoRequest
  825. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  826. NSError *_Nullable error) {
  827. if (error) {
  828. completeWithError(nil, error);
  829. return;
  830. }
  831. _anonymous = NO;
  832. [self updateWithGetAccountInfoResponse:response];
  833. if (![self updateKeychain:&error]) {
  834. completeWithError(nil, error);
  835. return;
  836. }
  837. completeWithError(result, nil);
  838. }];
  839. }];
  840. }];
  841. }];
  842. }];
  843. });
  844. }
  845. - (void)unlinkFromProvider:(NSString *)provider
  846. completion:(nullable FIRAuthResultCallback)completion {
  847. [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
  848. CallbackWithError completeAndCallbackWithError = ^(NSError *error) {
  849. complete();
  850. callInMainThreadWithUserAndError(completion, self, error);
  851. };
  852. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  853. NSError *_Nullable error) {
  854. if (error) {
  855. completeAndCallbackWithError(error);
  856. return;
  857. }
  858. FIRAuthRequestConfiguration *requestConfiguration = _auth.requestConfiguration;
  859. FIRSetAccountInfoRequest *setAccountInfoRequest =
  860. [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:requestConfiguration];
  861. setAccountInfoRequest.accessToken = accessToken;
  862. BOOL isEmailPasswordProvider = [provider isEqualToString:FIREmailAuthProviderID];
  863. if (isEmailPasswordProvider) {
  864. if (!_hasEmailPasswordCredential) {
  865. completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]);
  866. return;
  867. }
  868. setAccountInfoRequest.deleteAttributes = @[ FIRSetAccountInfoUserAttributePassword ];
  869. } else {
  870. if (!_providerData[provider]) {
  871. completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]);
  872. return;
  873. }
  874. setAccountInfoRequest.deleteProviders = @[ provider ];
  875. }
  876. [FIRAuthBackend setAccountInfo:setAccountInfoRequest
  877. callback:^(FIRSetAccountInfoResponse *_Nullable response,
  878. NSError *_Nullable error) {
  879. if (error) {
  880. completeAndCallbackWithError(error);
  881. return;
  882. }
  883. if (isEmailPasswordProvider) {
  884. _hasEmailPasswordCredential = NO;
  885. } else {
  886. // We can't just use the provider info objects in FIRSetAcccountInfoResponse because they
  887. // don't have localID and email fields. Remove the specific provider manually.
  888. NSMutableDictionary *mutableProviderData = [_providerData mutableCopy];
  889. [mutableProviderData removeObjectForKey:provider];
  890. _providerData = [mutableProviderData copy];
  891. #if TARGET_OS_IOS
  892. // After successfully unlinking a phone auth provider, remove the phone number from the
  893. // cached user info.
  894. if ([provider isEqualToString:FIRPhoneAuthProviderID]) {
  895. _phoneNumber = nil;
  896. }
  897. #endif
  898. }
  899. if (response.IDToken && response.refreshToken) {
  900. FIRSecureTokenService *tokenService = [[FIRSecureTokenService alloc]
  901. initWithRequestConfiguration:requestConfiguration
  902. accessToken:response.IDToken
  903. accessTokenExpirationDate:response.approximateExpirationDate
  904. refreshToken:response.refreshToken];
  905. [self setTokenService:tokenService callback:^(NSError *_Nullable error) {
  906. completeAndCallbackWithError(error);
  907. }];
  908. return;
  909. }
  910. if (![self updateKeychain:&error]) {
  911. completeAndCallbackWithError(error);
  912. return;
  913. }
  914. completeAndCallbackWithError(nil);
  915. }];
  916. }];
  917. }];
  918. }
  919. - (void)sendEmailVerificationWithCompletion:(nullable FIRSendEmailVerificationCallback)completion {
  920. [self sendEmailVerificationWithNullableActionCodeSettings:nil completion:completion];
  921. }
  922. - (void)sendEmailVerificationWithActionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings
  923. completion:(nullable FIRSendEmailVerificationCallback)
  924. completion {
  925. [self sendEmailVerificationWithNullableActionCodeSettings:actionCodeSettings
  926. completion:completion];
  927. }
  928. /** @fn sendEmailVerificationWithNullableActionCodeSettings:completion:
  929. @brief Initiates email verification for the user.
  930. @param actionCodeSettings Optionally, a @c FIRActionCodeSettings object containing settings
  931. related to the handling action codes.
  932. */
  933. - (void)sendEmailVerificationWithNullableActionCodeSettings:(nullable FIRActionCodeSettings *)
  934. actionCodeSettings
  935. completion:
  936. (nullable FIRSendEmailVerificationCallback)
  937. completion {
  938. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  939. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  940. NSError *_Nullable error) {
  941. if (error) {
  942. callInMainThreadWithError(completion, error);
  943. return;
  944. }
  945. FIRAuthRequestConfiguration *configuration = _auth.requestConfiguration;
  946. FIRGetOOBConfirmationCodeRequest *request =
  947. [FIRGetOOBConfirmationCodeRequest verifyEmailRequestWithAccessToken:accessToken
  948. actionCodeSettings:actionCodeSettings
  949. requestConfiguration:configuration];
  950. [FIRAuthBackend getOOBConfirmationCode:request
  951. callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable
  952. response,
  953. NSError *_Nullable error) {
  954. callInMainThreadWithError(completion, error);
  955. }];
  956. }];
  957. });
  958. }
  959. - (void)deleteWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
  960. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  961. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  962. NSError *_Nullable error) {
  963. if (error) {
  964. callInMainThreadWithError(completion, error);
  965. return;
  966. }
  967. FIRDeleteAccountRequest *deleteUserRequest =
  968. [[FIRDeleteAccountRequest alloc] initWitLocalID:_userID
  969. accessToken:accessToken
  970. requestConfiguration:_auth.requestConfiguration];
  971. [FIRAuthBackend deleteAccount:deleteUserRequest callback:^(NSError *_Nullable error) {
  972. if (error) {
  973. callInMainThreadWithError(completion, error);
  974. return;
  975. }
  976. if (![_auth signOutByForceWithUserID:_userID error:&error]) {
  977. callInMainThreadWithError(completion, error);
  978. return;
  979. }
  980. callInMainThreadWithError(completion, error);
  981. }];
  982. }];
  983. });
  984. }
  985. @end
  986. @implementation FIRUserProfileChangeRequest {
  987. /** @var _user
  988. @brief The user associated with the change request.
  989. */
  990. FIRUser *_user;
  991. /** @var _displayName
  992. @brief The display name value to set if @c _displayNameSet is YES.
  993. */
  994. NSString *_displayName;
  995. /** @var _displayNameSet
  996. @brief Indicates the display name should be part of the change request.
  997. */
  998. BOOL _displayNameSet;
  999. /** @var _photoURL
  1000. @brief The photo URL value to set if @c _displayNameSet is YES.
  1001. */
  1002. NSURL *_photoURL;
  1003. /** @var _photoURLSet
  1004. @brief Indicates the photo URL should be part of the change request.
  1005. */
  1006. BOOL _photoURLSet;
  1007. /** @var _consumed
  1008. @brief Indicates the @c commitChangesWithCallback: method has already been invoked.
  1009. */
  1010. BOOL _consumed;
  1011. }
  1012. - (nullable instancetype)initWithUser:(FIRUser *)user {
  1013. self = [super init];
  1014. if (self) {
  1015. _user = user;
  1016. }
  1017. return self;
  1018. }
  1019. - (nullable NSString *)displayName {
  1020. return _displayName;
  1021. }
  1022. - (void)setDisplayName:(nullable NSString *)displayName {
  1023. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1024. if (_consumed) {
  1025. [NSException raise:NSInternalInconsistencyException
  1026. format:@"%@",
  1027. @"Invalid call to setDisplayName: after commitChangesWithCallback:."];
  1028. return;
  1029. }
  1030. _displayNameSet = YES;
  1031. _displayName = [displayName copy];
  1032. });
  1033. }
  1034. - (nullable NSURL *)photoURL {
  1035. return _photoURL;
  1036. }
  1037. - (void)setPhotoURL:(nullable NSURL *)photoURL {
  1038. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1039. if (_consumed) {
  1040. [NSException raise:NSInternalInconsistencyException
  1041. format:@"%@",
  1042. @"Invalid call to setPhotoURL: after commitChangesWithCallback:."];
  1043. return;
  1044. }
  1045. _photoURLSet = YES;
  1046. _photoURL = [photoURL copy];
  1047. });
  1048. }
  1049. /** @fn hasUpdates
  1050. @brief Indicates at least one field has a value which needs to be committed.
  1051. */
  1052. - (BOOL)hasUpdates {
  1053. return _displayNameSet || _photoURLSet;
  1054. }
  1055. - (void)commitChangesWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
  1056. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1057. if (_consumed) {
  1058. [NSException raise:NSInternalInconsistencyException
  1059. format:@"%@",
  1060. @"commitChangesWithCallback: should only be called once."];
  1061. return;
  1062. }
  1063. _consumed = YES;
  1064. // Return fast if there is nothing to update:
  1065. if (![self hasUpdates]) {
  1066. callInMainThreadWithError(completion, nil);
  1067. return;
  1068. }
  1069. NSString *displayName = [_displayName copy];
  1070. BOOL displayNameWasSet = _displayNameSet;
  1071. NSURL *photoURL = [_photoURL copy];
  1072. BOOL photoURLWasSet = _photoURLSet;
  1073. [_user executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
  1074. FIRSetAccountInfoRequest *request) {
  1075. if (photoURLWasSet) {
  1076. request.photoURL = photoURL;
  1077. }
  1078. if (displayNameWasSet) {
  1079. request.displayName = displayName;
  1080. }
  1081. }
  1082. callback:^(NSError *_Nullable error) {
  1083. if (error) {
  1084. callInMainThreadWithError(completion, error);
  1085. return;
  1086. }
  1087. if (displayNameWasSet) {
  1088. [_user setDisplayName:displayName];
  1089. }
  1090. if (photoURLWasSet) {
  1091. [_user setPhotoURL:photoURL];
  1092. }
  1093. if (![_user updateKeychain:&error]) {
  1094. callInMainThreadWithError(completion, error);
  1095. return;
  1096. }
  1097. callInMainThreadWithError(completion, nil);
  1098. }];
  1099. });
  1100. }
  1101. @end
  1102. NS_ASSUME_NONNULL_END