/* * 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 "FirebaseMessaging/Sources/Token/FIRMessagingCheckinStore.h" #import "FirebaseMessaging/Sources/FIRMessagingCode.h" #import "FirebaseMessaging/Sources/FIRMessagingConstants.h" #import "FirebaseMessaging/Sources/FIRMessagingLogger.h" #import "FirebaseMessaging/Sources/FIRMessagingUtilities.h" #import "FirebaseMessaging/Sources/NSError+FIRMessaging.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingAuthKeychain.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingBackupExcludedPlist.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinPreferences.h" #import "FirebaseMessaging/Sources/Token/FIRMessagingCheckinService.h" // NOTE: These values should be in sync with what InstanceID saves in as. static NSString *const kCheckinFileName = @"g-checkin"; static NSString *const kFIRMessagingCheckinKeychainGeneric = @"com.google.iid"; NSString *const kFIRMessagingCheckinKeychainService = @"com.google.iid.checkin"; @interface FIRMessagingCheckinStore () @property(nonatomic, readwrite, strong) FIRMessagingBackupExcludedPlist *plist; @property(nonatomic, readwrite, strong) FIRMessagingAuthKeychain *keychain; // Checkin will store items under // Keychain account: , // Keychain service: |kFIRMessagingCheckinKeychainService| @property(nonatomic, readonly) NSString *bundleIdentifierForKeychainAccount; @end @implementation FIRMessagingCheckinStore - (instancetype)init { self = [super init]; if (self) { _plist = [[FIRMessagingBackupExcludedPlist alloc] initWithFileName:kCheckinFileName subDirectory:kFIRMessagingInstanceIDSubDirectoryName]; _keychain = [[FIRMessagingAuthKeychain alloc] initWithIdentifier:kFIRMessagingCheckinKeychainGeneric]; } return self; } - (BOOL)hasCheckinPlist { return [self.plist doesFileExist]; } - (NSString *)bundleIdentifierForKeychainAccount { static NSString *bundleIdentifier; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ bundleIdentifier = FIRMessagingAppIdentifier(); }); return bundleIdentifier; } - (void)saveCheckinPreferences:(FIRMessagingCheckinPreferences *)preferences handler:(void (^)(NSError *error))handler { NSDictionary *checkinPlistContents = [preferences checkinPlistContents]; NSString *checkinKeychainContent = [preferences checkinKeychainContent]; if (![checkinKeychainContent length]) { NSString *failureReason = @"Failed to get checkin keychain content from memory."; FIRMessagingLoggerDebug(kFIRMessagingMessageCodeCheckinStore000, @"%@", failureReason); if (handler) { handler([NSError messagingErrorWithCode:kFIRMessagingErrorCodeRegistrarFailedToCheckIn failureReason:failureReason]); } return; } if (![checkinPlistContents count]) { NSString *failureReason = @"Failed to get checkin plist contents from memory."; FIRMessagingLoggerDebug(kFIRMessagingMessageCodeCheckinStore001, @"%@", failureReason); if (handler) { handler([NSError messagingErrorWithCode:kFIRMessagingErrorCodeRegistrarFailedToCheckIn failureReason:failureReason]); } return; } // Save all other checkin preferences in a plist NSError *error; if (![self.plist writeDictionary:checkinPlistContents error:&error]) { FIRMessagingLoggerDebug(kFIRMessagingMessageCodeCheckinStore003, @"Failed to save checkin plist contents." @"Will delete auth credentials"); [self.keychain removeItemsMatchingService:kFIRMessagingCheckinKeychainService account:self.bundleIdentifierForKeychainAccount handler:nil]; if (handler) { handler(error); } return; } FIRMessagingLoggerDebug(kFIRMessagingMessageCodeCheckinStoreCheckinPlistSaved, @"Checkin plist file is saved"); // Save the deviceID and secret in the Keychain if (!preferences.hasPreCachedAuthCredentials) { NSData *data = [checkinKeychainContent dataUsingEncoding:NSUTF8StringEncoding]; [self.keychain setData:data forService:kFIRMessagingCheckinKeychainService account:self.bundleIdentifierForKeychainAccount handler:^(NSError *error) { if (error) { if (handler) { handler(error); } return; } if (handler) { handler(nil); } }]; } else { handler(nil); } } - (void)removeCheckinPreferencesWithHandler:(void (^)(NSError *error))handler { // Delete the checkin preferences plist first to avoid delay. NSError *deletePlistError; if (![self.plist deleteFile:&deletePlistError]) { handler(deletePlistError); return; } FIRMessagingLoggerDebug(kFIRMessagingMessageCodeCheckinStoreCheckinPlistDeleted, @"Deleted checkin plist file."); // Remove deviceID and secret from Keychain [self.keychain removeItemsMatchingService:kFIRMessagingCheckinKeychainService account:self.bundleIdentifierForKeychainAccount handler:^(NSError *error) { handler(error); }]; } - (FIRMessagingCheckinPreferences *)cachedCheckinPreferences { // Query the keychain for deviceID and secret NSData *item = [self.keychain dataForService:kFIRMessagingCheckinKeychainService account:self.bundleIdentifierForKeychainAccount]; // Check info found in keychain NSString *checkinKeychainContent = [[NSString alloc] initWithData:item encoding:NSUTF8StringEncoding]; FIRMessagingCheckinPreferences *checkinPreferences = [FIRMessagingCheckinPreferences preferencesFromKeychainContents:[checkinKeychainContent copy]]; NSDictionary *checkinPlistContents = [self.plist contentAsDictionary]; NSString *plistDeviceAuthID = checkinPlistContents[kFIRMessagingDeviceAuthIdKey]; NSString *plistSecretToken = checkinPlistContents[kFIRMessagingSecretTokenKey]; // If deviceID and secret not found in the keychain verify that we don't have them in the // checkin preferences plist. if (![checkinPreferences.deviceID length] && ![checkinPreferences.secretToken length]) { if ([plistDeviceAuthID length] && [plistSecretToken length]) { // Couldn't find checkin credentials in keychain but found them in the plist. checkinPreferences = [[FIRMessagingCheckinPreferences alloc] initWithDeviceID:plistDeviceAuthID secretToken:plistSecretToken]; } else { // Couldn't find checkin credentials in keychain nor plist return nil; } } [checkinPreferences updateWithCheckinPlistContents:checkinPlistContents]; return checkinPreferences; } @end