You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

240 lines
9.0 KiB

/*
* 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 "FIRInstanceIDStore.h"
#import "FIRInstanceIDCheckinPreferences.h"
#import "FIRInstanceIDCheckinStore.h"
#import "FIRInstanceIDConstants.h"
#import "FIRInstanceIDLogger.h"
#import "FIRInstanceIDTokenStore.h"
#import "FIRInstanceIDVersionUtilities.h"
// NOTE: These values should be in sync with what InstanceID saves in as.
static NSString *const kCheckinFileName = @"g-checkin";
// APNS token (use the old key value i.e. with prefix GMS)
static NSString *const kFIRInstanceIDLibraryVersion = @"GMSInstanceID-version";
@interface FIRInstanceIDStore ()
@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinStore *checkinStore;
@property(nonatomic, readwrite, strong) FIRInstanceIDTokenStore *tokenStore;
@end
@implementation FIRInstanceIDStore
- (instancetype)initWithDelegate:(NSObject<FIRInstanceIDStoreDelegate> *)delegate {
FIRInstanceIDCheckinStore *checkinStore = [[FIRInstanceIDCheckinStore alloc]
initWithCheckinPlistFileName:kCheckinFileName
subDirectoryName:kFIRInstanceIDSubDirectoryName];
FIRInstanceIDTokenStore *tokenStore = [FIRInstanceIDTokenStore defaultStore];
return [self initWithCheckinStore:checkinStore tokenStore:tokenStore delegate:delegate];
}
- (instancetype)initWithCheckinStore:(FIRInstanceIDCheckinStore *)checkinStore
tokenStore:(FIRInstanceIDTokenStore *)tokenStore
delegate:(NSObject<FIRInstanceIDStoreDelegate> *)delegate {
self = [super init];
if (self) {
_checkinStore = checkinStore;
_tokenStore = tokenStore;
_delegate = delegate;
[self resetCredentialsIfNeeded];
}
return self;
}
#pragma mark - Upgrades
+ (BOOL)hasSubDirectory:(NSString *)subDirectoryName {
NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName];
BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath
isDirectory:&isDirectory]) {
return NO;
} else if (!isDirectory) {
return NO;
}
return YES;
}
+ (NSSearchPathDirectory)supportedDirectory {
#if TARGET_OS_TV
return NSCachesDirectory;
#else
return NSApplicationSupportDirectory;
#endif
}
+ (NSString *)pathForSupportSubDirectory:(NSString *)subDirectoryName {
NSArray *directoryPaths =
NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES);
NSString *dirPath = directoryPaths.lastObject;
NSArray *components = @[ dirPath, subDirectoryName ];
return [NSString pathWithComponents:components];
}
+ (BOOL)createSubDirectory:(NSString *)subDirectoryName {
NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName];
BOOL hasSubDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath
isDirectory:&hasSubDirectory]) {
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:subDirectoryPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeStore000,
@"Cannot create directory %@, error: %@", subDirectoryPath, error);
return NO;
}
} else {
if (!hasSubDirectory) {
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeStore001,
@"Found file instead of directory at %@", subDirectoryPath);
return NO;
}
}
return YES;
}
+ (BOOL)removeSubDirectory:(NSString *)subDirectoryName error:(NSError **)error {
if ([self hasSubDirectory:subDirectoryName]) {
NSString *subDirectoryPath = [self pathForSupportSubDirectory:subDirectoryName];
BOOL isDirectory;
if ([[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath
isDirectory:&isDirectory]) {
return [[NSFileManager defaultManager] removeItemAtPath:subDirectoryPath error:error];
}
}
return YES;
}
/**
* Reset the keychain preferences if the app had been deleted earlier and then reinstalled.
* Keychain preferences are not cleared in the above scenario so explicitly clear them.
*
* In case of an iCloud backup and restore the Keychain preferences should already be empty
* since the Keychain items are marked with `*BackupThisDeviceOnly`.
*/
- (void)resetCredentialsIfNeeded {
BOOL checkinPlistExists = [self.checkinStore hasCheckinPlist];
// Checkin info existed in backup excluded plist. Should not be a fresh install.
if (checkinPlistExists) {
return;
}
// Resets checkin in keychain if a fresh install.
// Keychain can still exist even if app is uninstalled.
FIRInstanceIDCheckinPreferences *oldCheckinPreferences =
[self.checkinStore cachedCheckinPreferences];
if (oldCheckinPreferences) {
[self.checkinStore removeCheckinPreferencesWithHandler:^(NSError *error) {
if (!error) {
FIRInstanceIDLoggerDebug(
kFIRInstanceIDMessageCodeStore002,
@"Removed cached checkin preferences from Keychain because this is a fresh install.");
} else {
FIRInstanceIDLoggerError(
kFIRInstanceIDMessageCodeStore003,
@"Couldn't remove cached checkin preferences for a fresh install. Error: %@", error);
}
if (oldCheckinPreferences.deviceID.length && oldCheckinPreferences.secretToken.length) {
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeStore006,
@"App reset detected. Will delete server registrations.");
// We don't really need to delete old FCM tokens created via IID auth tokens since
// those tokens are already hashed by APNS token as the has so creating a new
// token should automatically delete the old-token.
[self.delegate store:self didDeleteFCMScopedTokensForCheckin:oldCheckinPreferences];
} else {
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeStore009,
@"App reset detected but no valid checkin auth preferences found."
@" Will not delete server registrations.");
}
}];
}
}
#pragma mark - Get
- (FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity
scope:(NSString *)scope {
// TODO(chliangGoogle): If we don't have the token plist we should delete all the tokens from
// the keychain. This is because not having the plist signifies a backup and restore operation.
// In case the keychain has any tokens these would now be stale and therefore should be
// deleted.
if (![authorizedEntity length] || ![scope length]) {
return nil;
}
FIRInstanceIDTokenInfo *info = [self.tokenStore tokenInfoWithAuthorizedEntity:authorizedEntity
scope:scope];
return info;
}
- (NSArray<FIRInstanceIDTokenInfo *> *)cachedTokenInfos {
return [self.tokenStore cachedTokenInfos];
}
#pragma mark - Save
- (void)saveTokenInfo:(FIRInstanceIDTokenInfo *)tokenInfo
handler:(void (^)(NSError *error))handler {
[self.tokenStore saveTokenInfo:tokenInfo handler:handler];
}
#pragma mark - Delete
- (void)removeCachedTokenWithAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope {
if (![authorizedEntity length] || ![scope length]) {
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeStore012,
@"Will not delete token with invalid entity: %@, scope: %@",
authorizedEntity, scope);
return;
}
[self.tokenStore removeTokenWithAuthorizedEntity:authorizedEntity scope:scope];
}
- (void)removeAllCachedTokensWithHandler:(void (^)(NSError *error))handler {
[self.tokenStore removeAllTokensWithHandler:handler];
}
#pragma mark - FIRInstanceIDCheckinCache protocol
- (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences
handler:(void (^)(NSError *error))handler {
[self.checkinStore saveCheckinPreferences:preferences handler:handler];
}
- (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences {
return [self.checkinStore cachedCheckinPreferences];
}
- (void)removeCheckinPreferencesWithHandler:(void (^)(NSError *))handler {
[self.checkinStore removeCheckinPreferencesWithHandler:^(NSError *error) {
if (handler) {
handler(error);
}
}];
}
@end