diff options
Diffstat (limited to 'Firestore/Source/Auth/FSTCredentialsProvider.m')
-rw-r--r-- | Firestore/Source/Auth/FSTCredentialsProvider.m | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/Firestore/Source/Auth/FSTCredentialsProvider.m b/Firestore/Source/Auth/FSTCredentialsProvider.m new file mode 100644 index 0000000..cec7c2b --- /dev/null +++ b/Firestore/Source/Auth/FSTCredentialsProvider.m @@ -0,0 +1,161 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FSTCredentialsProvider.h" + +#import <FirebaseCommunity/FIRApp.h> +#import <FirebaseCommunity/FIRAuth.h> +#import <FirebaseCommunity/FIRUser.h> +#import <GRPCClient/GRPCCall.h> + +// This is not an exported header so it's not visible via FirebaseCommunity +#import "FIRAppInternal.h" + +#import "FIRFirestoreErrors.h" +#import "FSTAssert.h" +#import "FSTClasses.h" +#import "FSTDispatchQueue.h" +#import "FSTUser.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - FSTGetTokenResult + +@implementation FSTGetTokenResult +- (instancetype)initWithUser:(FSTUser *)user token:(NSString *_Nullable)token { + if (self = [super init]) { + _user = user; + _token = token; + } + return self; +} +@end + +#pragma mark - FSTFirebaseCredentialsProvider +// TODO(mikelehen): Currently, we have a strong dependency on FIRAuth but we should ideally use +// only internal APIs on FIRApp instead. However, currently the FIRApp internal APIs don't expose +// the uid of the current user and don't expose an auth state change listener. So we use FIRAuth. +@interface FSTFirebaseCredentialsProvider () + +@property(nonatomic, strong, readonly) FIRApp *app; +@property(nonatomic, strong, readonly) FIRAuth *auth; + +/** Handle used to stop receiving auth changes once userChangeListener is removed. */ +@property(nonatomic, strong, nullable, readwrite) + FIRAuthStateDidChangeListenerHandle authListenerHandle; + +/** The current user as reported to us via our AuthStateDidChangeListener. */ +@property(nonatomic, strong, nonnull, readwrite) FSTUser *currentUser; + +/** + * Counter used to detect if the user changed while a -getTokenForcingRefresh: request was + * outstanding. + */ +@property(nonatomic, assign, readwrite) int userCounter; + +@end + +@implementation FSTFirebaseCredentialsProvider { + FSTVoidUserBlock _userChangeListener; +} + +- (instancetype)initWithApp:(FIRApp *)app { + self = [super init]; + if (self) { + _app = app; + _auth = [FIRAuth authWithApp:app]; + _currentUser = [[FSTUser alloc] initWithUID:self.auth.currentUser.uid]; + _userCounter = 0; + + // Register for user changes so that we can internally track the current user. + FSTWeakify(self); + _authListenerHandle = [self.auth addAuthStateDidChangeListener:^(FIRAuth *auth, FIRUser *user) { + FSTStrongify(self); + if (self) { + @synchronized(self) { + FSTUser *newUser = [[FSTUser alloc] initWithUID:user.uid]; + if (![newUser isEqual:self.currentUser]) { + self.currentUser = newUser; + self.userCounter++; + FSTVoidUserBlock listenerBlock = self.userChangeListener; + if (listenerBlock) { + listenerBlock(self.currentUser); + } + } + } + } + }]; + } + return self; +} + +- (void)getTokenForcingRefresh:(BOOL)forceRefresh + completion:(FSTVoidGetTokenResultBlock)completion { + FSTAssert(self.authListenerHandle, @"getToken cannot be called after listener removed."); + + // Take note of the current value of the userCounter so that this method can fail (with a + // FIRFirestoreErrorCodeAborted error) if there is a user change while the request is outstanding. + int initialUserCounter = self.userCounter; + + void (^getTokenCallback)(NSString *, NSError *) = ^(NSString *_Nullable token, + NSError *_Nullable error) { + @synchronized(self) { + if (initialUserCounter != self.userCounter) { + // Cancel the request since the user changed while the request was outstanding so the + // response is likely for a previous user (which user, we can't be sure). + NSDictionary *errorInfo = @{ @"details" : @"getToken aborted due to user change." }; + NSError *cancelError = [NSError errorWithDomain:FIRFirestoreErrorDomain + code:FIRFirestoreErrorCodeAborted + userInfo:errorInfo]; + completion(nil, cancelError); + } else { + FSTGetTokenResult *result = + [[FSTGetTokenResult alloc] initWithUser:self.currentUser token:token]; + completion(result, error); + } + }; + }; + + [self.app getTokenForcingRefresh:forceRefresh withCallback:getTokenCallback]; +} + +- (void)setUserChangeListener:(nullable FSTVoidUserBlock)block { + @synchronized(self) { + if (block) { + FSTAssert(!_userChangeListener, @"UserChangeListener set twice!"); + + // Fire initial event. + block(self.currentUser); + } else { + FSTAssert(self.authListenerHandle, @"UserChangeListener removed twice!"); + FSTAssert(_userChangeListener, @"UserChangeListener removed without being set!"); + [self.auth removeAuthStateDidChangeListener:self.authListenerHandle]; + + self.authListenerHandle = nil; + } + _userChangeListener = block; + } +} + +- (nullable FSTVoidUserBlock)userChangeListener { + @synchronized(self) { + return _userChangeListener; + } +} + +@end + +NS_ASSUME_NONNULL_END |