FIRUser.m 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648
  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. publicKeyURL:gameCenterCredential.publicKeyURL
  1089. signature:gameCenterCredential.signature
  1090. salt:gameCenterCredential.salt
  1091. timestamp:gameCenterCredential.timestamp
  1092. displayName:gameCenterCredential.displayName
  1093. requestConfiguration:requestConfiguration];
  1094. gameCenterRequest.accessToken = accessToken;
  1095. [FIRAuthBackend
  1096. signInWithGameCenter:gameCenterRequest
  1097. callback:^(FIRSignInWithGameCenterResponse *_Nullable response,
  1098. NSError *_Nullable error) {
  1099. if (error) {
  1100. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  1101. } else {
  1102. // Update the new token and refresh user info again.
  1103. self->_tokenService = [[FIRSecureTokenService alloc]
  1104. initWithRequestConfiguration:requestConfiguration
  1105. accessToken:response.IDToken
  1106. accessTokenExpirationDate:response.approximateExpirationDate
  1107. refreshToken:response.refreshToken];
  1108. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1109. NSError *_Nullable error) {
  1110. if (error) {
  1111. callInMainThreadWithAuthDataResultAndError(completion, nil, error);
  1112. return;
  1113. }
  1114. FIRGetAccountInfoRequest *getAccountInfoRequest =
  1115. [[FIRGetAccountInfoRequest alloc]
  1116. initWithAccessToken:accessToken
  1117. requestConfiguration:requestConfiguration];
  1118. [FIRAuthBackend
  1119. getAccountInfo:getAccountInfoRequest
  1120. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  1121. NSError *_Nullable error) {
  1122. if (error) {
  1123. [self signOutIfTokenIsInvalidWithError:error];
  1124. callInMainThreadWithAuthDataResultAndError(completion,
  1125. nil, error);
  1126. return;
  1127. }
  1128. self.anonymous = NO;
  1129. [self updateWithGetAccountInfoResponse:response];
  1130. if (![self updateKeychain:&error]) {
  1131. callInMainThreadWithAuthDataResultAndError(completion,
  1132. nil, error);
  1133. return;
  1134. }
  1135. callInMainThreadWithAuthDataResultAndError(completion,
  1136. result, nil);
  1137. }];
  1138. }];
  1139. }
  1140. }];
  1141. }];
  1142. return;
  1143. }
  1144. #if TARGET_OS_IOS
  1145. if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) {
  1146. FIRPhoneAuthCredential *phoneAuthCredential = (FIRPhoneAuthCredential *)credential;
  1147. [self internalUpdateOrLinkPhoneNumberCredential:phoneAuthCredential
  1148. isLinkOperation:YES
  1149. completion:^(NSError *_Nullable error) {
  1150. if (error) {
  1151. callInMainThreadWithAuthDataResultAndError(
  1152. completion, nil, error);
  1153. } else {
  1154. callInMainThreadWithAuthDataResultAndError(
  1155. completion, result, nil);
  1156. }
  1157. }];
  1158. return;
  1159. }
  1160. #endif
  1161. [self->_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
  1162. CallbackWithAuthDataResultAndError completeWithError =
  1163. ^(FIRAuthDataResult *result, NSError *error) {
  1164. complete();
  1165. callInMainThreadWithAuthDataResultAndError(completion, result, error);
  1166. };
  1167. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1168. NSError *_Nullable error) {
  1169. if (error) {
  1170. completeWithError(nil, error);
  1171. return;
  1172. }
  1173. FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;
  1174. FIRVerifyAssertionRequest *request =
  1175. [[FIRVerifyAssertionRequest alloc] initWithProviderID:credential.provider
  1176. requestConfiguration:requestConfiguration];
  1177. [credential prepareVerifyAssertionRequest:request];
  1178. request.accessToken = accessToken;
  1179. [FIRAuthBackend
  1180. verifyAssertion:request
  1181. callback:^(FIRVerifyAssertionResponse *response, NSError *error) {
  1182. if (error) {
  1183. [self signOutIfTokenIsInvalidWithError:error];
  1184. completeWithError(nil, error);
  1185. return;
  1186. }
  1187. FIRAdditionalUserInfo *additionalUserInfo =
  1188. [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response];
  1189. FIROAuthCredential *updatedOAuthCredential =
  1190. [[FIROAuthCredential alloc] initWithVerifyAssertionResponse:response];
  1191. FIRAuthDataResult *result =
  1192. [[FIRAuthDataResult alloc] initWithUser:self
  1193. additionalUserInfo:additionalUserInfo
  1194. credential:updatedOAuthCredential];
  1195. // Update the new token and refresh user info again.
  1196. self->_tokenService = [[FIRSecureTokenService alloc]
  1197. initWithRequestConfiguration:requestConfiguration
  1198. accessToken:response.IDToken
  1199. accessTokenExpirationDate:response.approximateExpirationDate
  1200. refreshToken:response.refreshToken];
  1201. [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken,
  1202. NSError *_Nullable error) {
  1203. if (error) {
  1204. completeWithError(nil, error);
  1205. return;
  1206. }
  1207. FIRGetAccountInfoRequest *getAccountInfoRequest =
  1208. [[FIRGetAccountInfoRequest alloc]
  1209. initWithAccessToken:accessToken
  1210. requestConfiguration:requestConfiguration];
  1211. [FIRAuthBackend
  1212. getAccountInfo:getAccountInfoRequest
  1213. callback:^(FIRGetAccountInfoResponse *_Nullable response,
  1214. NSError *_Nullable error) {
  1215. if (error) {
  1216. [self signOutIfTokenIsInvalidWithError:error];
  1217. completeWithError(nil, error);
  1218. return;
  1219. }
  1220. self.anonymous = NO;
  1221. [self updateWithGetAccountInfoResponse:response];
  1222. if (![self updateKeychain:&error]) {
  1223. completeWithError(nil, error);
  1224. return;
  1225. }
  1226. completeWithError(result, nil);
  1227. }];
  1228. }];
  1229. }];
  1230. }];
  1231. }];
  1232. });
  1233. }
  1234. - (void)linkWithProvider:(id<FIRFederatedAuthProvider>)provider
  1235. UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
  1236. completion:(nullable FIRAuthDataResultCallback)completion {
  1237. #if TARGET_OS_IOS
  1238. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  1239. [provider getCredentialWithUIDelegate:UIDelegate
  1240. completion:^(FIRAuthCredential *_Nullable credential,
  1241. NSError *_Nullable error) {
  1242. if (error) {
  1243. completion(nil, error);
  1244. return;
  1245. }
  1246. [self linkWithCredential:credential completion:completion];
  1247. }];
  1248. });
  1249. #endif // TARGET_OS_IOS
  1250. }
  1251. - (void)unlinkFromProvider:(NSString *)provider
  1252. completion:(nullable FIRAuthResultCallback)completion {
  1253. [_taskQueue enqueueTask:^(FIRAuthSerialTaskCompletionBlock _Nonnull complete) {
  1254. CallbackWithError completeAndCallbackWithError = ^(NSError *error) {
  1255. complete();
  1256. callInMainThreadWithUserAndError(completion, self, error);
  1257. };
  1258. [self
  1259. internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  1260. if (error) {
  1261. completeAndCallbackWithError(error);
  1262. return;
  1263. }
  1264. FIRAuthRequestConfiguration *requestConfiguration = self->_auth.requestConfiguration;
  1265. FIRSetAccountInfoRequest *setAccountInfoRequest =
  1266. [[FIRSetAccountInfoRequest alloc] initWithRequestConfiguration:requestConfiguration];
  1267. setAccountInfoRequest.accessToken = accessToken;
  1268. if (!self->_providerData[provider]) {
  1269. completeAndCallbackWithError([FIRAuthErrorUtils noSuchProviderError]);
  1270. return;
  1271. }
  1272. setAccountInfoRequest.deleteProviders = @[ provider ];
  1273. [FIRAuthBackend
  1274. setAccountInfo:setAccountInfoRequest
  1275. callback:^(FIRSetAccountInfoResponse *_Nullable response,
  1276. NSError *_Nullable error) {
  1277. if (error) {
  1278. [self signOutIfTokenIsInvalidWithError:error];
  1279. completeAndCallbackWithError(error);
  1280. return;
  1281. }
  1282. // We can't just use the provider info objects in FIRSetAccountInfoResponse
  1283. // because they don't have localID and email fields. Remove the specific
  1284. // provider manually.
  1285. NSMutableDictionary *mutableProviderData = [self->_providerData mutableCopy];
  1286. [mutableProviderData removeObjectForKey:provider];
  1287. self->_providerData = [mutableProviderData copy];
  1288. if ([provider isEqualToString:FIREmailAuthProviderID]) {
  1289. self->_hasEmailPasswordCredential = NO;
  1290. }
  1291. #if TARGET_OS_IOS
  1292. // After successfully unlinking a phone auth provider, remove the phone number
  1293. // from the cached user info.
  1294. if ([provider isEqualToString:FIRPhoneAuthProviderID]) {
  1295. self->_phoneNumber = nil;
  1296. }
  1297. #endif
  1298. if (response.IDToken && response.refreshToken) {
  1299. FIRSecureTokenService *tokenService = [[FIRSecureTokenService alloc]
  1300. initWithRequestConfiguration:requestConfiguration
  1301. accessToken:response.IDToken
  1302. accessTokenExpirationDate:response.approximateExpirationDate
  1303. refreshToken:response.refreshToken];
  1304. [self setTokenService:tokenService
  1305. callback:^(NSError *_Nullable error) {
  1306. completeAndCallbackWithError(error);
  1307. }];
  1308. return;
  1309. }
  1310. if (![self updateKeychain:&error]) {
  1311. completeAndCallbackWithError(error);
  1312. return;
  1313. }
  1314. completeAndCallbackWithError(nil);
  1315. }];
  1316. }];
  1317. }];
  1318. }
  1319. - (void)sendEmailVerificationWithCompletion:(nullable FIRSendEmailVerificationCallback)completion {
  1320. [self sendEmailVerificationWithNullableActionCodeSettings:nil completion:completion];
  1321. }
  1322. - (void)sendEmailVerificationWithActionCodeSettings:(FIRActionCodeSettings *)actionCodeSettings
  1323. completion:
  1324. (nullable FIRSendEmailVerificationCallback)completion {
  1325. [self sendEmailVerificationWithNullableActionCodeSettings:actionCodeSettings
  1326. completion:completion];
  1327. }
  1328. /** @fn sendEmailVerificationWithNullableActionCodeSettings:completion:
  1329. @brief Initiates email verification for the user.
  1330. @param actionCodeSettings Optionally, a @c FIRActionCodeSettings object containing settings
  1331. related to the handling action codes.
  1332. */
  1333. - (void)sendEmailVerificationWithNullableActionCodeSettings:
  1334. (nullable FIRActionCodeSettings *)actionCodeSettings
  1335. completion:
  1336. (nullable FIRSendEmailVerificationCallback)
  1337. completion {
  1338. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  1339. [self
  1340. internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  1341. if (error) {
  1342. callInMainThreadWithError(completion, error);
  1343. return;
  1344. }
  1345. FIRAuthRequestConfiguration *configuration = self->_auth.requestConfiguration;
  1346. FIRGetOOBConfirmationCodeRequest *request =
  1347. [FIRGetOOBConfirmationCodeRequest verifyEmailRequestWithAccessToken:accessToken
  1348. actionCodeSettings:actionCodeSettings
  1349. requestConfiguration:configuration];
  1350. [FIRAuthBackend
  1351. getOOBConfirmationCode:request
  1352. callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response,
  1353. NSError *_Nullable error) {
  1354. [self signOutIfTokenIsInvalidWithError:error];
  1355. callInMainThreadWithError(completion, error);
  1356. }];
  1357. }];
  1358. });
  1359. }
  1360. - (void)deleteWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
  1361. dispatch_async(FIRAuthGlobalWorkQueue(), ^{
  1362. [self
  1363. internalGetTokenWithCallback:^(NSString *_Nullable accessToken, NSError *_Nullable error) {
  1364. if (error) {
  1365. callInMainThreadWithError(completion, error);
  1366. return;
  1367. }
  1368. FIRDeleteAccountRequest *deleteUserRequest =
  1369. [[FIRDeleteAccountRequest alloc] initWitLocalID:self->_userID
  1370. accessToken:accessToken
  1371. requestConfiguration:self->_auth.requestConfiguration];
  1372. [FIRAuthBackend deleteAccount:deleteUserRequest
  1373. callback:^(NSError *_Nullable error) {
  1374. if (error) {
  1375. callInMainThreadWithError(completion, error);
  1376. return;
  1377. }
  1378. if (![self->_auth signOutByForceWithUserID:self->_userID
  1379. error:&error]) {
  1380. callInMainThreadWithError(completion, error);
  1381. return;
  1382. }
  1383. callInMainThreadWithError(completion, error);
  1384. }];
  1385. }];
  1386. });
  1387. }
  1388. /** @fn signOutIfTokenIsInvalidWithError:
  1389. @brief Signs out this user if the user or the token is invalid.
  1390. @param error The error from the server.
  1391. */
  1392. - (void)signOutIfTokenIsInvalidWithError:(nullable NSError *)error {
  1393. NSInteger errorCode = error.code;
  1394. if (errorCode == FIRAuthErrorCodeUserNotFound || errorCode == FIRAuthErrorCodeUserDisabled ||
  1395. errorCode == FIRAuthErrorCodeInvalidUserToken ||
  1396. errorCode == FIRAuthErrorCodeUserTokenExpired) {
  1397. FIRLogNotice(kFIRLoggerAuth, @"I-AUT000016",
  1398. @"Invalid user token detected, user is automatically signed out.");
  1399. [_auth signOutByForceWithUserID:_userID error:NULL];
  1400. }
  1401. }
  1402. @end
  1403. @implementation FIRUserProfileChangeRequest {
  1404. /** @var _user
  1405. @brief The user associated with the change request.
  1406. */
  1407. FIRUser *_user;
  1408. /** @var _displayName
  1409. @brief The display name value to set if @c _displayNameSet is YES.
  1410. */
  1411. NSString *_displayName;
  1412. /** @var _displayNameSet
  1413. @brief Indicates the display name should be part of the change request.
  1414. */
  1415. BOOL _displayNameSet;
  1416. /** @var _photoURL
  1417. @brief The photo URL value to set if @c _displayNameSet is YES.
  1418. */
  1419. NSURL *_photoURL;
  1420. /** @var _photoURLSet
  1421. @brief Indicates the photo URL should be part of the change request.
  1422. */
  1423. BOOL _photoURLSet;
  1424. /** @var _consumed
  1425. @brief Indicates the @c commitChangesWithCallback: method has already been invoked.
  1426. */
  1427. BOOL _consumed;
  1428. }
  1429. - (nullable instancetype)initWithUser:(FIRUser *)user {
  1430. self = [super init];
  1431. if (self) {
  1432. _user = user;
  1433. }
  1434. return self;
  1435. }
  1436. - (nullable NSString *)displayName {
  1437. return _displayName;
  1438. }
  1439. - (void)setDisplayName:(nullable NSString *)displayName {
  1440. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1441. if (self->_consumed) {
  1442. [NSException
  1443. raise:NSInternalInconsistencyException
  1444. format:@"%@", @"Invalid call to setDisplayName: after commitChangesWithCallback:."];
  1445. return;
  1446. }
  1447. self->_displayNameSet = YES;
  1448. self->_displayName = [displayName copy];
  1449. });
  1450. }
  1451. - (nullable NSURL *)photoURL {
  1452. return _photoURL;
  1453. }
  1454. - (void)setPhotoURL:(nullable NSURL *)photoURL {
  1455. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1456. if (self->_consumed) {
  1457. [NSException raise:NSInternalInconsistencyException
  1458. format:@"%@", @"Invalid call to setPhotoURL: after commitChangesWithCallback:."];
  1459. return;
  1460. }
  1461. self->_photoURLSet = YES;
  1462. self->_photoURL = [photoURL copy];
  1463. });
  1464. }
  1465. /** @fn hasUpdates
  1466. @brief Indicates at least one field has a value which needs to be committed.
  1467. */
  1468. - (BOOL)hasUpdates {
  1469. return _displayNameSet || _photoURLSet;
  1470. }
  1471. - (void)commitChangesWithCompletion:(nullable FIRUserProfileChangeCallback)completion {
  1472. dispatch_sync(FIRAuthGlobalWorkQueue(), ^{
  1473. if (self->_consumed) {
  1474. [NSException raise:NSInternalInconsistencyException
  1475. format:@"%@", @"commitChangesWithCallback: should only be called once."];
  1476. return;
  1477. }
  1478. self->_consumed = YES;
  1479. // Return fast if there is nothing to update:
  1480. if (![self hasUpdates]) {
  1481. callInMainThreadWithError(completion, nil);
  1482. return;
  1483. }
  1484. NSString *displayName = [self->_displayName copy];
  1485. BOOL displayNameWasSet = self->_displayNameSet;
  1486. NSURL *photoURL = [self->_photoURL copy];
  1487. BOOL photoURLWasSet = self->_photoURLSet;
  1488. [self->_user
  1489. executeUserUpdateWithChanges:^(FIRGetAccountInfoResponseUser *user,
  1490. FIRSetAccountInfoRequest *request) {
  1491. if (photoURLWasSet) {
  1492. request.photoURL = photoURL;
  1493. }
  1494. if (displayNameWasSet) {
  1495. request.displayName = displayName;
  1496. }
  1497. }
  1498. callback:^(NSError *_Nullable error) {
  1499. if (error) {
  1500. callInMainThreadWithError(completion, error);
  1501. return;
  1502. }
  1503. if (displayNameWasSet) {
  1504. [self->_user setDisplayName:displayName];
  1505. }
  1506. if (photoURLWasSet) {
  1507. [self->_user setPhotoURL:photoURL];
  1508. }
  1509. if (![self->_user updateKeychain:&error]) {
  1510. callInMainThreadWithError(completion, error);
  1511. return;
  1512. }
  1513. callInMainThreadWithError(completion, nil);
  1514. }];
  1515. });
  1516. }
  1517. @end
  1518. NS_ASSUME_NONNULL_END