FIRUser.m 74 KB

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