AutorÃa | Ultima modificación | Ver Log |
/** Copyright 2019 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 "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h"#import <Security/Security.h>#if __has_include(<FBLPromises/FBLPromises.h>)#import <FBLPromises/FBLPromises.h>#else#import "FBLPromises.h"#endif#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainUtils.h"#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULSecureCoding.h"@interface GULKeychainStorage ()@property(nonatomic, readonly) dispatch_queue_t keychainQueue;@property(nonatomic, readonly) dispatch_queue_t inMemoryCacheQueue;@property(nonatomic, readonly) NSString *service;@property(nonatomic, readonly) NSCache<NSString *, id<NSSecureCoding>> *inMemoryCache;@end@implementation GULKeychainStorage- (instancetype)initWithService:(NSString *)service {NSCache *cache = [[NSCache alloc] init];// Cache up to 5 installations.cache.countLimit = 5;return [self initWithService:service cache:cache];}- (instancetype)initWithService:(NSString *)service cache:(NSCache *)cache {self = [super init];if (self) {_keychainQueue =dispatch_queue_create("com.gul.KeychainStorage.Keychain", DISPATCH_QUEUE_SERIAL);_inMemoryCacheQueue =dispatch_queue_create("com.gul.KeychainStorage.InMemoryCache", DISPATCH_QUEUE_SERIAL);_service = [service copy];_inMemoryCache = cache;}return self;}#pragma mark - Public- (FBLPromise<id<NSSecureCoding>> *)getObjectForKey:(NSString *)keyobjectClass:(Class)objectClassaccessGroup:(nullable NSString *)accessGroup {return [FBLPromise onQueue:self.inMemoryCacheQueuedo:^id _Nullable {// Return cached object or fail otherwise.id object = [self.inMemoryCache objectForKey:key];return object?: [[NSError alloc]initWithDomain:FBLPromiseErrorDomaincode:FBLPromiseErrorCodeValidationFailureuserInfo:nil];}].recover(^id _Nullable(NSError *error) {// Look for the object in the keychain.return [self getObjectFromKeychainForKey:keyobjectClass:objectClassaccessGroup:accessGroup];});}- (FBLPromise<NSNull *> *)setObject:(id<NSSecureCoding>)objectforKey:(NSString *)keyaccessGroup:(nullable NSString *)accessGroup {return [FBLPromise onQueue:self.inMemoryCacheQueuedo:^id _Nullable {// Save to the in-memory cache first.[self.inMemoryCache setObject:object forKey:[key copy]];return [NSNull null];}].thenOn(self.keychainQueue, ^id(id result) {// Then store the object to the keychain.NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];NSError *error;NSData *encodedObject = [GULSecureCoding archivedDataWithRootObject:object error:&error];if (!encodedObject) {return error;}if (![GULKeychainUtils setItem:encodedObject withQuery:query error:&error]) {return error;}return [NSNull null];});}- (FBLPromise<NSNull *> *)removeObjectForKey:(NSString *)keyaccessGroup:(nullable NSString *)accessGroup {return [FBLPromise onQueue:self.inMemoryCacheQueuedo:^id _Nullable {[self.inMemoryCache removeObjectForKey:key];return nil;}].thenOn(self.keychainQueue, ^id(id result) {NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];NSError *error;if (![GULKeychainUtils removeItemWithQuery:query error:&error]) {return error;}return [NSNull null];});}#pragma mark - Private- (FBLPromise<id<NSSecureCoding>> *)getObjectFromKeychainForKey:(NSString *)keyobjectClass:(Class)objectClassaccessGroup:(nullable NSString *)accessGroup {// Look for the object in the keychain.return [FBLPromiseonQueue:self.keychainQueuedo:^id {NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];NSError *error;NSData *encodedObject = [GULKeychainUtils getItemWithQuery:query error:&error];if (error) {return error;}if (!encodedObject) {return nil;}id object = [GULSecureCoding unarchivedObjectOfClass:objectClassfromData:encodedObjecterror:&error];if (error) {return error;}return object;}].thenOn(self.inMemoryCacheQueue,^id<NSSecureCoding> _Nullable(id<NSSecureCoding> _Nullable object) {// Save object to the in-memory cache if exists and return the object.if (object) {[self.inMemoryCache setObject:object forKey:[key copy]];}return object;});}- (void)resetInMemoryCache {[self.inMemoryCache removeAllObjects];}#pragma mark - Keychain- (NSMutableDictionary<NSString *, id> *)keychainQueryWithKey:(NSString *)keyaccessGroup:(nullable NSString *)accessGroup {NSMutableDictionary<NSString *, id> *query = [NSMutableDictionary dictionary];query[(__bridge NSString *)kSecClass] = (__bridge NSString *)kSecClassGenericPassword;query[(__bridge NSString *)kSecAttrService] = self.service;query[(__bridge NSString *)kSecAttrAccount] = key;if (accessGroup) {query[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;}#if TARGET_OS_OSXif (self.keychainRef) {query[(__bridge NSString *)kSecUseKeychain] = (__bridge id)(self.keychainRef);query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ];}#endif // TARGET_OSXreturn query;}@end