/* * 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 "FIRAuthAppCredentialManager.h" #import "FIRAuthAppCredential.h" #import "FIRAuthGlobalWorkQueue.h" #import "FIRAuthKeychain.h" NS_ASSUME_NONNULL_BEGIN /** @var kKeychainDataKey @brief The keychain key for the data. */ static NSString *const kKeychainDataKey = @"app_credentials"; /** @var kFullCredentialKey @brief The data key for the full app credential. */ static NSString *const kFullCredentialKey = @"full_credential"; /** @var kPendingReceiptsKey @brief The data key for the array of pending receipts. */ static NSString *const kPendingReceiptsKey = @"pending_receipts"; /** @var kMaximumNumberOfPendingReceipts @brief The maximum number of partial credentials kept by this class. */ static const NSUInteger kMaximumNumberOfPendingReceipts = 32; @implementation FIRAuthAppCredentialManager { /** @var _keychain @brief The keychain for app credentials to load from and to save to. */ FIRAuthKeychain *_keychain; /** @var _pendingReceipts @brief A list of pending receipts sorted in the order they were recorded. */ NSMutableArray *_pendingReceipts; /** @var _callbacksByReceipt @brief A map from pending receipts to callbacks. */ NSMutableDictionary *_callbacksByReceipt; } - (instancetype)initWithKeychain:(FIRAuthKeychain *)keychain { self = [super init]; if (self) { _keychain = keychain; // Load the credentials from keychain if possible. NSError *error; NSData *encodedData = [_keychain dataForKey:kKeychainDataKey error:&error]; if (!error && encodedData) { NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedData]; FIRAuthAppCredential *credential = [unarchiver decodeObjectOfClass:[FIRAuthAppCredential class] forKey:kFullCredentialKey]; if ([credential isKindOfClass:[FIRAuthAppCredential class]]) { _credential = credential; } NSSet *allowedClasses = [NSSet setWithObjects:[NSArray class], [NSString class], nil]; NSArray *pendingReceipts = [unarchiver decodeObjectOfClasses:allowedClasses forKey:kPendingReceiptsKey]; if ([pendingReceipts isKindOfClass:[NSArray class]]) { _pendingReceipts = [pendingReceipts mutableCopy]; } } if (!_pendingReceipts) { _pendingReceipts = [[NSMutableArray alloc] init]; } _callbacksByReceipt = [[NSMutableDictionary alloc] init]; } return self; } - (NSUInteger)maximumNumberOfPendingReceipts { return kMaximumNumberOfPendingReceipts; } - (void)didStartVerificationWithReceipt:(NSString *)receipt timeout:(NSTimeInterval)timeout callback:(FIRAuthAppCredentialCallback)callback { [_pendingReceipts removeObject:receipt]; if (_pendingReceipts.count >= kMaximumNumberOfPendingReceipts) { [_pendingReceipts removeObjectAtIndex:0]; } [_pendingReceipts addObject:receipt]; _callbacksByReceipt[receipt] = callback; [self saveData]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), FIRAuthGlobalWorkQueue(), ^{ [self callBackWithReceipt:receipt]; }); } - (BOOL)canFinishVerificationWithReceipt:(NSString *)receipt secret:(NSString *)secret { if (![_pendingReceipts containsObject:receipt]) { return NO; } [_pendingReceipts removeObject:receipt]; _credential = [[FIRAuthAppCredential alloc] initWithReceipt:receipt secret:secret]; [self saveData]; [self callBackWithReceipt:receipt]; return YES; } - (void)clearCredential { _credential = nil; [self saveData]; } #pragma mark - Internal methods /** @fn saveData @brief Save the data in memory to the keychain ignoring any errors. */ - (void)saveData { NSMutableData *archiveData = [NSMutableData data]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiveData]; [archiver encodeObject:_credential forKey:kFullCredentialKey]; [archiver encodeObject:_pendingReceipts forKey:kPendingReceiptsKey]; [archiver finishEncoding]; [_keychain setData:archiveData forKey:kKeychainDataKey error:NULL]; } /** @fn callBackWithReceipt: @brief Calls the saved callback for the specifc receipt. @param receipt The receipt associated with the callback. */ - (void)callBackWithReceipt:(NSString *)receipt { FIRAuthAppCredentialCallback callback = _callbacksByReceipt[receipt]; if (!callback) { return; } [_callbacksByReceipt removeObjectForKey:receipt]; if (_credential) { callback(_credential); } else { callback([[FIRAuthAppCredential alloc] initWithReceipt:receipt secret:nil]); } } @end NS_ASSUME_NONNULL_END