/* * 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 "FIRInstanceIDTokenStore.h" #import "FIRInstanceIDAuthKeyChain.h" #import "FIRInstanceIDConstants.h" #import "FIRInstanceIDLogger.h" #import "FIRInstanceIDTokenInfo.h" #import "FIRInstanceIDUtilities.h" static NSString *const kFIRInstanceIDTokenKeychainId = @"com.google.iid-tokens"; @interface FIRInstanceIDTokenStore () @property(nonatomic, readwrite, strong) FIRInstanceIDAuthKeychain *keychain; @end @implementation FIRInstanceIDTokenStore + (instancetype)defaultStore { FIRInstanceIDAuthKeychain *tokenKeychain = [[FIRInstanceIDAuthKeychain alloc] initWithIdentifier:kFIRInstanceIDTokenKeychainId]; return [[FIRInstanceIDTokenStore alloc] initWithKeychain:tokenKeychain]; } - (instancetype)initWithKeychain:(FIRInstanceIDAuthKeychain *)keychain { self = [super init]; if (self) { _keychain = keychain; } return self; } #pragma mark - Get + (NSString *)serviceKeyForAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope { return [NSString stringWithFormat:@"%@:%@", authorizedEntity, scope]; } - (nullable FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope { NSString *account = FIRInstanceIDAppIdentifier(); NSString *service = [[self class] serviceKeyForAuthorizedEntity:authorizedEntity scope:scope]; NSData *item = [self.keychain dataForService:service account:account]; if (!item) { return nil; } // Token infos created from legacy storage don't have appVersion, firebaseAppID, or APNSInfo. FIRInstanceIDTokenInfo *tokenInfo = [[self class] tokenInfoFromKeychainItem:item]; return tokenInfo; } - (NSArray *)cachedTokenInfos { NSString *account = FIRInstanceIDAppIdentifier(); NSArray *items = [self.keychain itemsMatchingService:kFIRInstanceIDKeychainWildcardIdentifier account:account]; NSMutableArray *tokenInfos = [NSMutableArray arrayWithCapacity:items.count]; for (NSData *item in items) { FIRInstanceIDTokenInfo *tokenInfo = [[self class] tokenInfoFromKeychainItem:item]; if (tokenInfo) { [tokenInfos addObject:tokenInfo]; } } return tokenInfos; } + (nullable FIRInstanceIDTokenInfo *)tokenInfoFromKeychainItem:(NSData *)item { // Check if it is saved as an archived FIRInstanceIDTokenInfo, otherwise return nil. FIRInstanceIDTokenInfo *tokenInfo = nil; // NOTE: Passing in nil to unarchiveObjectWithData will result in an iOS error logged // in the console on iOS 10 and below. Avoid by checking item.data's existence. if (item) { // TODO(chliangGoogle: Use the new API and secureCoding protocol. @try { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" tokenInfo = [NSKeyedUnarchiver unarchiveObjectWithData:item]; #pragma clang diagnostic pop } @catch (NSException *exception) { FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenStoreExceptionUnarchivingTokenInfo, @"Unable to parse token info from Keychain item; item was in an " @"invalid format"); tokenInfo = nil; } @finally { } } return tokenInfo; } #pragma mark - Save // Token Infos will be saved under these Keychain keys: // Account:
(e.g. com.mycompany.myapp) // Service: : (e.g. 1234567890:*) - (void)saveTokenInfo:(FIRInstanceIDTokenInfo *)tokenInfo handler:(void (^)(NSError *))handler { // Keep the cachetime up-to-date. tokenInfo.cacheTime = [NSDate date]; // Always write to the Keychain, so that the cacheTime is up-to-date. NSData *tokenInfoData; // TODO(chliangGoogle: Use the new API and secureCoding protocol. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" tokenInfoData = [NSKeyedArchiver archivedDataWithRootObject:tokenInfo]; #pragma clang diagnostic pop NSString *account = FIRInstanceIDAppIdentifier(); NSString *service = [[self class] serviceKeyForAuthorizedEntity:tokenInfo.authorizedEntity scope:tokenInfo.scope]; [self.keychain setData:tokenInfoData forService:service accessibility:NULL account:account handler:handler]; } #pragma mark - Delete - (void)removeTokenWithAuthorizedEntity:(nonnull NSString *)authorizedEntity scope:(nonnull NSString *)scope { NSString *account = FIRInstanceIDAppIdentifier(); NSString *service = [[self class] serviceKeyForAuthorizedEntity:authorizedEntity scope:scope]; [self.keychain removeItemsMatchingService:service account:account handler:nil]; } - (void)removeAllTokensWithHandler:(void (^)(NSError *error))handler { NSString *account = FIRInstanceIDAppIdentifier(); [self.keychain removeItemsMatchingService:kFIRInstanceIDKeychainWildcardIdentifier account:account handler:handler]; } @end