Archived
Push from command line
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
// The format of the debug code will show in the log as: e.g.
|
||||
// for code 1000, it will show as I-IID001000.
|
||||
typedef NS_ENUM(NSInteger, FIRInstanceIDMessageCode) {
|
||||
// DO NOT USE 2000, 2002.
|
||||
kFIRInstanceIDMessageCodeFIRApp000 = 1000, // I-IID001000
|
||||
kFIRInstanceIDMessageCodeFIRApp001 = 1001,
|
||||
kFIRInstanceIDMessageCodeFIRApp002 = 1002,
|
||||
kFIRInstanceIDMessageCodeInternal001 = 2001,
|
||||
kFIRInstanceIDMessageCodeInternal002 = 2002,
|
||||
// FIRInstanceID.m
|
||||
// DO NOT USE 4000.
|
||||
kFIRInstanceIDMessageCodeInstanceID000 = 3000,
|
||||
kFIRInstanceIDMessageCodeInstanceID001 = 3001,
|
||||
kFIRInstanceIDMessageCodeInstanceID002 = 3002,
|
||||
kFIRInstanceIDMessageCodeInstanceID003 = 3003,
|
||||
kFIRInstanceIDMessageCodeInstanceID004 = 3004,
|
||||
kFIRInstanceIDMessageCodeInstanceID005 = 3005,
|
||||
kFIRInstanceIDMessageCodeInstanceID006 = 3006,
|
||||
kFIRInstanceIDMessageCodeInstanceID007 = 3007,
|
||||
kFIRInstanceIDMessageCodeInstanceID008 = 3008,
|
||||
kFIRInstanceIDMessageCodeInstanceID009 = 3009,
|
||||
kFIRInstanceIDMessageCodeInstanceID010 = 3010,
|
||||
kFIRInstanceIDMessageCodeInstanceID011 = 3011,
|
||||
kFIRInstanceIDMessageCodeInstanceID012 = 3012,
|
||||
kFIRInstanceIDMessageCodeInstanceID013 = 3013,
|
||||
kFIRInstanceIDMessageCodeInstanceID014 = 3014,
|
||||
kFIRInstanceIDMessageCodeInstanceID015 = 3015,
|
||||
kFIRInstanceIDMessageCodeRefetchingTokenForAPNS = 3016,
|
||||
kFIRInstanceIDMessageCodeInstanceID017 = 3017,
|
||||
kFIRInstanceIDMessageCodeInstanceID018 = 3018,
|
||||
// FIRInstanceIDAuthService.m
|
||||
kFIRInstanceIDMessageCodeAuthService000 = 5000,
|
||||
kFIRInstanceIDMessageCodeAuthService001 = 5001,
|
||||
kFIRInstanceIDMessageCodeAuthService002 = 5002,
|
||||
kFIRInstanceIDMessageCodeAuthService003 = 5003,
|
||||
kFIRInstanceIDMessageCodeAuthService004 = 5004,
|
||||
kFIRInstanceIDMessageCodeAuthServiceCheckinInProgress = 5004,
|
||||
|
||||
// FIRInstanceIDBackupExcludedPlist.m
|
||||
kFIRInstanceIDMessageCodeBackupExcludedPlist000 = 6000,
|
||||
kFIRInstanceIDMessageCodeBackupExcludedPlist001 = 6001,
|
||||
kFIRInstanceIDMessageCodeBackupExcludedPlist002 = 6002,
|
||||
kFIRInstanceIDMessageCodeBackupExcludedPlistInvalidPlistEnum = 6003,
|
||||
// FIRInstanceIDCheckinService.m
|
||||
kFIRInstanceIDMessageCodeService000 = 7000,
|
||||
kFIRInstanceIDMessageCodeService001 = 7001,
|
||||
kFIRInstanceIDMessageCodeService002 = 7002,
|
||||
kFIRInstanceIDMessageCodeService003 = 7003,
|
||||
kFIRInstanceIDMessageCodeService004 = 7004,
|
||||
kFIRInstanceIDMessageCodeService005 = 7005,
|
||||
kFIRInstanceIDMessageCodeService006 = 7006,
|
||||
kFIRInstanceIDInvalidNetworkSession = 7007,
|
||||
kFIRInstanceIDInvalidSettingResponse = 7008,
|
||||
// FIRInstanceIDCheckinStore.m
|
||||
// DO NOT USE 8002, 8004 - 8008
|
||||
kFIRInstanceIDMessageCodeCheckinStore000 = 8000,
|
||||
kFIRInstanceIDMessageCodeCheckinStore001 = 8001,
|
||||
kFIRInstanceIDMessageCodeCheckinStore003 = 8003,
|
||||
kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistDeleted = 8009,
|
||||
kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistSaved = 8010,
|
||||
// FIRInstanceIDKeyPair.m
|
||||
// DO NOT USE 9001, 9003
|
||||
kFIRInstanceIDMessageCodeKeyPair000 = 9000,
|
||||
kFIRInstanceIDMessageCodeKeyPair002 = 9002,
|
||||
kFIRInstanceIDMessageCodeKeyPairMigrationError = 9004,
|
||||
kFIRInstanceIDMessageCodeKeyPairMigrationSuccess = 9005,
|
||||
kFIRInstanceIDMessageCodeKeyPairNoLegacyKeyPair = 9006,
|
||||
|
||||
// FIRInstanceIDKeyPairStore.m
|
||||
kFIRInstanceIDMessageCodeKeyPairStore000 = 10000,
|
||||
kFIRInstanceIDMessageCodeKeyPairStore001 = 10001,
|
||||
kFIRInstanceIDMessageCodeKeyPairStore002 = 10002,
|
||||
kFIRInstanceIDMessageCodeKeyPairStore003 = 10003,
|
||||
kFIRInstanceIDMessageCodeKeyPairStore004 = 10004,
|
||||
kFIRInstanceIDMessageCodeKeyPairStore005 = 10005,
|
||||
kFIRInstanceIDMessageCodeKeyPairStore006 = 10006,
|
||||
kFIRInstanceIDMessageCodeKeyPairStore007 = 10007,
|
||||
kFIRInstanceIDMessageCodeKeyPairStore008 = 10008,
|
||||
kFIRInstanceIDMessageCodeKeyPairStoreCouldNotLoadKeyPair = 10009,
|
||||
// FIRInstanceIDKeyPairUtilities.m
|
||||
kFIRInstanceIDMessageCodeKeyPairUtilities000 = 11000,
|
||||
kFIRInstanceIDMessageCodeKeyPairUtilities001 = 11001,
|
||||
kFIRInstanceIDMessageCodeKeyPairUtilitiesFirstConcatenateParamNil = 11002,
|
||||
|
||||
// DO NOT USE 12000 - 12014
|
||||
|
||||
// FIRInstanceIDStore.m
|
||||
// DO NOT USE 13004, 13005, 13007, 13008, 13010, 13011, 13013, 13014
|
||||
kFIRInstanceIDMessageCodeStore000 = 13000,
|
||||
kFIRInstanceIDMessageCodeStore001 = 13001,
|
||||
kFIRInstanceIDMessageCodeStore002 = 13002,
|
||||
kFIRInstanceIDMessageCodeStore003 = 13003,
|
||||
kFIRInstanceIDMessageCodeStore006 = 13006,
|
||||
kFIRInstanceIDMessageCodeStore009 = 13009,
|
||||
kFIRInstanceIDMessageCodeStore012 = 13012,
|
||||
// FIRInstanceIDTokenManager.m
|
||||
// DO NOT USE 14002, 14005
|
||||
kFIRInstanceIDMessageCodeTokenManager000 = 14000,
|
||||
kFIRInstanceIDMessageCodeTokenManager001 = 14001,
|
||||
kFIRInstanceIDMessageCodeTokenManager003 = 14003,
|
||||
kFIRInstanceIDMessageCodeTokenManager004 = 14004,
|
||||
kFIRInstanceIDMessageCodeTokenManagerErrorDeletingFCMTokensOnAppReset = 14006,
|
||||
kFIRInstanceIDMessageCodeTokenManagerDeletedFCMTokensOnAppReset = 14007,
|
||||
kFIRInstanceIDMessageCodeTokenManagerSavedAppVersion = 14008,
|
||||
kFIRInstanceIDMessageCodeTokenManagerErrorInvalidatingAllTokens = 14009,
|
||||
kFIRInstanceIDMessageCodeTokenManagerAPNSChanged = 14010,
|
||||
kFIRInstanceIDMessageCodeTokenManagerAPNSChangedTokenInvalidated = 14011,
|
||||
kFIRInstanceIDMessageCodeTokenManagerInvalidateStaleToken = 14012,
|
||||
// FIRInstanceIDTokenStore.m
|
||||
// DO NOT USE 15002 - 15013
|
||||
kFIRInstanceIDMessageCodeTokenStore000 = 15000,
|
||||
kFIRInstanceIDMessageCodeTokenStore001 = 15001,
|
||||
kFIRInstanceIDMessageCodeTokenStoreExceptionUnarchivingTokenInfo = 15015,
|
||||
|
||||
// DO NOT USE 16000, 18004
|
||||
|
||||
// FIRInstanceIDUtilities.m
|
||||
kFIRInstanceIDMessageCodeUtilitiesMissingBundleIdentifier = 18000,
|
||||
kFIRInstanceIDMessageCodeUtilitiesAppEnvironmentUtilNotAvailable = 18001,
|
||||
kFIRInstanceIDMessageCodeUtilitiesCannotGetHardwareModel = 18002,
|
||||
kFIRInstanceIDMessageCodeUtilitiesCannotGetSystemVersion = 18003,
|
||||
// FIRInstanceIDTokenOperation.m
|
||||
kFIRInstanceIDMessageCodeTokenOperationFailedToSignParams = 19000,
|
||||
// FIRInstanceIDTokenFetchOperation.m
|
||||
// DO NOT USE 20004, 20005
|
||||
kFIRInstanceIDMessageCodeTokenFetchOperationFetchRequest = 20000,
|
||||
kFIRInstanceIDMessageCodeTokenFetchOperationRequestError = 20001,
|
||||
kFIRInstanceIDMessageCodeTokenFetchOperationBadResponse = 20002,
|
||||
kFIRInstanceIDMessageCodeTokenFetchOperationBadTokenStructure = 20003,
|
||||
// FIRInstanceIDTokenDeleteOperation.m
|
||||
kFIRInstanceIDMessageCodeTokenDeleteOperationFetchRequest = 21000,
|
||||
kFIRInstanceIDMessageCodeTokenDeleteOperationRequestError = 21001,
|
||||
kFIRInstanceIDMessageCodeTokenDeleteOperationBadResponse = 21002,
|
||||
// FIRInstanceIDTokenInfo.m
|
||||
kFIRInstanceIDMessageCodeTokenInfoBadAPNSInfo = 22000,
|
||||
kFIRInstanceIDMessageCodeTokenInfoFirebaseAppIDChanged = 22001,
|
||||
kFIRInstanceIDMessageCodeTokenInfoLocaleChanged = 22002,
|
||||
// FIRInstanceIDKeychain.m
|
||||
kFIRInstanceIDKeychainReadItemError = 23000,
|
||||
kFIRInstanceIDKeychainAddItemError = 23001,
|
||||
kFIRInstanceIDKeychainDeleteItemError = 23002,
|
||||
kFIRInstanceIDKeychainCreateKeyPairError = 23003,
|
||||
kFIRInstanceIDKeychainUpdateItemError = 23004,
|
||||
|
||||
// FIRInstanceIDStringEncoding.m
|
||||
kFIRInstanceIDStringEncodingBufferUnderflow = 24000,
|
||||
kFIRInstanceIDStringEncodingBufferOverflow = 24001,
|
||||
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 "FIRInstanceID.h"
|
||||
|
||||
#import "FIRInstanceIDCheckinService.h"
|
||||
|
||||
/**
|
||||
* Internal API used by Firebase SDK teams by calling in reflection or internal teams.
|
||||
*/
|
||||
// TODO(chliangGoogle) Rename this to Internal.
|
||||
@interface FIRInstanceID (Private)
|
||||
|
||||
/**
|
||||
* Fetches checkin info for the app. If the app has valid cached checkin preferences
|
||||
* they are returned instead of making a network request.
|
||||
*
|
||||
* @param handler The completion handler to invoke once the request has completed.
|
||||
*/
|
||||
- (void)fetchCheckinInfoWithHandler:(nullable FIRInstanceIDDeviceCheckinCompletion)handler;
|
||||
|
||||
/**
|
||||
* Get the InstanceID for the app. If an ID was created before and cached
|
||||
* successfully we will return that ID. If no cached ID exists we create
|
||||
* a new ID, cache it and return that.
|
||||
*
|
||||
* This is a blocking call and should not really be called on the main thread.
|
||||
*
|
||||
* @param error The error object that represents the error while trying to
|
||||
* retrieve the instance id.
|
||||
*
|
||||
* @return The InstanceID for the app.
|
||||
*/
|
||||
- (nullable NSString *)appInstanceID:(NSError *_Nullable *_Nullable)error;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 "FIRInstanceID+Private.h"
|
||||
|
||||
#import "FIRInstanceIDAuthService.h"
|
||||
#import "FIRInstanceIDKeyPairStore.h"
|
||||
#import "FIRInstanceIDTokenManager.h"
|
||||
|
||||
@interface FIRInstanceID ()
|
||||
|
||||
@property(nonatomic, readonly, strong) FIRInstanceIDTokenManager *tokenManager;
|
||||
@property(nonatomic, readonly, strong) FIRInstanceIDKeyPairStore *keyPairStore;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceID (Private)
|
||||
|
||||
// This method just wraps our pre-configured auth service to make the request.
|
||||
// This method is only needed by first-party users, like Remote Config.
|
||||
- (void)fetchCheckinInfoWithHandler:(FIRInstanceIDDeviceCheckinCompletion)handler {
|
||||
[self.tokenManager.authService fetchCheckinInfoWithHandler:handler];
|
||||
}
|
||||
|
||||
- (NSString *)appInstanceID:(NSError **)error {
|
||||
return [self.keyPairStore appIdentityWithError:error];
|
||||
}
|
||||
|
||||
@end
|
||||
+1197
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Represents an APNS device token and whether its environment is for sandbox.
|
||||
* It can read from and write to an NSDictionary for simple serialization.
|
||||
*/
|
||||
@interface FIRInstanceIDAPNSInfo : NSObject <NSCoding>
|
||||
|
||||
/// The APNs device token, provided by the OS to the application delegate
|
||||
@property(nonatomic, readonly, strong) NSData *deviceToken;
|
||||
/// Represents whether or not this is deviceToken is for the sandbox
|
||||
/// environment, or production.
|
||||
@property(nonatomic, readonly, getter=isSandbox) BOOL sandbox;
|
||||
|
||||
/**
|
||||
* Initializes the receiver with an APNs device token, and boolean
|
||||
* representing whether that token is for the sandbox environment.
|
||||
*
|
||||
* @param deviceToken The APNs device token typically provided by the
|
||||
* operating system.
|
||||
* @param isSandbox YES if the APNs device token is for the sandbox
|
||||
* environment, or NO if it is for production.
|
||||
* @return An instance of FIRInstanceIDAPNSInfo.
|
||||
*/
|
||||
- (instancetype)initWithDeviceToken:(NSData *)deviceToken isSandbox:(BOOL)isSandbox;
|
||||
|
||||
/**
|
||||
* Initializes the receiver from a token options dictionary containing data
|
||||
* within the `kFIRInstanceIDTokenOptionsAPNSKey` and
|
||||
* `kFIRInstanceIDTokenOptionsAPNSIsSandboxKey` keys. The token should be an
|
||||
* NSData blob, and the sandbox value should be an NSNumber
|
||||
* representing a boolean value.
|
||||
*
|
||||
* @param dictionary A dictionary containing values under the keys
|
||||
* `kFIRInstanceIDTokenOptionsAPNSKey` and
|
||||
* `kFIRInstanceIDTokenOptionsAPNSIsSandboxKey`.
|
||||
* @return An instance of FIRInstanceIDAPNSInfo, or nil if the
|
||||
* dictionary data was invalid or missing.
|
||||
*/
|
||||
- (nullable instancetype)initWithTokenOptionsDictionary:(NSDictionary *)dictionary;
|
||||
|
||||
- (BOOL)isEqualToAPNSInfo:(FIRInstanceIDAPNSInfo *)otherInfo;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDAPNSInfo.h"
|
||||
|
||||
#import "FIRInstanceIDConstants.h"
|
||||
|
||||
/// The key used to find the APNs device token in an archive.
|
||||
NSString *const kFIRInstanceIDAPNSInfoTokenKey = @"device_token";
|
||||
/// The key used to find the sandbox value in an archive.
|
||||
NSString *const kFIRInstanceIDAPNSInfoSandboxKey = @"sandbox";
|
||||
|
||||
@implementation FIRInstanceIDAPNSInfo
|
||||
|
||||
- (instancetype)initWithDeviceToken:(NSData *)deviceToken isSandbox:(BOOL)isSandbox {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_deviceToken = [deviceToken copy];
|
||||
_sandbox = isSandbox;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithTokenOptionsDictionary:(NSDictionary *)dictionary {
|
||||
id deviceToken = dictionary[kFIRInstanceIDTokenOptionsAPNSKey];
|
||||
if (![deviceToken isKindOfClass:[NSData class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
id isSandbox = dictionary[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey];
|
||||
if (![isSandbox isKindOfClass:[NSNumber class]]) {
|
||||
return nil;
|
||||
}
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_deviceToken = (NSData *)deviceToken;
|
||||
_sandbox = ((NSNumber *)isSandbox).boolValue;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - NSCoding
|
||||
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
id deviceToken = [aDecoder decodeObjectForKey:kFIRInstanceIDAPNSInfoTokenKey];
|
||||
if (![deviceToken isKindOfClass:[NSData class]]) {
|
||||
return nil;
|
||||
}
|
||||
BOOL isSandbox = [aDecoder decodeBoolForKey:kFIRInstanceIDAPNSInfoSandboxKey];
|
||||
return [self initWithDeviceToken:(NSData *)deviceToken isSandbox:isSandbox];
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:self.deviceToken forKey:kFIRInstanceIDAPNSInfoTokenKey];
|
||||
[aCoder encodeBool:self.sandbox forKey:kFIRInstanceIDAPNSInfoSandboxKey];
|
||||
}
|
||||
|
||||
- (BOOL)isEqualToAPNSInfo:(FIRInstanceIDAPNSInfo *)otherInfo {
|
||||
if ([super isEqual:otherInfo]) {
|
||||
return YES;
|
||||
}
|
||||
return ([self.deviceToken isEqualToData:otherInfo.deviceToken] &&
|
||||
self.isSandbox == otherInfo.isSandbox);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
extern NSString *__nonnull const kFIRInstanceIDKeychainWildcardIdentifier;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Wrapper around storing FCM auth data in iOS keychain.
|
||||
*/
|
||||
@interface FIRInstanceIDAuthKeychain : NSObject
|
||||
|
||||
/**
|
||||
* Designated Initializer. Init a generic `SecClassGenericPassword` keychain with `identifier`
|
||||
* as the `kSecAttrGeneric`.
|
||||
*
|
||||
* @param identifier The generic attribute to be used by the keychain.
|
||||
*
|
||||
* @return A Keychain object with `kSecAttrGeneric` attribute set to identifier.
|
||||
*/
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier;
|
||||
|
||||
/**
|
||||
* Get keychain items matching the given service and account. The service and/or account
|
||||
* can be a wildcard (`kFIRInstanceIDKeychainWildcardIdentifier`), which case the query
|
||||
* will include all items matching any services and/or accounts.
|
||||
*
|
||||
* @param service The kSecAttrService used to save the password. Can be wildcard.
|
||||
* @param account The kSecAttrAccount used to save the password. Can be wildcard.
|
||||
*
|
||||
* @return An array of |NSData|s matching the provided inputs.
|
||||
*/
|
||||
- (NSArray<NSData *> *)itemsMatchingService:(NSString *)service account:(NSString *)account;
|
||||
|
||||
/**
|
||||
* Get keychain item for a given service and account.
|
||||
*
|
||||
* @param service The kSecAttrService used to save the password.
|
||||
* @param account The kSecAttrAccount used to save the password.
|
||||
*
|
||||
* @return A cached keychain item for a given account and service, or nil if it was not
|
||||
* found or could not be retrieved.
|
||||
*/
|
||||
- (NSData *)dataForService:(NSString *)service account:(NSString *)account;
|
||||
|
||||
/**
|
||||
* Remove the cached items from the keychain matching the service, account and access group.
|
||||
* In case the items do not exist, YES is returned but with a valid error object with code
|
||||
* `errSecItemNotFound`.
|
||||
*
|
||||
* @param service The kSecAttrService used to save the password.
|
||||
* @param account The kSecAttrAccount used to save the password.
|
||||
* @param handler The callback handler which is invoked when the remove operation is complete, with
|
||||
* an error if there is any.
|
||||
*/
|
||||
- (void)removeItemsMatchingService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
handler:(nullable void (^)(NSError *error))handler;
|
||||
|
||||
/**
|
||||
* Set the data for a given service and account with a specific accessibility. If
|
||||
* accessibility is NULL we use `kSecAttrAccessibleAlwaysThisDeviceOnly` which
|
||||
* prevents backup and restore to iCloud, and works for app extension that can
|
||||
* execute right after a device is restarted (and not unlocked).
|
||||
*
|
||||
* @param data The data to save.
|
||||
* @param service The `kSecAttrService` used to save the password.
|
||||
* @param accessibility The `kSecAttrAccessibility` used to save the password. If NULL
|
||||
* set this to `kSecAttrAccessibleAlwaysThisDeviceOnly`.
|
||||
* @param account The `kSecAttrAccount` used to save the password.
|
||||
* @param handler The callback handler which is invoked when the add operation is complete,
|
||||
* with an error if there is any.
|
||||
*
|
||||
*/
|
||||
- (void)setData:(NSData *)data
|
||||
forService:(NSString *)service
|
||||
accessibility:(nullable CFTypeRef)accessibility
|
||||
account:(NSString *)account
|
||||
handler:(nullable void (^)(NSError *))handler;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDAuthKeyChain.h"
|
||||
#import "FIRInstanceIDKeychain.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
|
||||
/**
|
||||
* The error type representing why we couldn't read data from the keychain.
|
||||
*/
|
||||
typedef NS_ENUM(int, FIRInstanceIDKeychainErrorType) {
|
||||
kFIRInstanceIDKeychainErrorBadArguments = -1301,
|
||||
};
|
||||
|
||||
NSString *const kFIRInstanceIDKeychainWildcardIdentifier = @"*";
|
||||
|
||||
@interface FIRInstanceIDAuthKeychain ()
|
||||
|
||||
@property(nonatomic, copy) NSString *generic;
|
||||
// cachedKeychainData is keyed by service and account, the value is an array of NSData.
|
||||
// It is used to cache the tokens per service, per account, as well as checkin data per service,
|
||||
// per account inside the keychain.
|
||||
@property(nonatomic)
|
||||
NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, NSArray<NSData *> *> *>
|
||||
*cachedKeychainData;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDAuthKeychain
|
||||
|
||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_generic = [identifier copy];
|
||||
_cachedKeychainData = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (NSMutableDictionary *)keychainQueryForService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
generic:(NSString *)generic {
|
||||
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword};
|
||||
|
||||
NSMutableDictionary *finalQuery = [NSMutableDictionary dictionaryWithDictionary:query];
|
||||
if ([generic length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:generic]) {
|
||||
finalQuery[(__bridge NSString *)kSecAttrGeneric] = generic;
|
||||
}
|
||||
if ([account length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:account]) {
|
||||
finalQuery[(__bridge NSString *)kSecAttrAccount] = account;
|
||||
}
|
||||
if ([service length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:service]) {
|
||||
finalQuery[(__bridge NSString *)kSecAttrService] = service;
|
||||
}
|
||||
return finalQuery;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
|
||||
return [[self class] keychainQueryForService:service account:account generic:self.generic];
|
||||
}
|
||||
|
||||
- (NSArray<NSData *> *)itemsMatchingService:(NSString *)service account:(NSString *)account {
|
||||
// If query wildcard service, it asks for all the results, which always query from keychain.
|
||||
if (![service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] &&
|
||||
![account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] &&
|
||||
_cachedKeychainData[service][account]) {
|
||||
// As long as service, account array exist, even it's empty, it means we've queried it before,
|
||||
// returns the cache value.
|
||||
return _cachedKeychainData[service][account];
|
||||
}
|
||||
|
||||
NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
|
||||
NSMutableArray<NSData *> *results;
|
||||
keychainQuery[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
||||
keychainQuery[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
|
||||
keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
|
||||
// FIRInstanceIDKeychain should only take a query and return a result, will handle the query here.
|
||||
NSArray *passwordInfos =
|
||||
CFBridgingRelease([[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery]);
|
||||
#elif TARGET_OS_OSX
|
||||
keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
|
||||
NSData *passwordInfos =
|
||||
CFBridgingRelease([[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery]);
|
||||
#endif
|
||||
|
||||
if (!passwordInfos) {
|
||||
// Nothing was found, simply return from this sync block.
|
||||
// Make sure to label the cache entry empty, signaling that we've queried this entry.
|
||||
if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
|
||||
[account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
|
||||
// Do not update cache if it's wildcard query.
|
||||
return @[];
|
||||
} else if (_cachedKeychainData[service]) {
|
||||
[_cachedKeychainData[service] setObject:@[] forKey:account];
|
||||
} else {
|
||||
[_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
|
||||
}
|
||||
return @[];
|
||||
}
|
||||
results = [[NSMutableArray alloc] init];
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
||||
NSInteger numPasswords = passwordInfos.count;
|
||||
for (NSUInteger i = 0; i < numPasswords; i++) {
|
||||
NSDictionary *passwordInfo = [passwordInfos objectAtIndex:i];
|
||||
if (passwordInfo[(__bridge id)kSecValueData]) {
|
||||
[results addObject:passwordInfo[(__bridge id)kSecValueData]];
|
||||
}
|
||||
}
|
||||
#elif TARGET_OS_OSX
|
||||
[results addObject:passwordInfos];
|
||||
#endif
|
||||
// We query the keychain because it didn't exist in cache, now query is done, update the result in
|
||||
// the cache.
|
||||
if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
|
||||
[account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
|
||||
// Do not update cache if it's wildcard query.
|
||||
return [results copy];
|
||||
} else if (_cachedKeychainData[service]) {
|
||||
[_cachedKeychainData[service] setObject:[results copy] forKey:account];
|
||||
} else {
|
||||
NSMutableDictionary *entry = [@{account : [results copy]} mutableCopy];
|
||||
[_cachedKeychainData setObject:entry forKey:service];
|
||||
}
|
||||
return [results copy];
|
||||
}
|
||||
|
||||
- (NSData *)dataForService:(NSString *)service account:(NSString *)account {
|
||||
NSArray<NSData *> *items = [self itemsMatchingService:service account:account];
|
||||
// If items is nil or empty, nil will be returned.
|
||||
return items.firstObject;
|
||||
}
|
||||
|
||||
- (void)removeItemsMatchingService:(NSString *)service
|
||||
account:(NSString *)account
|
||||
handler:(void (^)(NSError *error))handler {
|
||||
if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
|
||||
// Delete all keychain items.
|
||||
_cachedKeychainData = [[NSMutableDictionary alloc] init];
|
||||
} else if ([account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
|
||||
// Delete all entries under service,
|
||||
if (_cachedKeychainData[service]) {
|
||||
_cachedKeychainData[service] = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
} else if (_cachedKeychainData[service]) {
|
||||
// We should keep the service/account entry instead of nil so we know
|
||||
// it's "empty entry" instead of "not query from keychain yet".
|
||||
[_cachedKeychainData[service] setObject:@[] forKey:account];
|
||||
} else {
|
||||
[_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
|
||||
}
|
||||
NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
|
||||
[[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:keychainQuery handler:handler];
|
||||
}
|
||||
|
||||
- (void)setData:(NSData *)data
|
||||
forService:(NSString *)service
|
||||
accessibility:(CFTypeRef)accessibility
|
||||
account:(NSString *)account
|
||||
handler:(void (^)(NSError *))handler {
|
||||
if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
|
||||
[account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
|
||||
if (handler) {
|
||||
handler([NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain
|
||||
code:kFIRInstanceIDKeychainErrorBadArguments
|
||||
userInfo:nil]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
[self removeItemsMatchingService:service
|
||||
account:account
|
||||
handler:^(NSError *error) {
|
||||
if (error) {
|
||||
if (handler) {
|
||||
handler(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.length > 0) {
|
||||
NSMutableDictionary *keychainQuery =
|
||||
[self keychainQueryForService:service account:account];
|
||||
keychainQuery[(__bridge id)kSecValueData] = data;
|
||||
|
||||
if (accessibility != NULL) {
|
||||
keychainQuery[(__bridge id)kSecAttrAccessible] =
|
||||
(__bridge id)accessibility;
|
||||
} else {
|
||||
// Defaults to No backup
|
||||
keychainQuery[(__bridge id)kSecAttrAccessible] =
|
||||
(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly;
|
||||
}
|
||||
[[FIRInstanceIDKeychain sharedInstance]
|
||||
addItemWithQuery:keychainQuery
|
||||
handler:handler];
|
||||
}
|
||||
}];
|
||||
// Set the cache value. This must happen after removeItemsMatchingService:account:handler was
|
||||
// called, so the cache value was reset before setting a new value.
|
||||
if (_cachedKeychainData[service]) {
|
||||
if (_cachedKeychainData[service][account]) {
|
||||
_cachedKeychainData[service][account] = @[ data ];
|
||||
} else {
|
||||
[_cachedKeychainData[service] setObject:@[ data ] forKey:account];
|
||||
}
|
||||
} else {
|
||||
[_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
#import "FIRInstanceIDCheckinService.h"
|
||||
|
||||
@class FIRInstanceIDCheckinPreferences;
|
||||
@class FIRInstanceIDStore;
|
||||
|
||||
/**
|
||||
* FIRInstanceIDAuthService is responsible for retrieving, caching, and supplying checkin info
|
||||
* for the rest of Instance ID. A checkin can be scheduled, meaning that it will keep retrying the
|
||||
* checkin request until it is successful. A checkin can also be requested directly, with a
|
||||
* completion handler.
|
||||
*/
|
||||
@interface FIRInstanceIDAuthService : NSObject
|
||||
|
||||
/**
|
||||
* Used only for testing. In addition to taking a store (for locally caching the checkin info), it
|
||||
* also takes a checkinService.
|
||||
*/
|
||||
- (instancetype)initWithCheckinService:(FIRInstanceIDCheckinService *)checkinService
|
||||
store:(FIRInstanceIDStore *)store;
|
||||
|
||||
/**
|
||||
* Initializes the auth service given a store (which provides the local caching of checkin info).
|
||||
* This initializer will create its own instance of FIRInstanceIDCheckinService.
|
||||
*/
|
||||
- (instancetype)initWithStore:(FIRInstanceIDStore *)store;
|
||||
|
||||
#pragma mark - Checkin Service
|
||||
|
||||
/**
|
||||
* Checks if the current deviceID and secret are valid or not.
|
||||
*
|
||||
* @return YES if the checkin credentials are valid else NO.
|
||||
*/
|
||||
- (BOOL)hasValidCheckinInfo;
|
||||
|
||||
/**
|
||||
* Fetch checkin info from the server. This would usually refresh the existing
|
||||
* checkin credentials for the current app.
|
||||
*
|
||||
* @param handler The completion handler to invoke once the checkin info has been
|
||||
* refreshed.
|
||||
*/
|
||||
- (void)fetchCheckinInfoWithHandler:(FIRInstanceIDDeviceCheckinCompletion)handler;
|
||||
|
||||
/**
|
||||
* Schedule checkin. Will hit the network only if the currently loaded checkin
|
||||
* preferences are stale.
|
||||
*
|
||||
* @param immediately YES if we want it to be scheduled immediately else NO.
|
||||
*/
|
||||
- (void)scheduleCheckin:(BOOL)immediately;
|
||||
|
||||
/**
|
||||
* Returns the checkin preferences currently loaded in memory. The Checkin preferences
|
||||
* can be either valid or invalid.
|
||||
*
|
||||
* @return The checkin preferences loaded in memory.
|
||||
*/
|
||||
- (FIRInstanceIDCheckinPreferences *)checkinPreferences;
|
||||
|
||||
/**
|
||||
* Cancels any ongoing checkin fetch, if any.
|
||||
*/
|
||||
- (void)stopCheckinRequest;
|
||||
|
||||
/**
|
||||
* Resets the checkin information.
|
||||
*
|
||||
* @param handler The callback handler which is invoked when checkin reset is complete,
|
||||
* with an error if there is any.
|
||||
*/
|
||||
- (void)resetCheckinWithHandler:(void (^)(NSError *error))handler;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDAuthService.h"
|
||||
|
||||
#import "FIRInstanceIDCheckinPreferences+Internal.h"
|
||||
#import "FIRInstanceIDCheckinPreferences.h"
|
||||
#import "FIRInstanceIDCheckinPreferences_Private.h"
|
||||
#import "FIRInstanceIDConstants.h"
|
||||
#import "FIRInstanceIDDefines.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDStore.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
// Max time interval between checkin retry in seconds.
|
||||
static const int64_t kMaxCheckinRetryIntervalInSeconds = 1 << 5;
|
||||
|
||||
@interface FIRInstanceIDAuthService ()
|
||||
|
||||
// Used to retrieve and cache the checkin info to disk and Keychain.
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDStore *store;
|
||||
// Used to perform single checkin fetches.
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinService *checkinService;
|
||||
// The current checkin info. It will be compared to what is retrieved to determine whether it is
|
||||
// different than what is in the cache.
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinPreferences *checkinPreferences;
|
||||
|
||||
// This array will track multiple handlers waiting for checkin to be performed. When a checkin
|
||||
// request completes, all the handlers will be notified.
|
||||
// Changes to the checkinHandlers array should happen in a thread-safe manner.
|
||||
@property(nonatomic, readonly, strong)
|
||||
NSMutableArray<FIRInstanceIDDeviceCheckinCompletion> *checkinHandlers;
|
||||
|
||||
// This is set to true if there is a checkin request in-flight.
|
||||
@property(atomic, readwrite, assign) BOOL isCheckinInProgress;
|
||||
// This timer is used a perform checkin retries. It is cancellable.
|
||||
@property(atomic, readwrite, strong) NSTimer *scheduledCheckinTimer;
|
||||
// The number of times checkin has been retried during a scheduled checkin.
|
||||
@property(atomic, readwrite, assign) int checkinRetryCount;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDAuthService
|
||||
|
||||
- (instancetype)initWithCheckinService:(FIRInstanceIDCheckinService *)checkinService
|
||||
store:(FIRInstanceIDStore *)store {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_store = store;
|
||||
_checkinPreferences = [_store cachedCheckinPreferences];
|
||||
_checkinService = checkinService;
|
||||
_checkinHandlers = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_scheduledCheckinTimer invalidate];
|
||||
}
|
||||
|
||||
- (instancetype)initWithStore:(FIRInstanceIDStore *)store {
|
||||
FIRInstanceIDCheckinService *checkinService = [[FIRInstanceIDCheckinService alloc] init];
|
||||
return [self initWithCheckinService:checkinService store:store];
|
||||
}
|
||||
|
||||
#pragma mark - Schedule Checkin
|
||||
|
||||
- (void)scheduleCheckin:(BOOL)immediately {
|
||||
// Checkin is still valid, so a remote checkin is not required.
|
||||
if ([self.checkinPreferences hasValidCheckinInfo]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Checkin is already scheduled, so this (non-immediate) request can be ignored.
|
||||
if (!immediately && [self.scheduledCheckinTimer isValid]) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService000,
|
||||
@"Checkin sync already scheduled. Will not schedule.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (immediately) {
|
||||
[self performScheduledCheckin];
|
||||
} else {
|
||||
int64_t checkinRetryDuration = [self calculateNextCheckinRetryIntervalInSeconds];
|
||||
[self startCheckinTimerWithDuration:(NSTimeInterval)checkinRetryDuration];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startCheckinTimerWithDuration:(NSTimeInterval)timerDuration {
|
||||
self.scheduledCheckinTimer =
|
||||
[NSTimer scheduledTimerWithTimeInterval:timerDuration
|
||||
target:self
|
||||
selector:@selector(onScheduledCheckinTimerFired:)
|
||||
userInfo:nil
|
||||
repeats:NO];
|
||||
// Add some tolerance to the timer, to allow iOS to be more flexible with this timer
|
||||
self.scheduledCheckinTimer.tolerance = 0.5;
|
||||
}
|
||||
|
||||
- (void)clearScheduledCheckinTimer {
|
||||
[self.scheduledCheckinTimer invalidate];
|
||||
self.scheduledCheckinTimer = nil;
|
||||
}
|
||||
|
||||
- (void)onScheduledCheckinTimerFired:(NSTimer *)timer {
|
||||
[self performScheduledCheckin];
|
||||
}
|
||||
|
||||
- (void)performScheduledCheckin {
|
||||
// No checkin scheduled as of now.
|
||||
[self clearScheduledCheckinTimer];
|
||||
|
||||
// Checkin is still valid, so a remote checkin is not required.
|
||||
if ([self.checkinPreferences hasValidCheckinInfo]) {
|
||||
return;
|
||||
}
|
||||
|
||||
FIRInstanceID_WEAKIFY(self);
|
||||
[self
|
||||
fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *preferences, NSError *error) {
|
||||
FIRInstanceID_STRONGIFY(self);
|
||||
self.checkinRetryCount++;
|
||||
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService001, @"Checkin error %@.",
|
||||
error);
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Schedule another checkin
|
||||
[self scheduleCheckin:NO];
|
||||
});
|
||||
|
||||
} else {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService002, @"Checkin success.");
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (int64_t)calculateNextCheckinRetryIntervalInSeconds {
|
||||
// persistent failures can lead to overflow prevent that.
|
||||
if (self.checkinRetryCount >= 10) {
|
||||
return kMaxCheckinRetryIntervalInSeconds;
|
||||
}
|
||||
return MIN(1 << self.checkinRetryCount, kMaxCheckinRetryIntervalInSeconds);
|
||||
}
|
||||
|
||||
#pragma mark - Checkin Service
|
||||
|
||||
- (BOOL)hasValidCheckinInfo {
|
||||
return [self.checkinPreferences hasValidCheckinInfo];
|
||||
}
|
||||
|
||||
- (void)fetchCheckinInfoWithHandler:(nonnull FIRInstanceIDDeviceCheckinCompletion)handler {
|
||||
// Perform any changes to self.checkinHandlers and _isCheckinInProgress in a thread-safe way.
|
||||
@synchronized(self) {
|
||||
[self.checkinHandlers addObject:handler];
|
||||
|
||||
if (_isCheckinInProgress) {
|
||||
// Nothing more to do until our checkin request is done
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthServiceCheckinInProgress,
|
||||
@"Checkin is in progress\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Checkin is still valid, so a remote checkin is not required.
|
||||
if ([self.checkinPreferences hasValidCheckinInfo]) {
|
||||
[self notifyCheckinHandlersWithCheckin:self.checkinPreferences error:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
_isCheckinInProgress = YES;
|
||||
}
|
||||
[self.checkinService
|
||||
checkinWithExistingCheckin:self.checkinPreferences
|
||||
completion:^(FIRInstanceIDCheckinPreferences *checkinPreferences,
|
||||
NSError *error) {
|
||||
@synchronized(self) {
|
||||
self->_isCheckinInProgress = NO;
|
||||
}
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService003,
|
||||
@"Failed to checkin device %@", error);
|
||||
[self notifyCheckinHandlersWithCheckin:nil error:error];
|
||||
return;
|
||||
}
|
||||
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeAuthService004,
|
||||
@"Successfully got checkin credentials");
|
||||
BOOL hasSameCachedPreferences =
|
||||
[self cachedCheckinMatchesCheckin:checkinPreferences];
|
||||
checkinPreferences.hasPreCachedAuthCredentials = hasSameCachedPreferences;
|
||||
|
||||
// Update to the most recent checkin preferences
|
||||
self.checkinPreferences = checkinPreferences;
|
||||
|
||||
// Save the checkin info to disk
|
||||
// Keychain might not be accessible, so confirm that checkin preferences can
|
||||
// be saved
|
||||
[self.store
|
||||
saveCheckinPreferences:checkinPreferences
|
||||
handler:^(NSError *checkinSaveError) {
|
||||
if (checkinSaveError && !hasSameCachedPreferences) {
|
||||
// The checkin info was new, but it couldn't be
|
||||
// written to the Keychain. Delete any stuff that was
|
||||
// cached in memory. This doesn't delete any
|
||||
// previously persisted preferences.
|
||||
FIRInstanceIDLoggerError(
|
||||
kFIRInstanceIDMessageCodeService004,
|
||||
@"Unable to save checkin info, resetting "
|
||||
@"checkin preferences "
|
||||
"in memory.");
|
||||
[checkinPreferences reset];
|
||||
[self
|
||||
notifyCheckinHandlersWithCheckin:nil
|
||||
error:
|
||||
checkinSaveError];
|
||||
} else {
|
||||
// The checkin is either new, or it was the same (and
|
||||
// it couldn't be saved). Either way, report that the
|
||||
// checkin preferences were received successfully.
|
||||
[self notifyCheckinHandlersWithCheckin:
|
||||
checkinPreferences
|
||||
error:nil];
|
||||
if (!hasSameCachedPreferences) {
|
||||
// Checkin is new.
|
||||
// Notify any listeners that might be waiting for
|
||||
// checkin to be fetched, such as Firebase
|
||||
// Messaging (for its MCS connection).
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:
|
||||
kFIRInstanceIDCheckinFetchedNotification
|
||||
object:nil];
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (FIRInstanceIDCheckinPreferences *)checkinPreferences {
|
||||
return _checkinPreferences;
|
||||
}
|
||||
|
||||
- (void)stopCheckinRequest {
|
||||
[self.checkinService stopFetching];
|
||||
}
|
||||
|
||||
- (void)resetCheckinWithHandler:(void (^)(NSError *error))handler {
|
||||
[self.store removeCheckinPreferencesWithHandler:^(NSError *error) {
|
||||
if (!error) {
|
||||
self.checkinPreferences = nil;
|
||||
}
|
||||
if (handler) {
|
||||
handler(error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
/**
|
||||
* Goes through the current list of checkin handlers and fires them with the same checkin and/or
|
||||
* error info. The checkin handlers will get cleared after.
|
||||
*/
|
||||
- (void)notifyCheckinHandlersWithCheckin:(nullable FIRInstanceIDCheckinPreferences *)checkin
|
||||
error:(nullable NSError *)error {
|
||||
@synchronized(self) {
|
||||
for (FIRInstanceIDDeviceCheckinCompletion handler in self.checkinHandlers) {
|
||||
handler(checkin, error);
|
||||
}
|
||||
[self.checkinHandlers removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a |checkin|, it will compare it to the current checkinPreferences to see if the
|
||||
* deviceID and secretToken are the same.
|
||||
*/
|
||||
- (BOOL)cachedCheckinMatchesCheckin:(FIRInstanceIDCheckinPreferences *)checkin {
|
||||
if (self.checkinPreferences && checkin) {
|
||||
return ([self.checkinPreferences.deviceID isEqualToString:checkin.deviceID] &&
|
||||
[self.checkinPreferences.secretToken isEqualToString:checkin.secretToken]);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
@end
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@interface FIRInstanceIDBackupExcludedPlist : NSObject
|
||||
|
||||
/**
|
||||
* Caches the plist contents in memory so we don't hit the disk each time we want
|
||||
* to query something in the plist. This is loaded lazily i.e. if you write to the
|
||||
* plist the contents you want to write will be stored here if the write was
|
||||
* successful. The other case where it is loaded is if you read the plist contents
|
||||
* by calling `contentAsDictionary`.
|
||||
*
|
||||
* In case you write to the plist and then try to read the file using
|
||||
* `contentAsDictionary` we would just return the cachedPlistContents since it would
|
||||
* represent the disk contents.
|
||||
*/
|
||||
@property(nonatomic, readonly, strong) NSDictionary *cachedPlistContents;
|
||||
|
||||
/**
|
||||
* Init a backup excluded plist file.
|
||||
*
|
||||
* @param fileName The filename for the plist file.
|
||||
* @param subDirectory The subdirectory in Application Support to save the plist.
|
||||
*
|
||||
* @return Helper which allows to read write data to a backup excluded plist.
|
||||
*/
|
||||
- (instancetype)initWithFileName:(NSString *)fileName subDirectory:(NSString *)subDirectory;
|
||||
|
||||
/**
|
||||
* Write dictionary data to the backup excluded plist file. If the file does not exist
|
||||
* it would be created before writing to it.
|
||||
*
|
||||
* @param dict The data to be written to the plist.
|
||||
* @param error The error object if any while writing the data.
|
||||
*
|
||||
* @return YES if the write was successful else NO.
|
||||
*/
|
||||
- (BOOL)writeDictionary:(NSDictionary *)dict error:(NSError **)error;
|
||||
|
||||
/**
|
||||
* Delete the backup excluded plist created with the above filename.
|
||||
*
|
||||
* @param error The error object if any while deleting the file.
|
||||
*
|
||||
* @return YES If the delete was successful else NO.
|
||||
*/
|
||||
- (BOOL)deleteFile:(NSError **)error;
|
||||
|
||||
/**
|
||||
* The contents of the plist file. We also store the contents of the file in-memory.
|
||||
* If the in-memory contents are valid we return the in-memory contents else we read
|
||||
* the file from disk.
|
||||
*
|
||||
* @return A dictionary object that contains the contents of the plist file if the file
|
||||
* exists else nil.
|
||||
*/
|
||||
- (NSDictionary *)contentAsDictionary;
|
||||
|
||||
/**
|
||||
* Check if the plist exists on the disk or not.
|
||||
*
|
||||
* @return YES if the file exists on the disk else NO.
|
||||
*/
|
||||
- (BOOL)doesFileExist;
|
||||
|
||||
@end
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDBackupExcludedPlist.h"
|
||||
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
FIRInstanceIDPlistDirectoryUnknown,
|
||||
FIRInstanceIDPlistDirectoryDocuments,
|
||||
FIRInstanceIDPlistDirectoryApplicationSupport,
|
||||
} FIRInstanceIDPlistDirectory;
|
||||
|
||||
@interface FIRInstanceIDBackupExcludedPlist ()
|
||||
|
||||
@property(nonatomic, readwrite, copy) NSString *fileName;
|
||||
@property(nonatomic, readwrite, copy) NSString *subDirectoryName;
|
||||
@property(nonatomic, readwrite, assign) BOOL fileInStandardDirectory;
|
||||
|
||||
@property(nonatomic, readwrite, strong) NSDictionary *cachedPlistContents;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDBackupExcludedPlist
|
||||
|
||||
- (instancetype)initWithFileName:(NSString *)fileName subDirectory:(NSString *)subDirectory {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_fileName = [fileName copy];
|
||||
_subDirectoryName = [subDirectory copy];
|
||||
#if TARGET_OS_IOS
|
||||
_fileInStandardDirectory = [self moveToApplicationSupportSubDirectory:subDirectory];
|
||||
#else
|
||||
// For tvOS and macOS, we never store the content in document folder, so
|
||||
// the migration is unnecessary.
|
||||
_fileInStandardDirectory = YES;
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)writeDictionary:(NSDictionary *)dict error:(NSError **)error {
|
||||
NSString *path = [self plistPathInDirectory:[self plistDirectory]];
|
||||
if (![dict writeToFile:path atomically:YES]) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeBackupExcludedPlist000,
|
||||
@"Failed to write to %@.plist", self.fileName);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Successfully wrote contents -- change the in-memory contents
|
||||
self.cachedPlistContents = [dict copy];
|
||||
|
||||
NSURL *URL = [NSURL fileURLWithPath:path];
|
||||
if (error) {
|
||||
*error = nil;
|
||||
}
|
||||
|
||||
NSDictionary *preferences = [URL resourceValuesForKeys:@[ NSURLIsExcludedFromBackupKey ]
|
||||
error:error];
|
||||
if ([preferences[NSURLIsExcludedFromBackupKey] boolValue]) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
BOOL success = [URL setResourceValue:@(YES) forKey:NSURLIsExcludedFromBackupKey error:error];
|
||||
if (!success) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeBackupExcludedPlist001,
|
||||
@"Error excluding %@ from backup, %@", [URL lastPathComponent],
|
||||
error ? *error : @"");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
- (BOOL)deleteFile:(NSError **)error {
|
||||
BOOL success = YES;
|
||||
NSString *path = [self plistPathInDirectory:[self plistDirectory]];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
success = [[NSFileManager defaultManager] removeItemAtPath:path error:error];
|
||||
}
|
||||
// remove the in-memory contents
|
||||
self.cachedPlistContents = nil;
|
||||
return success;
|
||||
}
|
||||
|
||||
- (NSDictionary *)contentAsDictionary {
|
||||
if (!self.cachedPlistContents) {
|
||||
NSString *path = [self plistPathInDirectory:[self plistDirectory]];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
self.cachedPlistContents = [[NSDictionary alloc] initWithContentsOfFile:path];
|
||||
}
|
||||
}
|
||||
return self.cachedPlistContents;
|
||||
}
|
||||
|
||||
- (BOOL)moveToApplicationSupportSubDirectory:(NSString *)subDirectoryName {
|
||||
NSArray *directoryPaths =
|
||||
NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES);
|
||||
// This only going to happen inside iOS so it is an applicationSupportDirectory.
|
||||
NSString *applicationSupportDirPath = directoryPaths.lastObject;
|
||||
NSArray *components = @[ applicationSupportDirPath, subDirectoryName ];
|
||||
NSString *subDirectoryPath = [NSString pathWithComponents:components];
|
||||
BOOL hasSubDirectory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath
|
||||
isDirectory:&hasSubDirectory]) {
|
||||
// Cannot move to non-existent directory
|
||||
return NO;
|
||||
}
|
||||
|
||||
if ([self doesFileExistInDirectory:FIRInstanceIDPlistDirectoryDocuments]) {
|
||||
NSString *oldPlistPath = [self plistPathInDirectory:FIRInstanceIDPlistDirectoryDocuments];
|
||||
NSString *newPlistPath =
|
||||
[self plistPathInDirectory:FIRInstanceIDPlistDirectoryApplicationSupport];
|
||||
if ([self doesFileExistInDirectory:FIRInstanceIDPlistDirectoryApplicationSupport]) {
|
||||
// File exists in both Documents and ApplicationSupport
|
||||
return NO;
|
||||
}
|
||||
NSError *moveError;
|
||||
if (![[NSFileManager defaultManager] moveItemAtPath:oldPlistPath
|
||||
toPath:newPlistPath
|
||||
error:&moveError]) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeBackupExcludedPlist002,
|
||||
@"Failed to move file %@ from %@ to %@. Error: %@", self.fileName,
|
||||
oldPlistPath, newPlistPath, moveError);
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
// We moved the file if it existed, otherwise we didn't need to do anything
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)doesFileExist {
|
||||
return [self doesFileExistInDirectory:[self plistDirectory]];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (FIRInstanceIDPlistDirectory)plistDirectory {
|
||||
if (_fileInStandardDirectory) {
|
||||
return FIRInstanceIDPlistDirectoryApplicationSupport;
|
||||
} else {
|
||||
return FIRInstanceIDPlistDirectoryDocuments;
|
||||
};
|
||||
}
|
||||
|
||||
- (NSString *)plistPathInDirectory:(FIRInstanceIDPlistDirectory)directory {
|
||||
return [self pathWithName:self.fileName inDirectory:directory];
|
||||
}
|
||||
|
||||
- (NSString *)pathWithName:(NSString *)plistName
|
||||
inDirectory:(FIRInstanceIDPlistDirectory)directory {
|
||||
NSArray *directoryPaths;
|
||||
NSArray *components = @[];
|
||||
NSString *plistNameWithExtension = [NSString stringWithFormat:@"%@.plist", plistName];
|
||||
switch (directory) {
|
||||
case FIRInstanceIDPlistDirectoryDocuments:
|
||||
directoryPaths =
|
||||
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
components = @[ directoryPaths.lastObject, plistNameWithExtension ];
|
||||
break;
|
||||
|
||||
case FIRInstanceIDPlistDirectoryApplicationSupport:
|
||||
directoryPaths =
|
||||
NSSearchPathForDirectoriesInDomains([self supportedDirectory], NSUserDomainMask, YES);
|
||||
components = @[ directoryPaths.lastObject, _subDirectoryName, plistNameWithExtension ];
|
||||
break;
|
||||
|
||||
default:
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeBackupExcludedPlistInvalidPlistEnum,
|
||||
@"Invalid plist directory type: %lu", (unsigned long)directory);
|
||||
NSAssert(NO, @"Invalid plist directory type: %lu", (unsigned long)directory);
|
||||
break;
|
||||
}
|
||||
|
||||
return [NSString pathWithComponents:components];
|
||||
}
|
||||
|
||||
- (BOOL)doesFileExistInDirectory:(FIRInstanceIDPlistDirectory)directory {
|
||||
NSString *path = [self plistPathInDirectory:directory];
|
||||
return [[NSFileManager defaultManager] fileExistsAtPath:path];
|
||||
}
|
||||
|
||||
- (NSSearchPathDirectory)supportedDirectory {
|
||||
#if TARGET_OS_TV
|
||||
return NSCachesDirectory;
|
||||
#else
|
||||
return NSApplicationSupportDirectory;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDCheckinPreferences.h"
|
||||
|
||||
@interface FIRInstanceIDCheckinPreferences (Internal)
|
||||
|
||||
/**
|
||||
* Parse the checkin auth credentials saved in the Keychain to initialize checkin
|
||||
* preferences.
|
||||
*
|
||||
* @param keychainContent The checkin auth credentials saved in the Keychain.
|
||||
*
|
||||
* @return A valid checkin preferences object if the checkin auth credentials in the
|
||||
* keychain can be parsed successfully else nil.
|
||||
*/
|
||||
+ (FIRInstanceIDCheckinPreferences *)preferencesFromKeychainContents:(NSString *)keychainContent;
|
||||
|
||||
/**
|
||||
* Default initializer for InstanceID checkin preferences.
|
||||
*
|
||||
* @param deviceID The deviceID for the app.
|
||||
* @param secretToken The secret token the app uses to authenticate with the server.
|
||||
*
|
||||
* @return A checkin preferences object with given deviceID and secretToken.
|
||||
*/
|
||||
- (instancetype)initWithDeviceID:(NSString *)deviceID secretToken:(NSString *)secretToken;
|
||||
|
||||
/**
|
||||
* Update checkin preferences from the preferences dict persisted as a plist. The dict contains
|
||||
* all the checkin preferences retrieved from the server except the deviceID and secret which
|
||||
* are stored in the Keychain.
|
||||
*
|
||||
* @param checkinPlistContent The checkin preferences saved in a plist on the disk.
|
||||
*/
|
||||
- (void)updateWithCheckinPlistContents:(NSDictionary *)checkinPlistContent;
|
||||
|
||||
/**
|
||||
* Reset the current checkin preferences object.
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
/**
|
||||
* The string that contains the checkin auth credentials i.e. deviceID and secret. This
|
||||
* needs to be stored in the Keychain.
|
||||
*
|
||||
* @return The checkin auth credential string containing the deviceID and secret.
|
||||
*/
|
||||
- (NSString *)checkinKeychainContent;
|
||||
|
||||
@end
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDCheckinPreferences+Internal.h"
|
||||
|
||||
#import "FIRInstanceIDCheckinService.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
|
||||
static NSString *const kCheckinKeychainContentSeparatorString = @"|";
|
||||
|
||||
@interface FIRInstanceIDCheckinPreferences ()
|
||||
|
||||
@property(nonatomic, readwrite, copy) NSString *deviceID;
|
||||
@property(nonatomic, readwrite, copy) NSString *secretToken;
|
||||
@property(nonatomic, readwrite, copy) NSString *digest;
|
||||
@property(nonatomic, readwrite, copy) NSString *versionInfo;
|
||||
@property(nonatomic, readwrite, copy) NSString *deviceDataVersion;
|
||||
|
||||
@property(nonatomic, readwrite, strong) NSMutableDictionary *gServicesData;
|
||||
@property(nonatomic, readwrite, assign) int64_t lastCheckinTimestampMillis;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDCheckinPreferences (Internal)
|
||||
|
||||
+ (FIRInstanceIDCheckinPreferences *)preferencesFromKeychainContents:(NSString *)keychainContent {
|
||||
NSString *deviceID = [self checkinDeviceIDFromKeychainContent:keychainContent];
|
||||
NSString *secret = [self checkinSecretFromKeychainContent:keychainContent];
|
||||
if ([deviceID length] && [secret length]) {
|
||||
return [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:deviceID secretToken:secret];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithDeviceID:(NSString *)deviceID secretToken:(NSString *)secretToken {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.deviceID = [deviceID copy];
|
||||
self.secretToken = [secretToken copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
self.deviceID = nil;
|
||||
self.secretToken = nil;
|
||||
self.digest = nil;
|
||||
self.versionInfo = nil;
|
||||
self.gServicesData = nil;
|
||||
self.deviceDataVersion = nil;
|
||||
self.lastCheckinTimestampMillis = 0;
|
||||
}
|
||||
|
||||
- (void)updateWithCheckinPlistContents:(NSDictionary *)checkinPlistContent {
|
||||
for (NSString *key in checkinPlistContent) {
|
||||
if ([kFIRInstanceIDDigestStringKey isEqualToString:key]) {
|
||||
self.digest = [checkinPlistContent[key] copy];
|
||||
} else if ([kFIRInstanceIDVersionInfoStringKey isEqualToString:key]) {
|
||||
self.versionInfo = [checkinPlistContent[key] copy];
|
||||
} else if ([kFIRInstanceIDLastCheckinTimeKey isEqualToString:key]) {
|
||||
self.lastCheckinTimestampMillis = [checkinPlistContent[key] longLongValue];
|
||||
} else if ([kFIRInstanceIDGServicesDictionaryKey isEqualToString:key]) {
|
||||
self.gServicesData = [checkinPlistContent[key] mutableCopy];
|
||||
} else if ([kFIRInstanceIDDeviceDataVersionKey isEqualToString:key]) {
|
||||
self.deviceDataVersion = [checkinPlistContent[key] copy];
|
||||
}
|
||||
// Otherwise we have some keys we don't care about
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)checkinKeychainContent {
|
||||
if ([self.deviceID length] && [self.secretToken length]) {
|
||||
return [NSString stringWithFormat:@"%@%@%@", self.deviceID,
|
||||
kCheckinKeychainContentSeparatorString, self.secretToken];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSString *)checkinDeviceIDFromKeychainContent:(NSString *)keychainContent {
|
||||
return [self checkinKeychainContent:keychainContent forIndex:0];
|
||||
}
|
||||
|
||||
+ (NSString *)checkinSecretFromKeychainContent:(NSString *)keychainContent {
|
||||
return [self checkinKeychainContent:keychainContent forIndex:1];
|
||||
}
|
||||
|
||||
+ (NSString *)checkinKeychainContent:(NSString *)keychainContent forIndex:(int)index {
|
||||
NSArray *keychainComponents =
|
||||
[keychainContent componentsSeparatedByString:kCheckinKeychainContentSeparatorString];
|
||||
if (index >= 0 && index < 2 && [keychainComponents count] == 2) {
|
||||
return keychainComponents[index];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* The preferences InstanceID loads from checkin server. The deviceID and secret that checkin
|
||||
* provides is used to authenticate all future requests to the server. Besides the deviceID
|
||||
* and secret the other information that checkin provides is stored in a plist on the device.
|
||||
* The deviceID and secret are persisted in the device keychain.
|
||||
*/
|
||||
@interface FIRInstanceIDCheckinPreferences : NSObject
|
||||
|
||||
/**
|
||||
* DeviceID and secretToken are the checkin auth credentials and are stored in the Keychain.
|
||||
*/
|
||||
@property(nonatomic, readonly, copy) NSString *deviceID;
|
||||
@property(nonatomic, readonly, copy) NSString *secretToken;
|
||||
|
||||
/**
|
||||
* All the other checkin preferences other than deviceID and secret are stored in a plist.
|
||||
*/
|
||||
@property(nonatomic, readonly, copy) NSString *deviceDataVersion;
|
||||
@property(nonatomic, readonly, copy) NSString *digest;
|
||||
@property(nonatomic, readonly, copy) NSString *versionInfo;
|
||||
@property(nonatomic, readonly, assign) int64_t lastCheckinTimestampMillis;
|
||||
|
||||
/**
|
||||
* The content retrieved from checkin server that should be persisted in a plist. This
|
||||
* doesn't contain the deviceID and secret which are stored in the Keychain since they
|
||||
* should be more private.
|
||||
*
|
||||
* @return The checkin preferences that should be persisted in a plist.
|
||||
*/
|
||||
- (NSDictionary *)checkinPlistContents;
|
||||
|
||||
/**
|
||||
* Return whether checkin info exists, valid or not.
|
||||
*/
|
||||
- (BOOL)hasCheckinInfo;
|
||||
|
||||
/**
|
||||
* Verify if checkin preferences are valid or not.
|
||||
*
|
||||
* @return YES if valid checkin preferences else NO.
|
||||
*/
|
||||
- (BOOL)hasValidCheckinInfo;
|
||||
|
||||
@end
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDCheckinPreferences.h"
|
||||
|
||||
#import <GoogleUtilities/GULUserDefaults.h>
|
||||
#import "FIRInstanceIDCheckinService.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
|
||||
const NSTimeInterval kFIRInstanceIDDefaultCheckinInterval = 7 * 24 * 60 * 60; // 7 days.
|
||||
|
||||
@interface FIRInstanceIDCheckinPreferences ()
|
||||
|
||||
@property(nonatomic, readwrite, copy) NSString *deviceID;
|
||||
@property(nonatomic, readwrite, copy) NSString *secretToken;
|
||||
@property(nonatomic, readwrite, copy) NSString *digest;
|
||||
@property(nonatomic, readwrite, copy) NSString *versionInfo;
|
||||
@property(nonatomic, readwrite, copy) NSString *deviceDataVersion;
|
||||
|
||||
@property(nonatomic, readwrite, strong) NSMutableDictionary *gServicesData;
|
||||
@property(nonatomic, readwrite, assign) int64_t lastCheckinTimestampMillis;
|
||||
|
||||
// This flag indicates that we have already saved the above deviceID and secret
|
||||
// to our keychain and hence we don't need to save again. This is helpful since
|
||||
// on checkin refresh we can avoid writing to the Keychain which can sometimes
|
||||
// be very buggy. For info check this https://forums.developer.apple.com/thread/4743
|
||||
@property(nonatomic, readwrite, assign) BOOL hasPreCachedAuthCredentials;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDCheckinPreferences
|
||||
|
||||
- (NSDictionary *)checkinPlistContents {
|
||||
NSMutableDictionary *checkinPlistContents = [NSMutableDictionary dictionary];
|
||||
checkinPlistContents[kFIRInstanceIDDigestStringKey] = self.digest ?: @"";
|
||||
checkinPlistContents[kFIRInstanceIDVersionInfoStringKey] = self.versionInfo ?: @"";
|
||||
checkinPlistContents[kFIRInstanceIDDeviceDataVersionKey] = self.deviceDataVersion ?: @"";
|
||||
checkinPlistContents[kFIRInstanceIDLastCheckinTimeKey] = @(self.lastCheckinTimestampMillis);
|
||||
checkinPlistContents[kFIRInstanceIDGServicesDictionaryKey] =
|
||||
[self.gServicesData count] ? self.gServicesData : @{};
|
||||
return checkinPlistContents;
|
||||
}
|
||||
|
||||
- (BOOL)hasCheckinInfo {
|
||||
return (self.deviceID.length && self.secretToken.length);
|
||||
}
|
||||
|
||||
- (BOOL)hasValidCheckinInfo {
|
||||
int64_t currentTimestampInMillis = FIRInstanceIDCurrentTimestampInMilliseconds();
|
||||
int64_t timeSinceLastCheckinInMillis = currentTimestampInMillis - self.lastCheckinTimestampMillis;
|
||||
|
||||
BOOL hasCheckinInfo = [self hasCheckinInfo];
|
||||
NSString *lastLocale =
|
||||
[[GULUserDefaults standardUserDefaults] stringForKey:kFIRInstanceIDUserDefaultsKeyLocale];
|
||||
// If it's app's first time open and checkin is already fetched and no locale information is
|
||||
// stored, then checkin info is valid. We should not checkin again because locale is considered
|
||||
// "changed".
|
||||
if (hasCheckinInfo && !lastLocale) {
|
||||
NSString *currentLocale = FIRInstanceIDCurrentLocale();
|
||||
[[GULUserDefaults standardUserDefaults] setObject:currentLocale
|
||||
forKey:kFIRInstanceIDUserDefaultsKeyLocale];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// If locale has changed, checkin info is no longer valid.
|
||||
// Also update locale information if changed. (Only do it here not in token refresh)
|
||||
if (FIRInstanceIDHasLocaleChanged()) {
|
||||
NSString *currentLocale = FIRInstanceIDCurrentLocale();
|
||||
[[GULUserDefaults standardUserDefaults] setObject:currentLocale
|
||||
forKey:kFIRInstanceIDUserDefaultsKeyLocale];
|
||||
return NO;
|
||||
}
|
||||
|
||||
return (hasCheckinInfo &&
|
||||
(timeSinceLastCheckinInMillis / 1000.0 < kFIRInstanceIDDefaultCheckinInterval));
|
||||
}
|
||||
|
||||
- (void)setHasPreCachedAuthCredentials:(BOOL)hasPreCachedAuthCredentials {
|
||||
_hasPreCachedAuthCredentials = hasPreCachedAuthCredentials;
|
||||
}
|
||||
|
||||
@end
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDCheckinPreferences.h"
|
||||
|
||||
/** Checkin refresh interval. **/
|
||||
FOUNDATION_EXPORT const NSTimeInterval kFIRInstanceIDDefaultCheckinInterval;
|
||||
|
||||
@interface FIRInstanceIDCheckinPreferences ()
|
||||
|
||||
- (BOOL)hasPreCachedAuthCredentials;
|
||||
- (void)setHasPreCachedAuthCredentials:(BOOL)hasPreCachedAuthCredentials;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// keys in Checkin preferences
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDDeviceAuthIdKey;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDSecretTokenKey;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDDigestStringKey;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDLastCheckinTimeKey;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDVersionInfoStringKey;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDGServicesDictionaryKey;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDDeviceDataVersionKey;
|
||||
|
||||
@class FIRInstanceIDCheckinPreferences;
|
||||
|
||||
/**
|
||||
* @related FIRInstanceIDCheckinService
|
||||
*
|
||||
* The completion handler invoked once the fetch from Checkin server finishes.
|
||||
* For successful fetches we returned checkin information by the checkin service
|
||||
* and `nil` error, else we return the appropriate error object as reported by the
|
||||
* Checkin Service.
|
||||
*
|
||||
* @param checkinPreferences The checkin preferences as fetched from the server.
|
||||
* @param error The error object which fetching GServices data.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDDeviceCheckinCompletion)(
|
||||
FIRInstanceIDCheckinPreferences *_Nullable checkinPreferences, NSError *_Nullable error);
|
||||
|
||||
/**
|
||||
* Register the device with Checkin Service and get back the `authID`, `secret
|
||||
* token` etc. for the client. Checkin results are cached in the
|
||||
* `FIRInstanceIDCache` and periodically refreshed to prevent them from being stale.
|
||||
* Each client needs to register with checkin before registering with InstanceID.
|
||||
*/
|
||||
@interface FIRInstanceIDCheckinService : NSObject
|
||||
|
||||
/**
|
||||
* Execute a device checkin request to obtain an deviceID, secret token,
|
||||
* gService data.
|
||||
*
|
||||
* @param existingCheckin An existing checkin preference object, if available.
|
||||
* @param completion Completion hander called on success or failure of device checkin.
|
||||
*/
|
||||
- (void)checkinWithExistingCheckin:(nullable FIRInstanceIDCheckinPreferences *)existingCheckin
|
||||
completion:(FIRInstanceIDDeviceCheckinCompletion)completion;
|
||||
|
||||
/**
|
||||
* This would stop any request that the service made to the checkin backend and also
|
||||
* release any callback handlers that it holds.
|
||||
*/
|
||||
- (void)stopFetching;
|
||||
|
||||
/**
|
||||
* Set test block for mock testing network requests.
|
||||
*
|
||||
* @param block The block to invoke as a mock response from the network.
|
||||
*/
|
||||
+ (void)setCheckinTestBlock:(nullable FIRInstanceIDURLRequestTestBlock)block;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDCheckinService.h"
|
||||
|
||||
#import "FIRInstanceIDCheckinPreferences+Internal.h"
|
||||
#import "FIRInstanceIDCheckinPreferences_Private.h"
|
||||
#import "FIRInstanceIDDefines.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDStore.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
static NSString *const kDeviceCheckinURL = @"https://device-provisioning.googleapis.com/checkin";
|
||||
|
||||
// keys in Checkin preferences
|
||||
NSString *const kFIRInstanceIDDeviceAuthIdKey = @"GMSInstanceIDDeviceAuthIdKey";
|
||||
NSString *const kFIRInstanceIDSecretTokenKey = @"GMSInstanceIDSecretTokenKey";
|
||||
NSString *const kFIRInstanceIDDigestStringKey = @"GMSInstanceIDDigestKey";
|
||||
NSString *const kFIRInstanceIDLastCheckinTimeKey = @"GMSInstanceIDLastCheckinTimestampKey";
|
||||
NSString *const kFIRInstanceIDVersionInfoStringKey = @"GMSInstanceIDVersionInfo";
|
||||
NSString *const kFIRInstanceIDGServicesDictionaryKey = @"GMSInstanceIDGServicesData";
|
||||
NSString *const kFIRInstanceIDDeviceDataVersionKey = @"GMSInstanceIDDeviceDataVersion";
|
||||
|
||||
static NSUInteger const kCheckinType = 2; // DeviceType IOS in l/w/a/_checkin.proto
|
||||
static NSUInteger const kCheckinVersion = 2;
|
||||
static NSUInteger const kFragment = 0;
|
||||
|
||||
static FIRInstanceIDURLRequestTestBlock testBlock;
|
||||
|
||||
@interface FIRInstanceIDCheckinService ()
|
||||
|
||||
@property(nonatomic, readwrite, strong) NSURLSession *session;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDCheckinService
|
||||
;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
// Create an URLSession once, even though checkin should happen about once a day
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
config.timeoutIntervalForResource = 60.0f; // 1 minute
|
||||
config.allowsCellularAccess = YES;
|
||||
_session = [NSURLSession sessionWithConfiguration:config];
|
||||
_session.sessionDescription = @"com.google.iid-checkin";
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
testBlock = nil;
|
||||
[self.session invalidateAndCancel];
|
||||
}
|
||||
|
||||
- (void)checkinWithExistingCheckin:(FIRInstanceIDCheckinPreferences *)existingCheckin
|
||||
completion:(FIRInstanceIDDeviceCheckinCompletion)completion {
|
||||
if (self.session == nil) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDInvalidNetworkSession,
|
||||
@"Inconsistent state: NSURLSession has been invalidated");
|
||||
NSError *error =
|
||||
[NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn];
|
||||
if (completion) {
|
||||
completion(nil, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSURL *url = [NSURL URLWithString:kDeviceCheckinURL];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"content-type"];
|
||||
NSDictionary *checkinParameters = [self checkinParametersWithExistingCheckin:existingCheckin];
|
||||
NSData *checkinData = [NSJSONSerialization dataWithJSONObject:checkinParameters
|
||||
options:0
|
||||
error:nil];
|
||||
request.HTTPMethod = @"POST";
|
||||
request.HTTPBody = checkinData;
|
||||
|
||||
void (^handler)(NSData *, NSURLResponse *, NSError *) =
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService000,
|
||||
@"Device checkin HTTP fetch error. Error Code: %ld",
|
||||
(long)error.code);
|
||||
if (completion) {
|
||||
completion(nil, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSError *serializationError;
|
||||
NSDictionary *dataResponse = [NSJSONSerialization JSONObjectWithData:data
|
||||
options:0
|
||||
error:&serializationError];
|
||||
if (serializationError) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService001,
|
||||
@"Error serializing json object. Error Code: %ld",
|
||||
_FIRInstanceID_L(serializationError.code));
|
||||
if (completion) {
|
||||
completion(nil, serializationError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *deviceAuthID = [dataResponse[@"android_id"] stringValue];
|
||||
NSString *secretToken = [dataResponse[@"security_token"] stringValue];
|
||||
if ([deviceAuthID length] == 0) {
|
||||
NSError *error =
|
||||
[NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidRequest];
|
||||
if (completion) {
|
||||
completion(nil, error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t lastCheckinTimestampMillis = [dataResponse[@"time_msec"] longLongValue];
|
||||
int64_t currentTimestampMillis = FIRInstanceIDCurrentTimestampInMilliseconds();
|
||||
// Somehow the server clock gets out of sync with the device clock.
|
||||
// Reset the last checkin timestamp in case this happens.
|
||||
if (lastCheckinTimestampMillis > currentTimestampMillis) {
|
||||
FIRInstanceIDLoggerDebug(
|
||||
kFIRInstanceIDMessageCodeService002, @"Invalid last checkin timestamp %@ in future.",
|
||||
[NSDate dateWithTimeIntervalSince1970:lastCheckinTimestampMillis / 1000.0]);
|
||||
lastCheckinTimestampMillis = currentTimestampMillis;
|
||||
}
|
||||
|
||||
NSString *deviceDataVersionInfo = dataResponse[@"device_data_version_info"] ?: @"";
|
||||
NSString *digest = dataResponse[@"digest"] ?: @"";
|
||||
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService003,
|
||||
@"Checkin successful with authId: %@, "
|
||||
@"digest: %@, "
|
||||
@"lastCheckinTimestamp: %lld",
|
||||
deviceAuthID, digest, lastCheckinTimestampMillis);
|
||||
|
||||
NSString *versionInfo = dataResponse[@"version_info"] ?: @"";
|
||||
NSMutableDictionary *gservicesData = [NSMutableDictionary dictionary];
|
||||
|
||||
// Read gServices data.
|
||||
NSArray *flatSettings = dataResponse[@"setting"];
|
||||
for (NSDictionary *dict in flatSettings) {
|
||||
if (dict[@"name"] && dict[@"value"]) {
|
||||
gservicesData[dict[@"name"]] = dict[@"value"];
|
||||
} else {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDInvalidSettingResponse,
|
||||
@"Invalid setting in checkin response: (%@: %@)",
|
||||
dict[@"name"], dict[@"value"]);
|
||||
}
|
||||
}
|
||||
|
||||
FIRInstanceIDCheckinPreferences *checkinPreferences =
|
||||
[[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:deviceAuthID
|
||||
secretToken:secretToken];
|
||||
NSDictionary *preferences = @{
|
||||
kFIRInstanceIDDigestStringKey : digest,
|
||||
kFIRInstanceIDVersionInfoStringKey : versionInfo,
|
||||
kFIRInstanceIDLastCheckinTimeKey : @(lastCheckinTimestampMillis),
|
||||
kFIRInstanceIDGServicesDictionaryKey : gservicesData,
|
||||
kFIRInstanceIDDeviceDataVersionKey : deviceDataVersionInfo,
|
||||
};
|
||||
[checkinPreferences updateWithCheckinPlistContents:preferences];
|
||||
if (completion) {
|
||||
completion(checkinPreferences, nil);
|
||||
}
|
||||
};
|
||||
// Test block
|
||||
if (testBlock) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService005,
|
||||
@"Test block set, will not hit the server");
|
||||
testBlock(request, handler);
|
||||
return;
|
||||
}
|
||||
|
||||
NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request completionHandler:handler];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
- (void)stopFetching {
|
||||
[self.session invalidateAndCancel];
|
||||
// The session cannot be reused after invalidation. Dispose it to prevent accident reusing.
|
||||
self.session = nil;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (NSDictionary *)checkinParametersWithExistingCheckin:
|
||||
(nullable FIRInstanceIDCheckinPreferences *)checkinPreferences {
|
||||
NSString *deviceModel = FIRInstanceIDDeviceModel();
|
||||
NSString *systemVersion = FIRInstanceIDOperatingSystemVersion();
|
||||
NSString *osVersion = [NSString stringWithFormat:@"IOS_%@", systemVersion];
|
||||
|
||||
// Get locale from GCM if GCM exists else use system API.
|
||||
NSString *locale = FIRInstanceIDCurrentLocale();
|
||||
|
||||
NSInteger userNumber = 0; // Multi Profile may change this.
|
||||
NSInteger userSerialNumber = 0; // Multi Profile may change this
|
||||
|
||||
uint32_t loggingID = arc4random();
|
||||
NSString *timeZone = [NSTimeZone localTimeZone].name;
|
||||
int64_t lastCheckingTimestampMillis = checkinPreferences.lastCheckinTimestampMillis;
|
||||
|
||||
NSDictionary *checkinParameters = @{
|
||||
@"checkin" : @{
|
||||
@"iosbuild" : @{@"model" : deviceModel, @"os_version" : osVersion},
|
||||
@"type" : @(kCheckinType),
|
||||
@"user_number" : @(userNumber),
|
||||
@"last_checkin_msec" : @(lastCheckingTimestampMillis),
|
||||
},
|
||||
@"fragment" : @(kFragment),
|
||||
@"logging_id" : @(loggingID),
|
||||
@"locale" : locale,
|
||||
@"version" : @(kCheckinVersion),
|
||||
@"digest" : checkinPreferences.digest ?: @"",
|
||||
@"time_zone" : timeZone,
|
||||
@"user_serial_number" : @(userSerialNumber),
|
||||
@"id" : @([checkinPreferences.deviceID longLongValue]),
|
||||
@"security_token" : @([checkinPreferences.secretToken longLongValue]),
|
||||
};
|
||||
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeService006, @"Checkin parameters: %@",
|
||||
checkinParameters);
|
||||
return checkinParameters;
|
||||
}
|
||||
|
||||
+ (void)setCheckinTestBlock:(FIRInstanceIDURLRequestTestBlock)block {
|
||||
testBlock = [block copy];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class FIRInstanceIDAuthKeychain;
|
||||
@class FIRInstanceIDBackupExcludedPlist;
|
||||
@class FIRInstanceIDCheckinPreferences;
|
||||
|
||||
// These values exposed for testing
|
||||
extern NSString *const kFIRInstanceIDCheckinKeychainService;
|
||||
extern NSString *const kFIRInstanceIDLegacyCheckinKeychainAccount;
|
||||
extern NSString *const kFIRInstanceIDLegacyCheckinKeychainService;
|
||||
|
||||
/**
|
||||
* Checkin preferences backing store.
|
||||
*/
|
||||
@interface FIRInstanceIDCheckinStore : NSObject
|
||||
|
||||
/**
|
||||
* Designated Initializer. Initialize a checkin store with the given backup excluded
|
||||
* plist filename.
|
||||
*
|
||||
* @param checkinFilename The backup excluded plist filename to persist checkin
|
||||
* preferences.
|
||||
*
|
||||
* @param subDirectoryName Sub-directory in standard directory where we write
|
||||
* InstanceID plist.
|
||||
*
|
||||
* @return Store to persist checkin preferences.
|
||||
*/
|
||||
- (instancetype)initWithCheckinPlistFileName:(NSString *)checkinFilename
|
||||
subDirectoryName:(NSString *)subDirectoryName;
|
||||
|
||||
/**
|
||||
* Initialize a checkin store with the given backup excluded plist and keychain.
|
||||
*
|
||||
* @param plist The backup excluded plist to persist checkin preferences.
|
||||
* @param keychain The keychain used to persist checkin auth preferences.
|
||||
*
|
||||
* @return Store to persist checkin preferences.
|
||||
*/
|
||||
- (instancetype)initWithCheckinPlist:(FIRInstanceIDBackupExcludedPlist *)plist
|
||||
keychain:(FIRInstanceIDAuthKeychain *)keychain;
|
||||
|
||||
/**
|
||||
* Checks whether the backup excluded checkin preferences are present on the disk or not.
|
||||
*
|
||||
* @return YES if the backup excluded checkin plist exists on the disks else NO.
|
||||
*/
|
||||
- (BOOL)hasCheckinPlist;
|
||||
|
||||
#pragma mark - Save
|
||||
|
||||
/**
|
||||
* Save the checkin preferences to backing store.
|
||||
*
|
||||
* @param preferences Checkin preferences to save.
|
||||
* @param handler The callback handler which is invoked when the operation is complete,
|
||||
* with an error if there is any.
|
||||
*/
|
||||
- (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences
|
||||
handler:(void (^)(NSError *error))handler;
|
||||
|
||||
#pragma mark - Delete
|
||||
|
||||
/**
|
||||
* Remove the cached checkin preferences.
|
||||
*
|
||||
* @param handler The callback handler which is invoked when the operation is complete,
|
||||
* with an error if there is any.
|
||||
*/
|
||||
- (void)removeCheckinPreferencesWithHandler:(void (^)(NSError *error))handler;
|
||||
|
||||
#pragma mark - Get
|
||||
|
||||
/**
|
||||
* Get the cached device secret. If we cannot access it for some reason we
|
||||
* return the appropriate error object.
|
||||
*
|
||||
* @return The cached checkin preferences if present else nil.
|
||||
*/
|
||||
- (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences;
|
||||
|
||||
/**
|
||||
* Migrate the checkin item from old service/account to the new one.
|
||||
* The new account is dynamic as it uses bundle ID.
|
||||
* This is to ensure checkin is not shared across apps, but still the same
|
||||
* if app has used GCM before.
|
||||
* This call should only happen once.
|
||||
*
|
||||
*/
|
||||
- (void)migrateCheckinItemIfNeeded;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDCheckinStore.h"
|
||||
|
||||
#import "FIRInstanceIDAuthKeyChain.h"
|
||||
#import "FIRInstanceIDBackupExcludedPlist.h"
|
||||
#import "FIRInstanceIDCheckinPreferences+Internal.h"
|
||||
#import "FIRInstanceIDCheckinPreferences_Private.h"
|
||||
#import "FIRInstanceIDCheckinService.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
#import "FIRInstanceIDVersionUtilities.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
static NSString *const kFIRInstanceIDCheckinKeychainGeneric = @"com.google.iid";
|
||||
|
||||
NSString *const kFIRInstanceIDCheckinKeychainService = @"com.google.iid.checkin";
|
||||
NSString *const kFIRInstanceIDLegacyCheckinKeychainAccount = @"com.google.iid.checkin-account";
|
||||
NSString *const kFIRInstanceIDLegacyCheckinKeychainService = @"com.google.iid.checkin-service";
|
||||
|
||||
// Checkin plist used to have the deviceID and secret stored in them and that's why they
|
||||
// had 6 items in it. Since the deviceID and secret have been moved to the keychain
|
||||
// there would only be 4 items.
|
||||
static const NSInteger kOldCheckinPlistCount = 6;
|
||||
|
||||
@interface FIRInstanceIDCheckinStore ()
|
||||
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDBackupExcludedPlist *plist;
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDAuthKeychain *keychain;
|
||||
// Checkin will store items under
|
||||
// Keychain account: <app bundle id>,
|
||||
// Keychain service: |kFIRInstanceIDCheckinKeychainService|
|
||||
@property(nonatomic, readonly) NSString *bundleIdentifierForKeychainAccount;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDCheckinStore
|
||||
|
||||
- (instancetype)initWithCheckinPlistFileName:(NSString *)checkinFilename
|
||||
subDirectoryName:(NSString *)subDirectoryName {
|
||||
FIRInstanceIDBackupExcludedPlist *plist =
|
||||
[[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:checkinFilename
|
||||
subDirectory:subDirectoryName];
|
||||
|
||||
FIRInstanceIDAuthKeychain *keychain =
|
||||
[[FIRInstanceIDAuthKeychain alloc] initWithIdentifier:kFIRInstanceIDCheckinKeychainGeneric];
|
||||
return [self initWithCheckinPlist:plist keychain:keychain];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCheckinPlist:(FIRInstanceIDBackupExcludedPlist *)plist
|
||||
keychain:(FIRInstanceIDAuthKeychain *)keychain {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_plist = plist;
|
||||
_keychain = keychain;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)hasCheckinPlist {
|
||||
return [self.plist doesFileExist];
|
||||
}
|
||||
|
||||
- (NSString *)bundleIdentifierForKeychainAccount {
|
||||
static NSString *bundleIdentifier;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
bundleIdentifier = FIRInstanceIDAppIdentifier();
|
||||
});
|
||||
return bundleIdentifier;
|
||||
}
|
||||
|
||||
- (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences
|
||||
handler:(void (^)(NSError *error))handler {
|
||||
NSDictionary *checkinPlistContents = [preferences checkinPlistContents];
|
||||
NSString *checkinKeychainContent = [preferences checkinKeychainContent];
|
||||
|
||||
if (![checkinKeychainContent length]) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore000,
|
||||
@"Failed to get checkin keychain content from memory.");
|
||||
if (handler) {
|
||||
handler([NSError
|
||||
errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (![checkinPlistContents count]) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore001,
|
||||
@"Failed to get checkin plist contents from memory.");
|
||||
if (handler) {
|
||||
handler([NSError
|
||||
errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Save all other checkin preferences in a plist
|
||||
NSError *error;
|
||||
if (![self.plist writeDictionary:checkinPlistContents error:&error]) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore003,
|
||||
@"Failed to save checkin plist contents."
|
||||
@"Will delete auth credentials");
|
||||
[self.keychain removeItemsMatchingService:kFIRInstanceIDCheckinKeychainService
|
||||
account:self.bundleIdentifierForKeychainAccount
|
||||
handler:nil];
|
||||
if (handler) {
|
||||
handler(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistSaved,
|
||||
@"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:kFIRInstanceIDCheckinKeychainService
|
||||
accessibility:nil
|
||||
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;
|
||||
}
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistDeleted,
|
||||
@"Deleted checkin plist file.");
|
||||
// Remove deviceID and secret from Keychain
|
||||
[self.keychain
|
||||
removeItemsMatchingService:kFIRInstanceIDCheckinKeychainService
|
||||
account:self.bundleIdentifierForKeychainAccount
|
||||
handler:^(NSError *error) {
|
||||
// Try to remove from old location as well because migration
|
||||
// is no longer needed. Consider this is either a fresh install
|
||||
// or an identity wipe.
|
||||
[self.keychain
|
||||
removeItemsMatchingService:kFIRInstanceIDLegacyCheckinKeychainService
|
||||
account:kFIRInstanceIDLegacyCheckinKeychainAccount
|
||||
handler:nil];
|
||||
handler(error);
|
||||
}];
|
||||
}
|
||||
|
||||
- (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences {
|
||||
// Query the keychain for deviceID and secret
|
||||
NSData *item = [self.keychain dataForService:kFIRInstanceIDCheckinKeychainService
|
||||
account:self.bundleIdentifierForKeychainAccount];
|
||||
|
||||
// Check info found in keychain
|
||||
NSString *checkinKeychainContent = [[NSString alloc] initWithData:item
|
||||
encoding:NSUTF8StringEncoding];
|
||||
FIRInstanceIDCheckinPreferences *checkinPreferences =
|
||||
[FIRInstanceIDCheckinPreferences preferencesFromKeychainContents:checkinKeychainContent];
|
||||
|
||||
NSDictionary *checkinPlistContents = [self.plist contentAsDictionary];
|
||||
|
||||
NSString *plistDeviceAuthID = checkinPlistContents[kFIRInstanceIDDeviceAuthIdKey];
|
||||
NSString *plistSecretToken = checkinPlistContents[kFIRInstanceIDSecretTokenKey];
|
||||
|
||||
// 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 =
|
||||
[[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:plistDeviceAuthID
|
||||
secretToken:plistSecretToken];
|
||||
} else {
|
||||
// Couldn't find checkin credentials in keychain nor plist
|
||||
return nil;
|
||||
}
|
||||
} else if (kOldCheckinPlistCount == checkinPlistContents.count) {
|
||||
// same check as above but just to be extra sure that we cover all upgrade cases properly.
|
||||
// TODO(chliangGoogle): Remove this case, after verifying it's not needed
|
||||
if ([plistDeviceAuthID length] && [plistSecretToken length]) {
|
||||
checkinPreferences =
|
||||
[[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:plistDeviceAuthID
|
||||
secretToken:plistSecretToken];
|
||||
}
|
||||
}
|
||||
|
||||
[checkinPreferences updateWithCheckinPlistContents:checkinPlistContents];
|
||||
return checkinPreferences;
|
||||
}
|
||||
|
||||
- (void)migrateCheckinItemIfNeeded {
|
||||
// Check for checkin in the old location, using the legacy keys
|
||||
// Query the keychain for deviceID and secret
|
||||
NSData *dataInOldLocation =
|
||||
[self.keychain dataForService:kFIRInstanceIDLegacyCheckinKeychainService
|
||||
account:kFIRInstanceIDLegacyCheckinKeychainAccount];
|
||||
if (dataInOldLocation) {
|
||||
// Save to new location
|
||||
[self.keychain setData:dataInOldLocation
|
||||
forService:kFIRInstanceIDCheckinKeychainService
|
||||
accessibility:NULL
|
||||
account:self.bundleIdentifierForKeychainAccount
|
||||
handler:nil];
|
||||
// Remove from old location
|
||||
[self.keychain removeItemsMatchingService:kFIRInstanceIDLegacyCheckinKeychainService
|
||||
account:kFIRInstanceIDLegacyCheckinKeychainAccount
|
||||
handler:nil];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* A generic class to combine several handler blocks into a single block in a thread-safe manner
|
||||
*/
|
||||
@interface FIRInstanceIDCombinedHandler<ResultType> : NSObject
|
||||
|
||||
- (void)addHandler:(void (^)(ResultType _Nullable result, NSError* _Nullable error))handler;
|
||||
- (void (^)(ResultType _Nullable result, NSError* _Nullable error))combinedHandler;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDCombinedHandler.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^FIRInstanseIDHandler)(id _Nullable result, NSError *_Nullable error);
|
||||
|
||||
@interface FIRInstanceIDCombinedHandler <ResultType>()
|
||||
@property(atomic, readonly, strong) NSMutableArray<FIRInstanseIDHandler> *handlers;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@implementation FIRInstanceIDCombinedHandler
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_handlers = [NSMutableArray array];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addHandler:(FIRInstanseIDHandler)handler {
|
||||
if (!handler) {
|
||||
return;
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
[self.handlers addObject:handler];
|
||||
}
|
||||
}
|
||||
|
||||
- (FIRInstanseIDHandler)combinedHandler {
|
||||
FIRInstanseIDHandler combinedHandler = nil;
|
||||
|
||||
@synchronized(self) {
|
||||
NSArray<FIRInstanseIDHandler> *handlers = [self.handlers copy];
|
||||
combinedHandler = ^(id result, NSError *error) {
|
||||
for (FIRInstanseIDHandler handler in handlers) {
|
||||
handler(result, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return combinedHandler;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#pragma mark - Commands
|
||||
|
||||
/**
|
||||
* Value included in a structured response or GCM message from IID, indicating
|
||||
* an identity reset.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceID_CMD_RST;
|
||||
|
||||
#pragma mark - Notifications
|
||||
|
||||
/// Notification used to deliver GCM messages for InstanceID.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDCheckinFetchedNotification;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDAPNSTokenNotification;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDDefaultGCMTokenNotification;
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDDefaultGCMTokenFailNotification;
|
||||
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDIdentityInvalidatedNotification;
|
||||
|
||||
#pragma mark - Miscellaneous
|
||||
|
||||
/// The scope used to save the IID "*" scope token. This is used for saving the
|
||||
/// IID auth token that we receive from the server. This feature was never
|
||||
/// implemented on the server side.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDAllScopeIdentifier;
|
||||
/// The scope used to save the IID "*" scope token.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDDefaultTokenScope;
|
||||
|
||||
/// Subdirectory in search path directory to store InstanceID preferences.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDSubDirectoryName;
|
||||
|
||||
/// The key for APNS token in options dictionary.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenOptionsAPNSKey;
|
||||
|
||||
/// The key for APNS token environment type in options dictionary.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenOptionsAPNSIsSandboxKey;
|
||||
|
||||
/// The key for GMP AppID sent in registration requests.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenOptionsFirebaseAppIDKey;
|
||||
|
||||
/// The key to enable auto-register by swizzling AppDelegate's methods.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDAppDelegateProxyEnabledInfoPlistKey;
|
||||
|
||||
/// Error code for missing entitlements in Keychain. iOS Keychain error
|
||||
/// https://forums.developer.apple.com/thread/4743
|
||||
FOUNDATION_EXPORT const int kFIRInstanceIDSecMissingEntitlementErrorCode;
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDConstants.h"
|
||||
|
||||
// Commands
|
||||
NSString *const kFIRInstanceID_CMD_RST = @"RST";
|
||||
|
||||
// NOTIFICATIONS
|
||||
NSString *const kFIRInstanceIDCheckinFetchedNotification = @"com.google.gcm.notif-checkin-fetched";
|
||||
NSString *const kFIRInstanceIDAPNSTokenNotification = @"com.firebase.iid.notif.apns-token";
|
||||
NSString *const kFIRInstanceIDDefaultGCMTokenNotification = @"com.firebase.iid.notif.fcm-token";
|
||||
NSString *const kFIRInstanceIDDefaultGCMTokenFailNotification =
|
||||
@"com.firebase.iid.notif.fcm-token-fail";
|
||||
|
||||
NSString *const kFIRInstanceIDIdentityInvalidatedNotification = @"com.google.iid.identity-invalid";
|
||||
|
||||
// Miscellaneous
|
||||
NSString *const kFIRInstanceIDAllScopeIdentifier = @"iid-all";
|
||||
NSString *const kFIRInstanceIDDefaultTokenScope = @"*";
|
||||
NSString *const kFIRInstanceIDSubDirectoryName = @"Google/FirebaseInstanceID";
|
||||
|
||||
// Registration Options
|
||||
NSString *const kFIRInstanceIDTokenOptionsAPNSKey = @"apns_token";
|
||||
NSString *const kFIRInstanceIDTokenOptionsAPNSIsSandboxKey = @"apns_sandbox";
|
||||
NSString *const kFIRInstanceIDTokenOptionsFirebaseAppIDKey = @"gmp_app_id";
|
||||
|
||||
NSString *const kFIRInstanceIDAppDelegateProxyEnabledInfoPlistKey =
|
||||
@"FirebaseAppDelegateProxyEnabled";
|
||||
|
||||
// iOS Keychain error https://forums.developer.apple.com/thread/4743
|
||||
// An undocumented error code hence need to be redeclared.
|
||||
const int kFIRInstanceIDSecMissingEntitlementErrorCode = -34018;
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef FIRInstanceIDLib_FIRInstanceIDDefines_h
|
||||
#define FIRInstanceIDLib_FIRInstanceIDDefines_h
|
||||
|
||||
#define _FIRInstanceID_VERBOSE_LOGGING 1
|
||||
|
||||
// Verbose Logging
|
||||
#if (_FIRInstanceID_VERBOSE_LOGGING)
|
||||
#define FIRInstanceID_DEV_VERBOSE_LOG(...) NSLog(__VA_ARGS__)
|
||||
#else
|
||||
#define FIRInstanceID_DEV_VERBOSE_LOG(...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif // VERBOSE_LOGGING
|
||||
|
||||
// WEAKIFY & STRONGIFY
|
||||
// Helper macro.
|
||||
#define _FIRInstanceID_WEAKNAME(VAR) VAR##_weak_
|
||||
|
||||
#define FIRInstanceID_WEAKIFY(VAR) __weak __typeof__(VAR) _FIRInstanceID_WEAKNAME(VAR) = (VAR);
|
||||
|
||||
#define FIRInstanceID_STRONGIFY(VAR) \
|
||||
_Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong __typeof__(VAR) VAR = _FIRInstanceID_WEAKNAME(VAR); \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
// Type Conversions (used for NSInteger etc)
|
||||
#ifndef _FIRInstanceID_L
|
||||
#define _FIRInstanceID_L(v) (long)(v)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@interface FIRInstanceIDKeyPair : NSObject
|
||||
|
||||
- (instancetype)init __attribute__((
|
||||
unavailable("Use -initWithPrivateKey:publicKey:publicKeyData:privateKeyData: instead.")));
|
||||
;
|
||||
|
||||
/**
|
||||
* Initialize a new 2048 bit RSA keypair. This also stores the keypair in the Keychain
|
||||
* Preferences.
|
||||
*
|
||||
* @param publicKey The publicKey stored in Keychain.
|
||||
* @param privateKey The privateKey stored in Keychain.
|
||||
* @param publicKeyData The publicKey in NSData format.
|
||||
* @param privateKeyData The privateKey in NSData format.
|
||||
*
|
||||
* @return A new KeyPair instance with the generated public and private key.
|
||||
*/
|
||||
- (instancetype)initWithPrivateKey:(SecKeyRef)privateKey
|
||||
publicKey:(SecKeyRef)publicKey
|
||||
publicKeyData:(NSData *)publicKeyData
|
||||
privateKeyData:(NSData *)privateKeyData NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* The public key in the RSA 20148 bit generated KeyPair.
|
||||
*
|
||||
* @return The 2048 bit RSA KeyPair's public key.
|
||||
*/
|
||||
@property(nonatomic, readonly, strong) NSData *publicKeyData;
|
||||
|
||||
/**
|
||||
* The private key in the RSA 20148 bit generated KeyPair.
|
||||
*
|
||||
* @return The 2048 bit RSA KeyPair's private key.
|
||||
*/
|
||||
@property(nonatomic, readonly, strong) NSData *privateKeyData;
|
||||
|
||||
#pragma mark - Info
|
||||
|
||||
/**
|
||||
* Checks if the private and public keyPair are valid or not.
|
||||
*
|
||||
* @return YES if keypair is valid else NO.
|
||||
*/
|
||||
- (BOOL)isValid;
|
||||
|
||||
/**
|
||||
* The public key in the RSA 2048 bit generated KeyPair.
|
||||
*
|
||||
* @return The 2048 bit RSA KeyPair's public key.
|
||||
*/
|
||||
- (SecKeyRef)publicKey;
|
||||
|
||||
/**
|
||||
* The private key in the RSA 2048 bit generated KeyPair.
|
||||
*
|
||||
* @return The 2048 bit RSA KeyPair's private key.
|
||||
*/
|
||||
- (SecKeyRef)privateKey;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDKeyPair.h"
|
||||
|
||||
#import <Security/Security.h>
|
||||
|
||||
#import "FIRInstanceIDKeyPairUtilities.h"
|
||||
#import "FIRInstanceIDKeychain.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
@interface FIRInstanceIDKeyPair () {
|
||||
SecKeyRef _privateKey;
|
||||
SecKeyRef _publicKey;
|
||||
}
|
||||
|
||||
@property(nonatomic, readwrite, strong) NSData *publicKeyData;
|
||||
@property(nonatomic, readwrite, strong) NSData *privateKeyData;
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDKeyPair
|
||||
- (instancetype)initWithPrivateKey:(SecKeyRef)privateKey
|
||||
publicKey:(SecKeyRef)publicKey
|
||||
publicKeyData:(NSData *)publicKeyData
|
||||
privateKeyData:(NSData *)privateKeyData {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_privateKey = privateKey;
|
||||
_publicKey = publicKey;
|
||||
_publicKeyData = publicKeyData;
|
||||
_privateKeyData = privateKeyData;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_privateKey) {
|
||||
CFRelease(_privateKey);
|
||||
}
|
||||
if (_publicKey) {
|
||||
CFRelease(_publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Info
|
||||
|
||||
- (BOOL)isValid {
|
||||
return _privateKey != NULL && _publicKey != NULL;
|
||||
}
|
||||
|
||||
- (SecKeyRef)publicKey {
|
||||
return _publicKey;
|
||||
}
|
||||
|
||||
- (SecKeyRef)privateKey {
|
||||
return _privateKey;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class FIRInstanceIDKeyPair;
|
||||
|
||||
extern NSString *const kFIRInstanceIDKeyPairSubType;
|
||||
|
||||
@class FIRInstanceIDKeyPairStore;
|
||||
|
||||
@interface FIRInstanceIDKeyPairStore : NSObject
|
||||
|
||||
/**
|
||||
* Invalidates the cached keypairs in the Keychain, if needed. The keypair metadata plist is
|
||||
* checked for existence. If the plist file does not exist, it is a signal of a new installation,
|
||||
* and therefore the key pairs are not valid.
|
||||
*
|
||||
* Returns YES if keypair has been invalidated.
|
||||
*/
|
||||
- (BOOL)invalidateKeyPairsIfNeeded;
|
||||
|
||||
/**
|
||||
* Delete the cached RSA keypair from Keychain with the given subtype.
|
||||
*
|
||||
* @param subtype The subtype used to cache the RSA keypair in Keychain.
|
||||
* @param handler The callback handler which is invoked when the keypair deletion is
|
||||
* complete, with an error if there is any.
|
||||
*/
|
||||
- (void)deleteSavedKeyPairWithSubtype:(NSString *)subtype handler:(void (^)(NSError *))handler;
|
||||
|
||||
/**
|
||||
* Delete the plist that caches KeyPair generation timestamps.
|
||||
*
|
||||
* @param error The error if any while deleting the plist else nil.
|
||||
*
|
||||
* @return YES if the delete was successful else NO.
|
||||
*/
|
||||
- (BOOL)removeKeyPairCreationTimePlistWithError:(NSError **)error;
|
||||
|
||||
/**
|
||||
* Loads a cached KeyPair if it exists in the Keychain else generate a new
|
||||
* one. If a keyPair already exists in memory this will just return that. This should
|
||||
* not be called from the main thread since it could potentially lead to creating a new
|
||||
* RSA-2048 bit keyPair which is an expensive operation.
|
||||
*
|
||||
* @param error The error, if any, while accessing the Keychain.
|
||||
*
|
||||
* @return A valid 2048 bit RSA key pair.
|
||||
*/
|
||||
- (FIRInstanceIDKeyPair *)loadKeyPairWithError:(NSError **)error;
|
||||
|
||||
/**
|
||||
* Check if the Keychain has any cached keypairs or not.
|
||||
*
|
||||
* @return YES if the Keychain has cached RSA KeyPairs else NO.
|
||||
*/
|
||||
- (BOOL)hasCachedKeyPairs;
|
||||
|
||||
/**
|
||||
* Return an identifier for the app instance. The result is a short identifier that can
|
||||
* be used as a key when storing information about the app. This method will return the same
|
||||
* ID as long as the application identity remains active. If the identity has been revoked or
|
||||
* expired the method will generate and return a new identifier.
|
||||
*
|
||||
* @param error The error if any while loading the RSA KeyPair.
|
||||
*
|
||||
* @return The identifier, as url safe string.
|
||||
*/
|
||||
- (NSString *)appIdentityWithError:(NSError *__autoreleasing *)error;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDKeyPairStore.h"
|
||||
|
||||
#import "FIRInstanceIDBackupExcludedPlist.h"
|
||||
#import "FIRInstanceIDConstants.h"
|
||||
#import "FIRInstanceIDKeyPair.h"
|
||||
#import "FIRInstanceIDKeyPairUtilities.h"
|
||||
#import "FIRInstanceIDKeychain.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
// NOTE: These values should be in sync with what InstanceID saves in as.
|
||||
static NSString *const kFIRInstanceIDKeyPairStoreFileName = @"com.google.iid-keypair";
|
||||
|
||||
static NSString *const kFIRInstanceIDStoreKeyGenerationTime = @"cre";
|
||||
|
||||
static NSString *const kFIRInstanceIDStoreKeyPrefix = @"com.google.iid-";
|
||||
static NSString *const kFIRInstanceIDStoreKeyPublic = @"|P|";
|
||||
static NSString *const kFIRInstanceIDStoreKeyPrivate = @"|K|";
|
||||
static NSString *const kFIRInstanceIDStoreKeySubtype = @"|S|";
|
||||
|
||||
static NSString *const kFIRInstanceIDKeyPairPublicTagPrefix = @"com.google.iid.keypair.public-";
|
||||
static NSString *const kFIRInstanceIDKeyPairPrivateTagPrefix = @"com.google.iid.keypair.private-";
|
||||
|
||||
static const int kMaxMissingEntitlementErrorCount = 3;
|
||||
|
||||
NSString *const kFIRInstanceIDKeyPairSubType = @"";
|
||||
|
||||
// Query the key with NSData format
|
||||
NSData *FIRInstanceIDKeyDataWithTag(NSString *tag) {
|
||||
if (![tag length]) {
|
||||
return NULL;
|
||||
}
|
||||
NSDictionary *queryKey = FIRInstanceIDKeyPairQuery(tag, YES, YES);
|
||||
CFTypeRef result = [[FIRInstanceIDKeychain sharedInstance] itemWithQuery:queryKey];
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
return (__bridge NSData *)result;
|
||||
}
|
||||
|
||||
// Query the key given a tag
|
||||
SecKeyRef FIRInstanceIDCachedKeyRefWithTag(NSString *tag) {
|
||||
if (!tag.length) {
|
||||
return NULL;
|
||||
}
|
||||
NSDictionary *queryKey = FIRInstanceIDKeyPairQuery(tag, YES, NO);
|
||||
CFTypeRef result = [[FIRInstanceIDKeychain sharedInstance] itemWithQuery:queryKey];
|
||||
return (SecKeyRef)result;
|
||||
}
|
||||
|
||||
// Check if keypair has been migrated from the legacy to the new version
|
||||
BOOL FIRInstanceIDHasMigratedKeyPair(NSString *legacyPublicKeyTag, NSString *newPublicKeyTag) {
|
||||
NSData *oldPublicKeyData = FIRInstanceIDKeyDataWithTag(legacyPublicKeyTag);
|
||||
NSData *newPublicKeyData = FIRInstanceIDKeyDataWithTag(newPublicKeyTag);
|
||||
return [oldPublicKeyData isEqualToData:newPublicKeyData];
|
||||
}
|
||||
|
||||
// The legacy value is hardcoded to be the same key. This is a potential problem in shared keychain
|
||||
// environments.
|
||||
NSString *FIRInstanceIDLegacyPublicTagWithSubtype(NSString *subtype) {
|
||||
NSString *prefix = kFIRInstanceIDStoreKeyPrefix;
|
||||
return [NSString stringWithFormat:@"%@%@%@", prefix, subtype, kFIRInstanceIDStoreKeyPublic];
|
||||
}
|
||||
|
||||
// The legacy value is hardcoded to be the same key. This is a potential problem in shared keychain
|
||||
// environments.
|
||||
NSString *FIRInstanceIDLegacyPrivateTagWithSubtype(NSString *subtype) {
|
||||
NSString *prefix = kFIRInstanceIDStoreKeyPrefix;
|
||||
return [NSString stringWithFormat:@"%@%@%@", prefix, subtype, kFIRInstanceIDStoreKeyPrivate];
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDPublicTagWithSubtype(NSString *subtype) {
|
||||
static NSString *publicTag;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *mainAppBundleID = FIRInstanceIDAppIdentifier();
|
||||
publicTag =
|
||||
[NSString stringWithFormat:@"%@%@", kFIRInstanceIDKeyPairPublicTagPrefix, mainAppBundleID];
|
||||
});
|
||||
return publicTag;
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDPrivateTagWithSubtype(NSString *subtype) {
|
||||
static NSString *privateTag;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *mainAppBundleID = FIRInstanceIDAppIdentifier();
|
||||
privateTag =
|
||||
[NSString stringWithFormat:@"%@%@", kFIRInstanceIDKeyPairPrivateTagPrefix, mainAppBundleID];
|
||||
});
|
||||
return privateTag;
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDCreationTimeKeyWithSubtype(NSString *subtype) {
|
||||
return [NSString stringWithFormat:@"%@%@%@", subtype, kFIRInstanceIDStoreKeySubtype,
|
||||
kFIRInstanceIDStoreKeyGenerationTime];
|
||||
}
|
||||
|
||||
@interface FIRInstanceIDKeyPairStore ()
|
||||
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDBackupExcludedPlist *plist;
|
||||
@property(atomic, readwrite, strong) FIRInstanceIDKeyPair *keyPair;
|
||||
@property(nonatomic, readwrite, assign) NSInteger keychainEntitlementsErrorCount;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDKeyPairStore
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSString *fileName = [[self class] keyStoreFileName];
|
||||
_plist =
|
||||
[[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:fileName
|
||||
subDirectory:kFIRInstanceIDSubDirectoryName];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)invalidateKeyPairsIfNeeded {
|
||||
// Currently keypairs are always invalidated if self.plist is missing. This normally indicates
|
||||
// a fresh install (or an uninstall/reinstall). In those situations the key pairs should be
|
||||
// deleted.
|
||||
// NOTE: Although this class refers to multiple key pairs, with different subtypes, in practice
|
||||
// only a single subtype is currently supported. (b/64906549)
|
||||
if (![self.plist doesFileExist]) {
|
||||
// A fresh install, clear all the key pairs in the key chain. Do not perform migration as all
|
||||
// key pairs are gone.
|
||||
[self deleteSavedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType handler:nil];
|
||||
return YES;
|
||||
}
|
||||
// Not a fresh install, perform migration at early state.
|
||||
[self migrateKeyPairCacheIfNeededWithHandler:nil];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)hasCachedKeyPairs {
|
||||
NSError *error;
|
||||
if ([self cachedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType error:&error] == nil) {
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairStore000,
|
||||
@"Failed to get the cached keyPair %@", error);
|
||||
}
|
||||
error = nil;
|
||||
[self removeKeyPairCreationTimePlistWithError:&error];
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairStore001,
|
||||
@"Failed to remove keyPair creationTime plist %@", error);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)appIdentityWithError:(NSError *__autoreleasing *)error {
|
||||
// Load the keyPair from Keychain (or generate a key pair, if this is the first run of the app).
|
||||
FIRInstanceIDKeyPair *keyPair = [self loadKeyPairWithError:error];
|
||||
if (!keyPair) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStoreCouldNotLoadKeyPair,
|
||||
@"Keypair could not be loaded from Keychain. Error: %@", (*error));
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = nil;
|
||||
}
|
||||
NSString *appIdentity = FIRInstanceIDAppIdentity(keyPair);
|
||||
if (!appIdentity.length) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown];
|
||||
}
|
||||
}
|
||||
return appIdentity;
|
||||
}
|
||||
|
||||
- (FIRInstanceIDKeyPair *)loadKeyPairWithError:(NSError **)error {
|
||||
// In case we call this from different threads we don't want to generate or fetch the
|
||||
// keyPair multiple times. Once we have a keyPair in the cache it would mostly be used
|
||||
// from there.
|
||||
@synchronized(self) {
|
||||
if ([self.keyPair isValid]) {
|
||||
return self.keyPair;
|
||||
}
|
||||
|
||||
if (self.keychainEntitlementsErrorCount >= kMaxMissingEntitlementErrorCount) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairStore002,
|
||||
@"Keychain not accessible, Entitlements missing error (-34018). "
|
||||
@"Will not check token in cache.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (!self.keyPair) {
|
||||
self.keyPair = [self validCachedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType error:error];
|
||||
}
|
||||
|
||||
if ((*error).code == kFIRInstanceIDSecMissingEntitlementErrorCode) {
|
||||
self.keychainEntitlementsErrorCount++;
|
||||
}
|
||||
|
||||
if (!self.keyPair) {
|
||||
self.keyPair = [self generateAndSaveKeyWithSubtype:kFIRInstanceIDKeyPairSubType
|
||||
creationTime:FIRInstanceIDCurrentTimestampInSeconds()
|
||||
error:error];
|
||||
}
|
||||
}
|
||||
return self.keyPair;
|
||||
}
|
||||
|
||||
// TODO(chliangGoogle: Remove subtype support, as it's not being used.
|
||||
- (FIRInstanceIDKeyPair *)generateAndSaveKeyWithSubtype:(NSString *)subtype
|
||||
creationTime:(int64_t)creationTime
|
||||
error:(NSError **)error {
|
||||
NSString *publicKeyTag = FIRInstanceIDPublicTagWithSubtype(subtype);
|
||||
NSString *privateKeyTag = FIRInstanceIDPrivateTagWithSubtype(subtype);
|
||||
FIRInstanceIDKeyPair *keyPair =
|
||||
[[FIRInstanceIDKeychain sharedInstance] generateKeyPairWithPrivateTag:privateKeyTag
|
||||
publicTag:publicKeyTag];
|
||||
|
||||
if (![keyPair isValid]) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore003,
|
||||
@"Unable to generate keypair.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *creationTimeKey = FIRInstanceIDCreationTimeKeyWithSubtype(subtype);
|
||||
NSDictionary *keyPairData = @{creationTimeKey : @(creationTime)};
|
||||
|
||||
if (error) {
|
||||
*error = nil;
|
||||
}
|
||||
NSMutableDictionary *allKeyPairs = [[self.plist contentAsDictionary] mutableCopy];
|
||||
if (allKeyPairs.count) {
|
||||
[allKeyPairs addEntriesFromDictionary:keyPairData];
|
||||
} else {
|
||||
allKeyPairs = [keyPairData mutableCopy];
|
||||
}
|
||||
if (![self.plist writeDictionary:allKeyPairs error:error]) {
|
||||
[FIRInstanceIDKeyPairStore deleteKeyPairWithPrivateTag:privateKeyTag
|
||||
publicTag:publicKeyTag
|
||||
handler:nil];
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore004,
|
||||
@"Failed to save keypair data to plist %@", error ? *error : @"");
|
||||
return nil;
|
||||
}
|
||||
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
- (FIRInstanceIDKeyPair *)validCachedKeyPairWithSubtype:(NSString *)subtype
|
||||
error:(NSError **)error {
|
||||
// On a new install (or if the ID was deleted), the plist will be missing, which should trigger
|
||||
// a reset of the key pairs in Keychain (if they exist).
|
||||
NSDictionary *allKeyPairs = [self.plist contentAsDictionary];
|
||||
NSString *creationTimeKey = FIRInstanceIDCreationTimeKeyWithSubtype(subtype);
|
||||
|
||||
if (allKeyPairs[creationTimeKey] > 0) {
|
||||
return [self cachedKeyPairWithSubtype:subtype error:error];
|
||||
} else {
|
||||
// There is no need to reset keypair again here as FIRInstanceID init call is always
|
||||
// going to be ahead of this call, which already trigger keypair reset if it's new install
|
||||
FIRInstanceIDErrorCode code = kFIRInstanceIDErrorCodeInvalidKeyPairCreationTime;
|
||||
if (error) {
|
||||
*error = [NSError errorWithFIRInstanceIDErrorCode:code];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (FIRInstanceIDKeyPair *)cachedKeyPairWithSubtype:(NSString *)subtype
|
||||
error:(NSError *__autoreleasing *)error {
|
||||
// base64 encoded keys
|
||||
NSString *publicKeyTag = FIRInstanceIDPublicTagWithSubtype(subtype);
|
||||
NSString *privateKeyTag = FIRInstanceIDPrivateTagWithSubtype(subtype);
|
||||
return [FIRInstanceIDKeyPairStore keyPairForPrivateKeyTag:privateKeyTag
|
||||
publicKeyTag:publicKeyTag
|
||||
error:error];
|
||||
}
|
||||
|
||||
+ (FIRInstanceIDKeyPair *)keyPairForPrivateKeyTag:(NSString *)privateKeyTag
|
||||
publicKeyTag:(NSString *)publicKeyTag
|
||||
error:(NSError *__autoreleasing *)error {
|
||||
if (![privateKeyTag length] || ![publicKeyTag length]) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidKeyPairTags];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
SecKeyRef privateKeyRef = FIRInstanceIDCachedKeyRefWithTag(privateKeyTag);
|
||||
SecKeyRef publicKeyRef = FIRInstanceIDCachedKeyRefWithTag(publicKeyTag);
|
||||
|
||||
if (!privateKeyRef || !publicKeyRef) {
|
||||
if (error) {
|
||||
*error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeMissingKeyPair];
|
||||
}
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPair000,
|
||||
@"No keypair info is found with tag %@", privateKeyTag);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *publicKeyData = FIRInstanceIDKeyDataWithTag(publicKeyTag);
|
||||
NSData *privateKeyData = FIRInstanceIDKeyDataWithTag(privateKeyTag);
|
||||
|
||||
FIRInstanceIDKeyPair *keyPair = [[FIRInstanceIDKeyPair alloc] initWithPrivateKey:privateKeyRef
|
||||
publicKey:publicKeyRef
|
||||
publicKeyData:publicKeyData
|
||||
privateKeyData:privateKeyData];
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
// Migrates from keypair saved under legacy keys (hardcoded value) to dynamic keys (stable, but
|
||||
// unique for the app's bundle id
|
||||
- (void)migrateKeyPairCacheIfNeededWithHandler:(void (^)(NSError *error))handler {
|
||||
// Attempt to load keypair using legacy keys
|
||||
NSString *legacyPublicKeyTag =
|
||||
FIRInstanceIDLegacyPublicTagWithSubtype(kFIRInstanceIDKeyPairSubType);
|
||||
NSString *legacyPrivateKeyTag =
|
||||
FIRInstanceIDLegacyPrivateTagWithSubtype(kFIRInstanceIDKeyPairSubType);
|
||||
NSError *error;
|
||||
FIRInstanceIDKeyPair *keyPair =
|
||||
[FIRInstanceIDKeyPairStore keyPairForPrivateKeyTag:legacyPrivateKeyTag
|
||||
publicKeyTag:legacyPublicKeyTag
|
||||
error:&error];
|
||||
if (![keyPair isValid]) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeKeyPairNoLegacyKeyPair,
|
||||
@"There's no legacy keypair so no need to do migration.");
|
||||
if (handler) {
|
||||
handler(nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether migration already done.
|
||||
NSString *publicKeyTag = FIRInstanceIDPublicTagWithSubtype(kFIRInstanceIDKeyPairSubType);
|
||||
if (FIRInstanceIDHasMigratedKeyPair(legacyPublicKeyTag, publicKeyTag)) {
|
||||
if (handler) {
|
||||
handler(nil);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Also cache locally since we are sure to use the migrated key pair.
|
||||
self.keyPair = keyPair;
|
||||
|
||||
// Either new key pair doesn't exist or it's different than legacy key pair, start the migration.
|
||||
__block NSError *updateKeyRefError;
|
||||
|
||||
NSString *privateKeyTag = FIRInstanceIDPrivateTagWithSubtype(kFIRInstanceIDKeyPairSubType);
|
||||
[self updateKeyRef:keyPair.publicKey
|
||||
withTag:publicKeyTag
|
||||
handler:^(NSError *error) {
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairMigrationError,
|
||||
@"Unable to migrate key pair from legacy ones.");
|
||||
updateKeyRefError = error;
|
||||
}
|
||||
}];
|
||||
|
||||
[self updateKeyRef:keyPair.privateKey
|
||||
withTag:privateKeyTag
|
||||
handler:^(NSError *error) {
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairMigrationError,
|
||||
@"Unable to migrate key pair from legacy ones.");
|
||||
updateKeyRefError = error;
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
handler(updateKeyRefError);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
// Used for migrating from legacy tags to updated tags. The legacy keychain is not deleted for
|
||||
// backward compatibility.
|
||||
// TODO(chliangGoogle) Delete the legacy keychain when GCM is fully deprecated.
|
||||
- (void)updateKeyRef:(SecKeyRef)keyRef
|
||||
withTag:(NSString *)tag
|
||||
handler:(void (^)(NSError *error))handler {
|
||||
NSData *updatedTagData = [tag dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
__block NSError *keychainError;
|
||||
|
||||
// Always delete the old keychain before adding a new one to avoid conflicts.
|
||||
NSDictionary *deleteQuery = @{
|
||||
(__bridge id)kSecAttrApplicationTag : updatedTagData,
|
||||
(__bridge id)kSecClass : (__bridge id)kSecClassKey,
|
||||
(__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeRSA,
|
||||
(__bridge id)kSecReturnRef : @(YES),
|
||||
};
|
||||
[[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:deleteQuery
|
||||
handler:^(NSError *error) {
|
||||
if (error) {
|
||||
keychainError = error;
|
||||
}
|
||||
}];
|
||||
|
||||
NSDictionary *addQuery = @{
|
||||
(__bridge id)kSecAttrApplicationTag : updatedTagData,
|
||||
(__bridge id)kSecClass : (__bridge id)kSecClassKey,
|
||||
(__bridge id)kSecValueRef : (__bridge id)keyRef,
|
||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly,
|
||||
};
|
||||
[[FIRInstanceIDKeychain sharedInstance] addItemWithQuery:addQuery
|
||||
handler:^(NSError *addError) {
|
||||
if (addError) {
|
||||
keychainError = addError;
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
handler(keychainError);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)deleteSavedKeyPairWithSubtype:(NSString *)subtype
|
||||
handler:(void (^)(NSError *error))handler {
|
||||
NSDictionary *allKeyPairs = [self.plist contentAsDictionary];
|
||||
|
||||
NSString *publicKeyTag = FIRInstanceIDPublicTagWithSubtype(subtype);
|
||||
NSString *privateKeyTag = FIRInstanceIDPrivateTagWithSubtype(subtype);
|
||||
NSString *creationTimeKey = FIRInstanceIDCreationTimeKeyWithSubtype(subtype);
|
||||
|
||||
// remove the creation time
|
||||
if (allKeyPairs[creationTimeKey] > 0) {
|
||||
NSMutableDictionary *newKeyPairs = [NSMutableDictionary dictionaryWithDictionary:allKeyPairs];
|
||||
[newKeyPairs removeObjectForKey:creationTimeKey];
|
||||
|
||||
NSError *plistError;
|
||||
if (![self.plist writeDictionary:newKeyPairs error:&plistError]) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore006,
|
||||
@"Unable to remove keypair creation time from plist %@", plistError);
|
||||
}
|
||||
}
|
||||
|
||||
self.keyPair = nil;
|
||||
|
||||
[FIRInstanceIDKeyPairStore
|
||||
deleteKeyPairWithPrivateTag:privateKeyTag
|
||||
publicTag:publicKeyTag
|
||||
handler:^(NSError *error) {
|
||||
// Delete legacy key pairs from GCM/FCM If they exist. All key pairs
|
||||
// should be deleted when app is newly installed.
|
||||
NSString *legacyPublicKeyTag =
|
||||
FIRInstanceIDLegacyPublicTagWithSubtype(subtype);
|
||||
NSString *legacyPrivateKeyTag =
|
||||
FIRInstanceIDLegacyPrivateTagWithSubtype(subtype);
|
||||
[FIRInstanceIDKeyPairStore
|
||||
deleteKeyPairWithPrivateTag:legacyPrivateKeyTag
|
||||
publicTag:legacyPublicKeyTag
|
||||
handler:nil];
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore007,
|
||||
@"Unable to remove RSA keypair, error: %@",
|
||||
error);
|
||||
if (handler) {
|
||||
handler(error);
|
||||
}
|
||||
} else {
|
||||
if (handler) {
|
||||
handler(nil);
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)deleteKeyPairWithPrivateTag:(NSString *)privateTag
|
||||
publicTag:(NSString *)publicTag
|
||||
handler:(void (^)(NSError *))handler {
|
||||
NSDictionary *queryPublicKey = FIRInstanceIDKeyPairQuery(publicTag, NO, NO);
|
||||
NSDictionary *queryPrivateKey = FIRInstanceIDKeyPairQuery(privateTag, NO, NO);
|
||||
|
||||
__block NSError *keychainError;
|
||||
|
||||
// Always remove public key first because it is the key we generate IID.
|
||||
[[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:queryPublicKey
|
||||
handler:^(NSError *error) {
|
||||
if (error) {
|
||||
keychainError = error;
|
||||
}
|
||||
}];
|
||||
|
||||
[[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:queryPrivateKey
|
||||
handler:^(NSError *error) {
|
||||
if (error) {
|
||||
keychainError = error;
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
handler(keychainError);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)removeKeyPairCreationTimePlistWithError:(NSError *__autoreleasing *)error {
|
||||
if (![self.plist deleteFile:error]) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPairStore008,
|
||||
@"Unable to delete keypair creation times plist");
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)keyStoreFileName {
|
||||
return kFIRInstanceIDKeyPairStoreFileName;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class FIRInstanceIDKeyPair;
|
||||
|
||||
/**
|
||||
* A web-safe base64 encoded string with no padding.
|
||||
*
|
||||
* @param data The data to encode.
|
||||
*
|
||||
* @return A web-safe base 64 encoded string with no padding.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDWebSafeBase64(NSData *data);
|
||||
|
||||
FOUNDATION_EXPORT NSData *FIRInstanceIDSHA1(NSData *data);
|
||||
|
||||
FOUNDATION_EXPORT NSDictionary *FIRInstanceIDKeyPairQuery(NSString *tag,
|
||||
BOOL addReturnAttr,
|
||||
BOOL returnData);
|
||||
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDAppIdentity(FIRInstanceIDKeyPair *keyPair);
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDKeyPairUtilities.h"
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
#import "FIRInstanceIDKeyPair.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDStringEncoding.h"
|
||||
|
||||
NSString *FIRInstanceIDWebSafeBase64(NSData *data) {
|
||||
// Websafe encoding with no padding.
|
||||
FIRInstanceIDStringEncoding *encoding =
|
||||
[FIRInstanceIDStringEncoding rfc4648Base64WebsafeStringEncoding];
|
||||
[encoding setDoPad:NO];
|
||||
return [encoding encode:data];
|
||||
}
|
||||
|
||||
NSData *FIRInstanceIDSHA1(NSData *data) {
|
||||
unsigned int outputLength = CC_SHA1_DIGEST_LENGTH;
|
||||
unsigned char output[outputLength];
|
||||
unsigned int length = (unsigned int)[data length];
|
||||
|
||||
CC_SHA1(data.bytes, length, output);
|
||||
return [NSMutableData dataWithBytes:output length:outputLength];
|
||||
}
|
||||
|
||||
NSDictionary *FIRInstanceIDKeyPairQuery(NSString *tag, BOOL addReturnAttr, BOOL returnData) {
|
||||
NSMutableDictionary *queryKey = [NSMutableDictionary dictionary];
|
||||
NSData *tagData = [tag dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
queryKey[(__bridge id)kSecClass] = (__bridge id)kSecClassKey;
|
||||
queryKey[(__bridge id)kSecAttrApplicationTag] = tagData;
|
||||
queryKey[(__bridge id)kSecAttrKeyType] = (__bridge id)kSecAttrKeyTypeRSA;
|
||||
if (addReturnAttr) {
|
||||
if (returnData) {
|
||||
queryKey[(__bridge id)kSecReturnData] = @(YES);
|
||||
} else {
|
||||
queryKey[(__bridge id)kSecReturnRef] = @(YES);
|
||||
}
|
||||
}
|
||||
return queryKey;
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDAppIdentity(FIRInstanceIDKeyPair *keyPair) {
|
||||
// An Instance-ID is a 64 bit (8 byte) integer with a fixed 4-bit header of 0111 (=^ 0x7).
|
||||
// The variable 60 bits are obtained by truncating the SHA1 of the app-instance's public key.
|
||||
SecKeyRef publicKeyRef = [keyPair publicKey];
|
||||
if (!publicKeyRef) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeKeyPair002,
|
||||
@"Unable to create a valid asymmetric crypto key");
|
||||
return nil;
|
||||
}
|
||||
NSData *publicKeyData = keyPair.publicKeyData;
|
||||
NSData *publicKeySHA1 = FIRInstanceIDSHA1(publicKeyData);
|
||||
|
||||
const uint8_t *bytes = publicKeySHA1.bytes;
|
||||
NSMutableData *identityData = [NSMutableData dataWithData:publicKeySHA1];
|
||||
|
||||
uint8_t b0 = bytes[0];
|
||||
// Take the first byte and make the initial four 7 by initially making the initial 4 bits 0
|
||||
// and then adding 0x70 to it.
|
||||
b0 = 0x70 + (0xF & b0);
|
||||
// failsafe should give you back b0 itself
|
||||
b0 = (b0 & 0xFF);
|
||||
[identityData replaceBytesInRange:NSMakeRange(0, 1) withBytes:&b0];
|
||||
NSData *data = [identityData subdataWithRange:NSMakeRange(0, 8 * sizeof(Byte))];
|
||||
return FIRInstanceIDWebSafeBase64(data);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
/* The Keychain error domain */
|
||||
extern NSString *const kFIRInstanceIDKeychainErrorDomain;
|
||||
|
||||
@class FIRInstanceIDKeyPair;
|
||||
|
||||
/*
|
||||
* Wrapping the keychain operations in a serialize queue. This is to avoid keychain operation
|
||||
* blocking main queue.
|
||||
*/
|
||||
@interface FIRInstanceIDKeychain : NSObject
|
||||
|
||||
/**
|
||||
* FIRInstanceIDKeychain.
|
||||
*
|
||||
* @return A shared instance of FIRInstanceIDKeychain.
|
||||
*/
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
* Get keychain items matching the given a query.
|
||||
*
|
||||
* @param keychainQuery The keychain query.
|
||||
*
|
||||
* @return An CFTypeRef result matching the provided inputs.
|
||||
*/
|
||||
- (CFTypeRef)itemWithQuery:(NSDictionary *)keychainQuery;
|
||||
|
||||
/**
|
||||
* Remove the cached items from the keychain matching the query.
|
||||
*
|
||||
* @param keychainQuery The keychain query.
|
||||
* @param handler The callback handler which is invoked when the remove operation is
|
||||
* complete, with an error if there is any.
|
||||
*/
|
||||
- (void)removeItemWithQuery:(NSDictionary *)keychainQuery handler:(void (^)(NSError *error))handler;
|
||||
|
||||
/**
|
||||
* Add the item with a given query.
|
||||
*
|
||||
* @param keychainQuery The keychain query.
|
||||
* @param handler The callback handler which is invoked when the add operation is
|
||||
* complete, with an error if there is any.
|
||||
*/
|
||||
- (void)addItemWithQuery:(NSDictionary *)keychainQuery handler:(void (^)(NSError *))handler;
|
||||
|
||||
#pragma mark - Keypair
|
||||
/**
|
||||
* Generate a public/private key pair given their tags.
|
||||
*
|
||||
* @param privateTag The private tag associated with the private key.
|
||||
* @param publicTag The public tag associated with the public key.
|
||||
*
|
||||
* @return A new FIRInstanceIDKeyPair object.
|
||||
*/
|
||||
- (FIRInstanceIDKeyPair *)generateKeyPairWithPrivateTag:(NSString *)privateTag
|
||||
publicTag:(NSString *)publicTag;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDKeychain.h"
|
||||
|
||||
#import "FIRInstanceIDKeyPair.h"
|
||||
#import "FIRInstanceIDKeyPairUtilities.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
|
||||
NSString *const kFIRInstanceIDKeychainErrorDomain = @"com.google.iid";
|
||||
|
||||
static const NSUInteger kRSA2048KeyPairSize = 2048;
|
||||
|
||||
@interface FIRInstanceIDKeychain () {
|
||||
dispatch_queue_t _keychainOperationQueue;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDKeychain
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static FIRInstanceIDKeychain *sharedInstance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[FIRInstanceIDKeychain alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_keychainOperationQueue =
|
||||
dispatch_queue_create("com.google.FirebaseInstanceID.Keychain", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CFTypeRef)itemWithQuery:(NSDictionary *)keychainQuery {
|
||||
__block SecKeyRef keyRef = NULL;
|
||||
dispatch_sync(_keychainOperationQueue, ^{
|
||||
OSStatus status =
|
||||
SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyRef);
|
||||
|
||||
if (status != noErr) {
|
||||
if (keyRef) {
|
||||
CFRelease(keyRef);
|
||||
}
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDKeychainReadItemError,
|
||||
@"Info is not found in Keychain. OSStatus: %d. Keychain query: %@",
|
||||
(int)status, keychainQuery);
|
||||
}
|
||||
});
|
||||
return keyRef;
|
||||
}
|
||||
|
||||
- (void)removeItemWithQuery:(NSDictionary *)keychainQuery
|
||||
handler:(void (^)(NSError *error))handler {
|
||||
dispatch_async(_keychainOperationQueue, ^{
|
||||
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
|
||||
if (status != noErr) {
|
||||
FIRInstanceIDLoggerDebug(
|
||||
kFIRInstanceIDKeychainDeleteItemError,
|
||||
@"Couldn't delete item from Keychain OSStatus: %d with the keychain query %@",
|
||||
(int)status, keychainQuery);
|
||||
}
|
||||
|
||||
if (handler) {
|
||||
NSError *error;
|
||||
// When item is not found, it should NOT be considered as an error. The operation should
|
||||
// continue.
|
||||
if (status != noErr && status != errSecItemNotFound) {
|
||||
error = [NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain
|
||||
code:status
|
||||
userInfo:nil];
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
handler(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)addItemWithQuery:(NSDictionary *)keychainQuery handler:(void (^)(NSError *))handler {
|
||||
dispatch_async(_keychainOperationQueue, ^{
|
||||
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
|
||||
|
||||
if (handler) {
|
||||
NSError *error;
|
||||
if (status != noErr) {
|
||||
FIRInstanceIDLoggerWarning(kFIRInstanceIDKeychainAddItemError,
|
||||
@"Couldn't add item to Keychain OSStatus: %d", (int)status);
|
||||
error = [NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain
|
||||
code:status
|
||||
userInfo:nil];
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
handler(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (FIRInstanceIDKeyPair *)generateKeyPairWithPrivateTag:(NSString *)privateTag
|
||||
publicTag:(NSString *)publicTag {
|
||||
// TODO(chliangGoogle) this is called by appInstanceID, which is an internal API used by other
|
||||
// Firebase teams, will see if we can make it async.
|
||||
NSData *publicTagData = [publicTag dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *privateTagData = [privateTag dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSDictionary *privateKeyAttr = @{
|
||||
(__bridge id)kSecAttrIsPermanent : @YES,
|
||||
(__bridge id)kSecAttrApplicationTag : privateTagData,
|
||||
(__bridge id)kSecAttrLabel : @"Firebase InstanceID Key Pair Private Key",
|
||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly,
|
||||
};
|
||||
|
||||
NSDictionary *publicKeyAttr = @{
|
||||
(__bridge id)kSecAttrIsPermanent : @YES,
|
||||
(__bridge id)kSecAttrApplicationTag : publicTagData,
|
||||
(__bridge id)kSecAttrLabel : @"Firebase InstanceID Key Pair Public Key",
|
||||
(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly,
|
||||
};
|
||||
|
||||
NSDictionary *keyPairAttributes = @{
|
||||
(__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeRSA,
|
||||
(__bridge id)kSecAttrLabel : @"Firebase InstanceID Key Pair",
|
||||
(__bridge id)kSecAttrKeySizeInBits : @(kRSA2048KeyPairSize),
|
||||
(__bridge id)kSecPrivateKeyAttrs : privateKeyAttr,
|
||||
(__bridge id)kSecPublicKeyAttrs : publicKeyAttr,
|
||||
};
|
||||
|
||||
__block SecKeyRef privateKey = NULL;
|
||||
__block SecKeyRef publicKey = NULL;
|
||||
dispatch_sync(_keychainOperationQueue, ^{
|
||||
// SecKeyGeneratePair does not allow you to set kSetAttrAccessible on the keys. We need the keys
|
||||
// to be accessible even when the device is locked (i.e. app is woken up during a push
|
||||
// notification, or some background refresh).
|
||||
OSStatus status =
|
||||
SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttributes, &publicKey, &privateKey);
|
||||
if (status != noErr || publicKey == NULL || privateKey == NULL) {
|
||||
FIRInstanceIDLoggerWarning(kFIRInstanceIDKeychainCreateKeyPairError,
|
||||
@"Couldn't create keypair from Keychain OSStatus: %d",
|
||||
(int)status);
|
||||
}
|
||||
});
|
||||
// Extract the actual public and private key data from the Keychain
|
||||
NSDictionary *publicKeyDataQuery = FIRInstanceIDKeyPairQuery(publicTag, YES, YES);
|
||||
NSDictionary *privateKeyDataQuery = FIRInstanceIDKeyPairQuery(privateTag, YES, YES);
|
||||
|
||||
NSData *publicKeyData = (__bridge NSData *)[self itemWithQuery:publicKeyDataQuery];
|
||||
NSData *privateKeyData = (__bridge NSData *)[self itemWithQuery:privateKeyDataQuery];
|
||||
|
||||
return [[FIRInstanceIDKeyPair alloc] initWithPrivateKey:privateKey
|
||||
publicKey:publicKey
|
||||
publicKeyData:publicKeyData
|
||||
privateKeyData:privateKeyData];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 "FIRIMessageCode.h"
|
||||
|
||||
// The convenience macros are only defined if they haven't already been defined.
|
||||
#ifndef FIRInstanceIDLoggerInfo
|
||||
|
||||
// Convenience macros that log to the shared GTMLogger instance. These macros
|
||||
// are how users should typically log to FIRInstanceIDLogger.
|
||||
#define FIRInstanceIDLoggerDebug(code, ...) \
|
||||
[FIRInstanceIDSharedLogger() logFuncDebug:__func__ messageCode:code msg:__VA_ARGS__]
|
||||
#define FIRInstanceIDLoggerInfo(code, ...) \
|
||||
[FIRInstanceIDSharedLogger() logFuncInfo:__func__ messageCode:code msg:__VA_ARGS__]
|
||||
#define FIRInstanceIDLoggerNotice(code, ...) \
|
||||
[FIRInstanceIDSharedLogger() logFuncNotice:__func__ messageCode:code msg:__VA_ARGS__]
|
||||
#define FIRInstanceIDLoggerWarning(code, ...) \
|
||||
[FIRInstanceIDSharedLogger() logFuncWarning:__func__ messageCode:code msg:__VA_ARGS__]
|
||||
#define FIRInstanceIDLoggerError(code, ...) \
|
||||
[FIRInstanceIDSharedLogger() logFuncError:__func__ messageCode:code msg:__VA_ARGS__]
|
||||
|
||||
#endif // !defined(FIRInstanceIDLoggerInfo)
|
||||
|
||||
@interface FIRInstanceIDLogger : NSObject
|
||||
|
||||
- (void)logFuncDebug:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
|
||||
|
||||
- (void)logFuncInfo:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
|
||||
|
||||
- (void)logFuncNotice:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
|
||||
|
||||
- (void)logFuncWarning:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
|
||||
|
||||
- (void)logFuncError:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... NS_FORMAT_FUNCTION(3, 4);
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Instantiates and/or returns a shared GTMLogger used exclusively
|
||||
* for InstanceID log messages.
|
||||
* @return the shared GTMLogger instance
|
||||
*/
|
||||
FIRInstanceIDLogger *FIRInstanceIDSharedLogger(void);
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDLogger.h"
|
||||
|
||||
#import <FirebaseCore/FIRLogger.h>
|
||||
|
||||
// Re-definition of FIRLogger service, as it is not included in :FIRAppHeaders target
|
||||
NSString *const kFIRInstanceIDLoggerService = @"[Firebase/InstanceID]";
|
||||
|
||||
@implementation FIRInstanceIDLogger
|
||||
|
||||
#pragma mark - Log Helpers
|
||||
|
||||
+ (NSString *)formatMessageCode:(FIRInstanceIDMessageCode)messageCode {
|
||||
return [NSString stringWithFormat:@"I-IID%06ld", (long)messageCode];
|
||||
}
|
||||
|
||||
- (void)logFuncDebug:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
FIRLogBasic(FIRLoggerLevelDebug, kFIRInstanceIDLoggerService,
|
||||
[FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncInfo:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
FIRLogBasic(FIRLoggerLevelInfo, kFIRInstanceIDLoggerService,
|
||||
[FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncNotice:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
FIRLogBasic(FIRLoggerLevelNotice, kFIRInstanceIDLoggerService,
|
||||
[FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncWarning:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
FIRLogBasic(FIRLoggerLevelWarning, kFIRInstanceIDLoggerService,
|
||||
[FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncError:(const char *)func
|
||||
messageCode:(FIRInstanceIDMessageCode)messageCode
|
||||
msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
FIRLogBasic(FIRLoggerLevelError, kFIRInstanceIDLoggerService,
|
||||
[FIRInstanceIDLogger formatMessageCode:messageCode], fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
FIRInstanceIDLogger *FIRInstanceIDSharedLogger() {
|
||||
static dispatch_once_t onceToken;
|
||||
static FIRInstanceIDLogger *logger;
|
||||
dispatch_once(&onceToken, ^{
|
||||
logger = [[FIRInstanceIDLogger alloc] init];
|
||||
});
|
||||
|
||||
return logger;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class FIRInstanceIDBackupExcludedPlist;
|
||||
@class FIRInstanceIDCheckinPreferences;
|
||||
@class FIRInstanceIDCheckinStore;
|
||||
@class FIRInstanceIDTokenInfo;
|
||||
@class FIRInstanceIDTokenStore;
|
||||
|
||||
@class FIRInstanceIDStore;
|
||||
@protocol FIRInstanceIDStoreDelegate <NSObject>
|
||||
|
||||
/**
|
||||
* This is called when the store has decided to invalide its tokens associated with the
|
||||
* previous checkin credentials. After deleting the tokens locally, it calls this method
|
||||
* to notify the delegate of the change. If possible, the delegate should use this time
|
||||
* to request the invalidation of the tokens on the server as well.
|
||||
*/
|
||||
- (void)store:(FIRInstanceIDStore *)store
|
||||
didDeleteFCMScopedTokensForCheckin:(FIRInstanceIDCheckinPreferences *)checkin;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Used to persist the InstanceID tokens. This is also used to cache the Checkin
|
||||
* credentials. The store also checks for stale entries in the store and
|
||||
* let's us know if things in the store are stale or not. It does not however
|
||||
* acts on stale entries in anyway.
|
||||
*/
|
||||
@interface FIRInstanceIDStore : NSObject
|
||||
|
||||
/**
|
||||
* The delegate set in the initializer which is notified of changes in the store.
|
||||
*/
|
||||
@property(nonatomic, readonly, weak) NSObject<FIRInstanceIDStoreDelegate> *delegate;
|
||||
|
||||
- (instancetype)init __attribute__((unavailable("Use initWithDelegate: instead.")));
|
||||
|
||||
/**
|
||||
* Initialize a default store to persist InstanceID tokens and options.
|
||||
*
|
||||
* @param delegate The delegate with which to be notified of changes in the store.
|
||||
* @return Store to persist InstanceID tokens.
|
||||
*/
|
||||
- (instancetype)initWithDelegate:(NSObject<FIRInstanceIDStoreDelegate> *)delegate;
|
||||
|
||||
/**
|
||||
* Initialize a store with the token store used to persist tokens, and a checkin store.
|
||||
* Used for testing.
|
||||
*
|
||||
* @param checkinStore Persistent store that persists checkin preferences.
|
||||
* @param tokenStore Persistent store that persists tokens.
|
||||
*
|
||||
* @return Store to persist InstanceID tokens and options.
|
||||
*/
|
||||
- (instancetype)initWithCheckinStore:(FIRInstanceIDCheckinStore *)checkinStore
|
||||
tokenStore:(FIRInstanceIDTokenStore *)tokenStore
|
||||
delegate:(NSObject<FIRInstanceIDStoreDelegate> *)delegate
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
#pragma mark - Save
|
||||
/**
|
||||
* Save the instanceID token info to the store.
|
||||
*
|
||||
* @param tokenInfo The token info to store.
|
||||
* @param handler The callback handler which is invoked when the operation is complete,
|
||||
* with an error if there is any.
|
||||
*/
|
||||
- (void)saveTokenInfo:(FIRInstanceIDTokenInfo *)tokenInfo handler:(void (^)(NSError *))handler;
|
||||
|
||||
#pragma mark - Get
|
||||
|
||||
/**
|
||||
* Get the cached token info.
|
||||
*
|
||||
* @param authorizedEntity The authorized entity for which we want the token.
|
||||
* @param scope The scope for which we want the token.
|
||||
*
|
||||
* @return The cached token info if any for the given authorizedEntity and scope else
|
||||
* returns nil.
|
||||
*/
|
||||
- (nullable FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope;
|
||||
/**
|
||||
* Return all cached token infos from the Keychain.
|
||||
*
|
||||
* @return The cached token infos, if any, that are stored in the Keychain.
|
||||
*/
|
||||
- (NSArray<FIRInstanceIDTokenInfo *> *)cachedTokenInfos;
|
||||
|
||||
#pragma mark - Delete
|
||||
|
||||
/**
|
||||
* Remove the cached token for a given authorizedEntity and scope. If the token was never
|
||||
* cached or deleted from the cache before this is a no-op.
|
||||
*
|
||||
* @param authorizedEntity The authorizedEntity for the cached token.
|
||||
* @param scope The scope for the cached token
|
||||
*/
|
||||
- (void)removeCachedTokenWithAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope;
|
||||
|
||||
/**
|
||||
* Removes all cached tokens from the persistent store. In case deleting the cached tokens
|
||||
* fails we try to delete the backup excluded plist that stores the tokens.
|
||||
*
|
||||
* @param handler The callback handler which is invoked when the operation is complete,
|
||||
* with an error if there is any.
|
||||
*
|
||||
*/
|
||||
- (void)removeAllCachedTokensWithHandler:(nullable void (^)(NSError *error))handler;
|
||||
|
||||
#pragma mark - Persisting Checkin Preferences
|
||||
|
||||
/**
|
||||
* Save the checkin preferences
|
||||
*
|
||||
* @param preferences Checkin preferences to save.
|
||||
* @param handler The callback handler which is invoked when the operation is complete,
|
||||
* with an error if there is any.
|
||||
*/
|
||||
- (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences
|
||||
handler:(nullable void (^)(NSError *error))handler;
|
||||
|
||||
/**
|
||||
* Return the cached checkin preferences.
|
||||
*
|
||||
* @return Checkin preferences.
|
||||
*/
|
||||
- (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences;
|
||||
|
||||
/**
|
||||
* Remove the cached checkin preferences from the store.
|
||||
*
|
||||
* @param handler The callback handler which is invoked when the operation is complete,
|
||||
* with an error if there is any.
|
||||
*/
|
||||
- (void)removeCheckinPreferencesWithHandler:(nullable void (^)(NSError *error))handler;
|
||||
|
||||
#pragma mark - Standard Directory sub-directory
|
||||
|
||||
/**
|
||||
* Check if supported directory has InstanceID subdirectory
|
||||
*
|
||||
* @return YES if the Application Support directory has InstanceID subdirectory else NO.
|
||||
*/
|
||||
+ (BOOL)hasSubDirectory:(NSString *)subDirectoryName;
|
||||
|
||||
/**
|
||||
* Create InstanceID subdirectory in Application support directory.
|
||||
*
|
||||
* @return YES if the subdirectory was created successfully else NO.
|
||||
*/
|
||||
+ (BOOL)createSubDirectory:(NSString *)subDirectoryName;
|
||||
|
||||
/**
|
||||
* Removes Application Support subdirectory for InstanceID.
|
||||
*
|
||||
* @param error The error object if any while trying to delete the sub-directory.
|
||||
*
|
||||
* @return YES if the deletion was successful else NO.
|
||||
*/
|
||||
+ (BOOL)removeSubDirectory:(NSString *)subDirectoryName error:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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) {
|
||||
// FCM user can still have the old version of checkin, migration should only happen once.
|
||||
[self.checkinStore migrateCheckinItemIfNeeded];
|
||||
return;
|
||||
}
|
||||
|
||||
// reset checkin in keychain if a fresh install.
|
||||
// set the old checkin preferences to unregister pre-registered tokens
|
||||
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
|
||||
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// GTMStringEncoding.h
|
||||
//
|
||||
// Copyright 2010 Google Inc.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// This is a copy of GTMStringEncoding. FIRInstanceID wants to avoid
|
||||
// a CocoaPods GTM dependency. Hence we use our own version of StringEncoding.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// A generic class for arbitrary base-2 to 128 string encoding and decoding.
|
||||
@interface FIRInstanceIDStringEncoding : NSObject {
|
||||
@private
|
||||
NSData *charMapData_;
|
||||
char *charMap_;
|
||||
int reverseCharMap_[128];
|
||||
int shift_;
|
||||
unsigned int mask_;
|
||||
BOOL doPad_;
|
||||
char paddingChar_;
|
||||
int padLen_;
|
||||
}
|
||||
|
||||
+ (id)rfc4648Base64WebsafeStringEncoding;
|
||||
|
||||
// Create a new, autoreleased GTMStringEncoding object with the given string,
|
||||
// as described below.
|
||||
+ (id)stringEncodingWithString:(NSString *)string;
|
||||
|
||||
// Initialize a new GTMStringEncoding object with the string.
|
||||
//
|
||||
// The length of the string must be a power of 2, at least 2 and at most 128.
|
||||
// Only 7-bit ASCII characters are permitted in the string.
|
||||
//
|
||||
// These characters are the canonical set emitted during encoding.
|
||||
// If the characters have alternatives (e.g. case, easily transposed) then use
|
||||
// addDecodeSynonyms: to configure them.
|
||||
- (id)initWithString:(NSString *)string;
|
||||
|
||||
// Indicates whether padding is performed during encoding.
|
||||
- (BOOL)doPad;
|
||||
- (void)setDoPad:(BOOL)doPad;
|
||||
|
||||
// Sets the padding character to use during encoding.
|
||||
- (void)setPaddingChar:(char)c;
|
||||
|
||||
// Encode a raw binary buffer to a 7-bit ASCII string.
|
||||
- (NSString *)encode:(NSData *)data;
|
||||
|
||||
// Decode a 7-bit ASCII string to a raw binary buffer.
|
||||
- (NSData *)decode:(NSString *)string;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// FIRInstanceIDStringEncoding.m
|
||||
//
|
||||
// Copyright 2009 Google Inc.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// This is a copy of GTMStringEncoding. FIRInstanceID wants to avoid
|
||||
// a CocoaPods GTM dependency. Hence we use our own version of StringEncoding.
|
||||
|
||||
#import "FIRInstanceIDStringEncoding.h"
|
||||
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
|
||||
enum { kUnknownChar = -1, kPaddingChar = -2, kIgnoreChar = -3 };
|
||||
|
||||
@implementation FIRInstanceIDStringEncoding
|
||||
|
||||
+ (id)rfc4648Base64WebsafeStringEncoding {
|
||||
FIRInstanceIDStringEncoding *ret = [self
|
||||
stringEncodingWithString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"];
|
||||
|
||||
[ret setPaddingChar:'='];
|
||||
[ret setDoPad:YES];
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int lcm(int a, int b) {
|
||||
for (int aa = a, bb = b;;) {
|
||||
if (aa == bb)
|
||||
return aa;
|
||||
else if (aa < bb)
|
||||
aa += a;
|
||||
else
|
||||
bb += b;
|
||||
}
|
||||
}
|
||||
|
||||
+ (id)stringEncodingWithString:(NSString *)string {
|
||||
return [[FIRInstanceIDStringEncoding alloc] initWithString:string];
|
||||
}
|
||||
|
||||
- (id)initWithString:(NSString *)string {
|
||||
if ((self = [super init])) {
|
||||
charMapData_ = [string dataUsingEncoding:NSASCIIStringEncoding];
|
||||
if (!charMapData_) {
|
||||
// Unable to convert string to ASCII
|
||||
return nil;
|
||||
}
|
||||
charMap_ = (char *)[charMapData_ bytes];
|
||||
NSUInteger length = [charMapData_ length];
|
||||
if (length < 2 || length > 128 || length & (length - 1)) {
|
||||
// Length not a power of 2 between 2 and 128
|
||||
return nil;
|
||||
}
|
||||
|
||||
memset(reverseCharMap_, kUnknownChar, sizeof(reverseCharMap_));
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
if (reverseCharMap_[(int)charMap_[i]] != kUnknownChar) {
|
||||
// Duplicate character at |i|
|
||||
return nil;
|
||||
}
|
||||
reverseCharMap_[(int)charMap_[i]] = i;
|
||||
}
|
||||
|
||||
for (NSUInteger i = 1; i < length; i <<= 1) shift_++;
|
||||
mask_ = (1 << shift_) - 1;
|
||||
padLen_ = lcm(8, shift_) / shift_;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<Base%d StringEncoder: %@>", 1 << shift_, charMapData_];
|
||||
}
|
||||
|
||||
- (BOOL)doPad {
|
||||
return doPad_;
|
||||
}
|
||||
|
||||
- (void)setDoPad:(BOOL)doPad {
|
||||
doPad_ = doPad;
|
||||
}
|
||||
|
||||
- (void)setPaddingChar:(char)c {
|
||||
paddingChar_ = c;
|
||||
reverseCharMap_[(int)c] = kPaddingChar;
|
||||
}
|
||||
|
||||
- (NSString *)encode:(NSData *)inData {
|
||||
NSUInteger inLen = [inData length];
|
||||
if (inLen <= 0) {
|
||||
// Empty input
|
||||
return @"";
|
||||
}
|
||||
unsigned char *inBuf = (unsigned char *)[inData bytes];
|
||||
NSUInteger inPos = 0;
|
||||
|
||||
NSUInteger outLen = (inLen * 8 + shift_ - 1) / shift_;
|
||||
if (doPad_) {
|
||||
outLen = ((outLen + padLen_ - 1) / padLen_) * padLen_;
|
||||
}
|
||||
NSMutableData *outData = [NSMutableData dataWithLength:outLen];
|
||||
unsigned char *outBuf = (unsigned char *)[outData mutableBytes];
|
||||
NSUInteger outPos = 0;
|
||||
|
||||
unsigned int buffer = inBuf[inPos++];
|
||||
int bitsLeft = 8;
|
||||
while (bitsLeft > 0 || inPos < inLen) {
|
||||
if (bitsLeft < shift_) {
|
||||
if (inPos < inLen) {
|
||||
buffer <<= 8;
|
||||
buffer |= (inBuf[inPos++] & 0xff);
|
||||
bitsLeft += 8;
|
||||
} else {
|
||||
int pad = shift_ - bitsLeft;
|
||||
buffer <<= pad;
|
||||
bitsLeft += pad;
|
||||
}
|
||||
}
|
||||
unsigned int idx = (buffer >> (bitsLeft - shift_)) & mask_;
|
||||
bitsLeft -= shift_;
|
||||
outBuf[outPos++] = charMap_[idx];
|
||||
}
|
||||
|
||||
if (doPad_) {
|
||||
while (outPos < outLen) outBuf[outPos++] = paddingChar_;
|
||||
}
|
||||
|
||||
if (outPos != outLen) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDStringEncodingBufferUnderflow,
|
||||
@"Underflowed output buffer");
|
||||
return nil;
|
||||
}
|
||||
[outData setLength:outPos];
|
||||
|
||||
return [[NSString alloc] initWithData:outData encoding:NSASCIIStringEncoding];
|
||||
}
|
||||
|
||||
- (NSData *)decode:(NSString *)inString {
|
||||
char *inBuf = (char *)[inString cStringUsingEncoding:NSASCIIStringEncoding];
|
||||
if (!inBuf) {
|
||||
// Unable to convert buffer to ASCII
|
||||
return nil;
|
||||
}
|
||||
NSUInteger inLen = strlen(inBuf);
|
||||
|
||||
NSUInteger outLen = inLen * shift_ / 8;
|
||||
NSMutableData *outData = [NSMutableData dataWithLength:outLen];
|
||||
unsigned char *outBuf = (unsigned char *)[outData mutableBytes];
|
||||
NSUInteger outPos = 0;
|
||||
|
||||
int buffer = 0;
|
||||
int bitsLeft = 0;
|
||||
BOOL expectPad = NO;
|
||||
for (NSUInteger i = 0; i < inLen; i++) {
|
||||
int val = reverseCharMap_[(int)inBuf[i]];
|
||||
switch (val) {
|
||||
case kIgnoreChar:
|
||||
break;
|
||||
case kPaddingChar:
|
||||
expectPad = YES;
|
||||
break;
|
||||
case kUnknownChar:
|
||||
// Unexpected data at input pos |i|
|
||||
return nil;
|
||||
default:
|
||||
if (expectPad) {
|
||||
// Expected further padding characters
|
||||
return nil;
|
||||
}
|
||||
buffer <<= shift_;
|
||||
buffer |= val & mask_;
|
||||
bitsLeft += shift_;
|
||||
if (bitsLeft >= 8) {
|
||||
outBuf[outPos++] = (unsigned char)(buffer >> (bitsLeft - 8));
|
||||
bitsLeft -= 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitsLeft && buffer & ((1 << bitsLeft) - 1)) {
|
||||
// Incomplete trailing data
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Shorten buffer if needed due to padding chars
|
||||
if (outPos > outLen) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDStringEncodingBufferOverflow, @"Overflowed buffer");
|
||||
}
|
||||
[outData setLength:outPos];
|
||||
|
||||
return outData;
|
||||
}
|
||||
|
||||
@end
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDTokenOperation.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FIRInstanceIDTokenDeleteOperation : FIRInstanceIDTokenOperation
|
||||
|
||||
- (instancetype)initWithAuthorizedEntity:(nullable NSString *)authorizedEntity
|
||||
scope:(nullable NSString *)scope
|
||||
checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
|
||||
keyPair:(nullable FIRInstanceIDKeyPair *)keyPair
|
||||
action:(FIRInstanceIDTokenAction)action;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDTokenDeleteOperation.h"
|
||||
|
||||
#import "FIRInstanceIDCheckinPreferences.h"
|
||||
#import "FIRInstanceIDDefines.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDTokenOperation+Private.h"
|
||||
#import "FIRInstanceIDURLQueryItem.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
@implementation FIRInstanceIDTokenDeleteOperation
|
||||
|
||||
- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair
|
||||
action:(FIRInstanceIDTokenAction)action {
|
||||
self = [super initWithAction:action
|
||||
forAuthorizedEntity:authorizedEntity
|
||||
scope:scope
|
||||
options:nil
|
||||
checkinPreferences:checkinPreferences
|
||||
keyPair:keyPair];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)performTokenOperation {
|
||||
NSString *authHeader =
|
||||
[FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:self.checkinPreferences];
|
||||
NSMutableURLRequest *request = [FIRInstanceIDTokenOperation requestWithAuthHeader:authHeader];
|
||||
|
||||
// Build form-encoded body
|
||||
NSString *deviceAuthID = self.checkinPreferences.deviceID;
|
||||
NSMutableArray<FIRInstanceIDURLQueryItem *> *queryItems =
|
||||
[FIRInstanceIDTokenOperation standardQueryItemsWithDeviceID:deviceAuthID scope:self.scope];
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"delete" value:@"true"]];
|
||||
if (self.action == FIRInstanceIDTokenActionDeleteTokenAndIID) {
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"iid-operation"
|
||||
value:@"delete"]];
|
||||
}
|
||||
if (self.authorizedEntity) {
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"sender"
|
||||
value:self.authorizedEntity]];
|
||||
}
|
||||
// Typically we include our public key-signed url items, but in some cases (like deleting all FCM
|
||||
// tokens), we don't.
|
||||
if (self.keyPair != nil) {
|
||||
[queryItems addObjectsFromArray:[self queryItemsWithKeyPair:self.keyPair]];
|
||||
}
|
||||
|
||||
NSString *content = FIRInstanceIDQueryFromQueryItems(queryItems);
|
||||
request.HTTPBody = [content dataUsingEncoding:NSUTF8StringEncoding];
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenDeleteOperationFetchRequest,
|
||||
@"Unregister request to %@ content: %@", FIRInstanceIDRegisterServer(),
|
||||
content);
|
||||
|
||||
FIRInstanceID_WEAKIFY(self);
|
||||
void (^requestHandler)(NSData *, NSURLResponse *, NSError *) =
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
FIRInstanceID_STRONGIFY(self);
|
||||
[self handleResponseWithData:data response:response error:error];
|
||||
};
|
||||
|
||||
// Test block
|
||||
if (self.testBlock) {
|
||||
self.testBlock(request, requestHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
NSURLSession *session = [FIRInstanceIDTokenOperation sharedURLSession];
|
||||
self.dataTask = [session dataTaskWithRequest:request completionHandler:requestHandler];
|
||||
[self.dataTask resume];
|
||||
}
|
||||
|
||||
- (void)handleResponseWithData:(NSData *)data
|
||||
response:(NSURLResponse *)response
|
||||
error:(NSError *)error {
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenDeleteOperationRequestError,
|
||||
@"Device unregister HTTP fetch error. Error code: %ld",
|
||||
_FIRInstanceID_L(error.code));
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *dataResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
if (dataResponse.length == 0) {
|
||||
NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown];
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error];
|
||||
return;
|
||||
}
|
||||
|
||||
if (![dataResponse hasPrefix:@"deleted="] && ![dataResponse hasPrefix:@"token="]) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenDeleteOperationBadResponse,
|
||||
@"Invalid unregister response %@", response);
|
||||
NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown];
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error];
|
||||
return;
|
||||
}
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationSucceeded token:nil error:nil];
|
||||
}
|
||||
@end
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDTokenOperation.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDFirebaseUserAgentKey;
|
||||
|
||||
@interface FIRInstanceIDTokenFetchOperation : FIRInstanceIDTokenOperation
|
||||
|
||||
- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
options:(nullable NSDictionary<NSString *, NSString *> *)options
|
||||
checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair;
|
||||
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDTokenFetchOperation.h"
|
||||
|
||||
#import "FIRInstanceIDCheckinPreferences.h"
|
||||
#import "FIRInstanceIDConstants.h"
|
||||
#import "FIRInstanceIDDefines.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDTokenOperation+Private.h"
|
||||
#import "FIRInstanceIDURLQueryItem.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
#import <FirebaseCore/FIRAppInternal.h>
|
||||
|
||||
// We can have a static int since this error should theoretically only
|
||||
// happen once (for the first time). If it repeats there is something
|
||||
// else that is wrong.
|
||||
static int phoneRegistrationErrorRetryCount = 0;
|
||||
static const int kMaxPhoneRegistrationErrorRetryCount = 10;
|
||||
NSString *const kFIRInstanceIDFirebaseUserAgentKey = @"X-firebase-client";
|
||||
|
||||
@implementation FIRInstanceIDTokenFetchOperation
|
||||
|
||||
- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
options:(nullable NSDictionary<NSString *, NSString *> *)options
|
||||
checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair {
|
||||
self = [super initWithAction:FIRInstanceIDTokenActionFetch
|
||||
forAuthorizedEntity:authorizedEntity
|
||||
scope:scope
|
||||
options:options
|
||||
checkinPreferences:checkinPreferences
|
||||
keyPair:keyPair];
|
||||
if (self) {
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)performTokenOperation {
|
||||
NSString *authHeader =
|
||||
[FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:self.checkinPreferences];
|
||||
NSMutableURLRequest *request = [[self class] requestWithAuthHeader:authHeader];
|
||||
NSString *checkinVersionInfo = self.checkinPreferences.versionInfo;
|
||||
[request setValue:checkinVersionInfo forHTTPHeaderField:@"info"];
|
||||
[request setValue:[FIRApp firebaseUserAgent]
|
||||
forHTTPHeaderField:kFIRInstanceIDFirebaseUserAgentKey];
|
||||
|
||||
// Build form-encoded body
|
||||
NSString *deviceAuthID = self.checkinPreferences.deviceID;
|
||||
NSMutableArray<FIRInstanceIDURLQueryItem *> *queryItems =
|
||||
[[self class] standardQueryItemsWithDeviceID:deviceAuthID scope:self.scope];
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"sender"
|
||||
value:self.authorizedEntity]];
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"X-subtype"
|
||||
value:self.authorizedEntity]];
|
||||
|
||||
[queryItems addObjectsFromArray:[self queryItemsWithKeyPair:self.keyPair]];
|
||||
|
||||
// Create query items from passed-in options
|
||||
id apnsTokenData = self.options[kFIRInstanceIDTokenOptionsAPNSKey];
|
||||
id apnsSandboxValue = self.options[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey];
|
||||
if ([apnsTokenData isKindOfClass:[NSData class]] &&
|
||||
[apnsSandboxValue isKindOfClass:[NSNumber class]]) {
|
||||
NSString *APNSString = FIRInstanceIDAPNSTupleStringForTokenAndServerType(
|
||||
apnsTokenData, ((NSNumber *)apnsSandboxValue).boolValue);
|
||||
// The name of the query item happens to be the same as the dictionary key
|
||||
FIRInstanceIDURLQueryItem *item =
|
||||
[FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDTokenOptionsAPNSKey
|
||||
value:APNSString];
|
||||
[queryItems addObject:item];
|
||||
}
|
||||
id firebaseAppID = self.options[kFIRInstanceIDTokenOptionsFirebaseAppIDKey];
|
||||
if ([firebaseAppID isKindOfClass:[NSString class]]) {
|
||||
// The name of the query item happens to be the same as the dictionary key
|
||||
FIRInstanceIDURLQueryItem *item =
|
||||
[FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDTokenOptionsFirebaseAppIDKey
|
||||
value:(NSString *)firebaseAppID];
|
||||
[queryItems addObject:item];
|
||||
}
|
||||
|
||||
NSString *content = FIRInstanceIDQueryFromQueryItems(queryItems);
|
||||
request.HTTPBody = [content dataUsingEncoding:NSUTF8StringEncoding];
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationFetchRequest,
|
||||
@"Register request to %@ content: %@", FIRInstanceIDRegisterServer(),
|
||||
content);
|
||||
|
||||
FIRInstanceID_WEAKIFY(self);
|
||||
void (^requestHandler)(NSData *, NSURLResponse *, NSError *) =
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
FIRInstanceID_STRONGIFY(self);
|
||||
[self handleResponseWithData:data response:response error:error];
|
||||
};
|
||||
|
||||
// Test block
|
||||
if (self.testBlock) {
|
||||
self.testBlock(request, requestHandler);
|
||||
return;
|
||||
}
|
||||
|
||||
NSURLSession *session = [FIRInstanceIDTokenOperation sharedURLSession];
|
||||
self.dataTask = [session dataTaskWithRequest:request completionHandler:requestHandler];
|
||||
[self.dataTask resume];
|
||||
}
|
||||
|
||||
#pragma mark - Request Handling
|
||||
|
||||
- (void)handleResponseWithData:(NSData *)data
|
||||
response:(NSURLResponse *)response
|
||||
error:(NSError *)error {
|
||||
if (error) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationRequestError,
|
||||
@"Token fetch HTTP error. Error Code: %ld", (long)error.code);
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error];
|
||||
return;
|
||||
}
|
||||
NSString *dataResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
|
||||
if (dataResponse.length == 0) {
|
||||
NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown];
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error];
|
||||
return;
|
||||
}
|
||||
NSDictionary *parsedResponse = [self parseFetchTokenResponse:dataResponse];
|
||||
|
||||
if ([parsedResponse[@"token"] length]) {
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationSucceeded
|
||||
token:parsedResponse[@"token"]
|
||||
error:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *errorValue = parsedResponse[@"Error"];
|
||||
NSError *responseError;
|
||||
if (errorValue.length) {
|
||||
NSArray *errorComponents = [errorValue componentsSeparatedByString:@":"];
|
||||
// HACK (Kansas replication delay), PHONE_REGISTRATION_ERROR on App
|
||||
// uninstall and reinstall.
|
||||
if ([errorComponents containsObject:@"PHONE_REGISTRATION_ERROR"]) {
|
||||
// Encountered issue http://b/27043795
|
||||
// Retry register until successful or another error encountered or a
|
||||
// certain number of tries are over.
|
||||
|
||||
if (phoneRegistrationErrorRetryCount < kMaxPhoneRegistrationErrorRetryCount) {
|
||||
const int nextRetryInterval = 1 << phoneRegistrationErrorRetryCount;
|
||||
FIRInstanceID_WEAKIFY(self);
|
||||
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(nextRetryInterval * NSEC_PER_SEC)),
|
||||
dispatch_get_main_queue(), ^{
|
||||
FIRInstanceID_STRONGIFY(self);
|
||||
phoneRegistrationErrorRetryCount++;
|
||||
[self performTokenOperation];
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if ([errorComponents containsObject:kFIRInstanceID_CMD_RST]) {
|
||||
// Server detected the identity we use is no longer valid.
|
||||
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
|
||||
[center postNotificationName:kFIRInstanceIDIdentityInvalidatedNotification object:nil];
|
||||
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInternal001,
|
||||
@"Identity is invalid. Server request identity reset.");
|
||||
responseError =
|
||||
[NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidIdentity];
|
||||
}
|
||||
}
|
||||
if (!responseError) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationBadResponse,
|
||||
@"Invalid fetch response, expected 'token' or 'Error' key");
|
||||
responseError = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown];
|
||||
}
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:responseError];
|
||||
}
|
||||
|
||||
// expect a response e.g. "token=<reg id>\nGOOG.ttl=123"
|
||||
- (NSDictionary *)parseFetchTokenResponse:(NSString *)response {
|
||||
NSArray *lines = [response componentsSeparatedByString:@"\n"];
|
||||
NSMutableDictionary *parsedResponse = [NSMutableDictionary dictionary];
|
||||
for (NSString *line in lines) {
|
||||
NSArray *keyAndValue = [line componentsSeparatedByString:@"="];
|
||||
if ([keyAndValue count] > 1) {
|
||||
parsedResponse[keyAndValue[0]] = keyAndValue[1];
|
||||
}
|
||||
}
|
||||
return parsedResponse;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "FIRInstanceIDAPNSInfo.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Represents an Instance ID token, and all of the relevant information
|
||||
* associated with it. It can read from and write to an NSDictionary object, for
|
||||
* simple serialization.
|
||||
*/
|
||||
@interface FIRInstanceIDTokenInfo : NSObject <NSCoding>
|
||||
|
||||
/// The authorized entity (also known as Sender ID), associated with the token.
|
||||
@property(nonatomic, readonly, copy) NSString *authorizedEntity;
|
||||
/// The scope associated with the token. This is an arbitrary string, typically "*".
|
||||
@property(nonatomic, readonly, copy) NSString *scope;
|
||||
/// The token value itself, with which all other properties are associated.
|
||||
@property(nonatomic, readonly, copy) NSString *token;
|
||||
|
||||
// These properties are nullable because they might not exist for tokens fetched from
|
||||
// legacy storage formats.
|
||||
|
||||
/// The app version that this token represents.
|
||||
@property(nonatomic, readonly, copy, nullable) NSString *appVersion;
|
||||
/// The Firebase app ID (also known as GMP App ID), that this token is associated with.
|
||||
@property(nonatomic, readonly, copy, nullable) NSString *firebaseAppID;
|
||||
|
||||
/// Tokens may not always be associated with an APNs token, and may be associated after
|
||||
/// being created.
|
||||
@property(nonatomic, strong, nullable) FIRInstanceIDAPNSInfo *APNSInfo;
|
||||
/// The time that this token info was updated. The cache time is writeable, since in
|
||||
/// some cases the token info may be refreshed from the server. In those situations,
|
||||
/// the cacheTime would be updated.
|
||||
@property(nonatomic, copy, nullable) NSDate *cacheTime;
|
||||
|
||||
/**
|
||||
* Initializes a FIRInstanceIDTokenInfo object with the required parameters. These
|
||||
* parameters represent all the relevant associated data with a token.
|
||||
*
|
||||
* @param authorizedEntity The authorized entity (also known as Sender ID).
|
||||
* @param scope The scope of the token, typically "*" meaning
|
||||
* it's a "default scope".
|
||||
* @param token The token value itself.
|
||||
* @param appVersion The application version that this token is associated with.
|
||||
* @param firebaseAppID The Firebase app ID which this token is associated with.
|
||||
* @return An instance of FIRInstanceIDTokenInfo.
|
||||
*/
|
||||
- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
token:(NSString *)token
|
||||
appVersion:(nullable NSString *)appVersion
|
||||
firebaseAppID:(nullable NSString *)firebaseAppID;
|
||||
|
||||
/**
|
||||
* Check whether the token is still fresh based on:
|
||||
* 1. Last fetch token is within the 7 days.
|
||||
* 2. Language setting is not changed.
|
||||
* 3. App version is current.
|
||||
* 4. GMP App ID is current.
|
||||
*/
|
||||
- (BOOL)isFresh;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDTokenInfo.h"
|
||||
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
|
||||
/**
|
||||
* @enum Token Info Dictionary Key Constants
|
||||
* @discussion The keys that are checked when a token info is
|
||||
* created from a dictionary. The same keys are used
|
||||
* when decoding/encoding an archive.
|
||||
*/
|
||||
/// Specifies a dictonary key whose value represents the authorized entity, or
|
||||
/// Sender ID for the token.
|
||||
static NSString *const kFIRInstanceIDAuthorizedEntityKey = @"authorized_entity";
|
||||
/// Specifies a dictionary key whose value represents the scope of the token,
|
||||
/// typically "*".
|
||||
static NSString *const kFIRInstanceIDScopeKey = @"scope";
|
||||
/// Specifies a dictionary key which represents the token value itself.
|
||||
static NSString *const kFIRInstanceIDTokenKey = @"token";
|
||||
/// Specifies a dictionary key which represents the app version associated
|
||||
/// with the token.
|
||||
static NSString *const kFIRInstanceIDAppVersionKey = @"app_version";
|
||||
/// Specifies a dictionary key which represents the GMP App ID associated with
|
||||
/// the token.
|
||||
static NSString *const kFIRInstanceIDFirebaseAppIDKey = @"firebase_app_id";
|
||||
/// Specifies a dictionary key representing an archive for a
|
||||
/// `FIRInstanceIDAPNSInfo` object.
|
||||
static NSString *const kFIRInstanceIDAPNSInfoKey = @"apns_info";
|
||||
/// Specifies a dictionary key representing the "last cached" time for the token.
|
||||
static NSString *const kFIRInstanceIDCacheTimeKey = @"cache_time";
|
||||
/// Default interval that token stays fresh.
|
||||
const NSTimeInterval kDefaultFetchTokenInterval = 7 * 24 * 60 * 60; // 7 days.
|
||||
|
||||
@implementation FIRInstanceIDTokenInfo
|
||||
|
||||
- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
token:(NSString *)token
|
||||
appVersion:(NSString *)appVersion
|
||||
firebaseAppID:(NSString *)firebaseAppID {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_authorizedEntity = [authorizedEntity copy];
|
||||
_scope = [scope copy];
|
||||
_token = [token copy];
|
||||
_appVersion = [appVersion copy];
|
||||
_firebaseAppID = [firebaseAppID copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isFresh {
|
||||
// Last fetch token cache time could be null if token is from legacy storage format. Then token is
|
||||
// considered not fresh and should be refreshed and overwrite with the latest storage format.
|
||||
if (!_cacheTime) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Check if app has just been updated to a new version.
|
||||
NSString *currentAppVersion = FIRInstanceIDCurrentAppVersion();
|
||||
if (!_appVersion || ![_appVersion isEqualToString:currentAppVersion]) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenManager004,
|
||||
@"Invalidating cached token for %@ (%@) due to app version change.",
|
||||
_authorizedEntity, _scope);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Check if GMP App ID has changed
|
||||
NSString *currentFirebaseAppID = FIRInstanceIDFirebaseAppID();
|
||||
if (!_firebaseAppID || ![_firebaseAppID isEqualToString:currentFirebaseAppID]) {
|
||||
FIRInstanceIDLoggerDebug(
|
||||
kFIRInstanceIDMessageCodeTokenInfoFirebaseAppIDChanged,
|
||||
@"Invalidating cached token due to Firebase App IID change from %@ to %@", _firebaseAppID,
|
||||
currentFirebaseAppID);
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Check whether locale has changed, if yes, token needs to be updated with server for locale
|
||||
// information.
|
||||
if (FIRInstanceIDHasLocaleChanged()) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenInfoLocaleChanged,
|
||||
@"Invalidating cached token due to locale change");
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Locale is not changed, check whether token has been fetched within 7 days.
|
||||
NSTimeInterval lastFetchTokenTimestamp = [_cacheTime timeIntervalSince1970];
|
||||
NSTimeInterval currentTimestamp = FIRInstanceIDCurrentTimestampInSeconds();
|
||||
NSTimeInterval timeSinceLastFetchToken = currentTimestamp - lastFetchTokenTimestamp;
|
||||
return (timeSinceLastFetchToken < kDefaultFetchTokenInterval);
|
||||
}
|
||||
#pragma mark - NSCoding
|
||||
|
||||
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
|
||||
// These value cannot be nil
|
||||
|
||||
id authorizedEntity = [aDecoder decodeObjectForKey:kFIRInstanceIDAuthorizedEntityKey];
|
||||
if (![authorizedEntity isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
id scope = [aDecoder decodeObjectForKey:kFIRInstanceIDScopeKey];
|
||||
if (![scope isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
id token = [aDecoder decodeObjectForKey:kFIRInstanceIDTokenKey];
|
||||
if (![token isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// These values are nullable, so only fail the decode if the type does not match
|
||||
|
||||
id appVersion = [aDecoder decodeObjectForKey:kFIRInstanceIDAppVersionKey];
|
||||
if (appVersion && ![appVersion isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
id firebaseAppID = [aDecoder decodeObjectForKey:kFIRInstanceIDFirebaseAppIDKey];
|
||||
if (firebaseAppID && ![firebaseAppID isKindOfClass:[NSString class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
id rawAPNSInfo = [aDecoder decodeObjectForKey:kFIRInstanceIDAPNSInfoKey];
|
||||
if (rawAPNSInfo && ![rawAPNSInfo isKindOfClass:[NSData class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
FIRInstanceIDAPNSInfo *APNSInfo = nil;
|
||||
if (rawAPNSInfo) {
|
||||
// TODO(chliangGoogle: Use the new API and secureCoding protocol.
|
||||
@try {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
APNSInfo = [NSKeyedUnarchiver unarchiveObjectWithData:rawAPNSInfo];
|
||||
#pragma clang diagnostic pop
|
||||
} @catch (NSException *exception) {
|
||||
FIRInstanceIDLoggerInfo(kFIRInstanceIDMessageCodeTokenInfoBadAPNSInfo,
|
||||
@"Could not parse raw APNS Info while parsing archived token info.");
|
||||
APNSInfo = nil;
|
||||
} @finally {
|
||||
}
|
||||
}
|
||||
|
||||
id cacheTime = [aDecoder decodeObjectForKey:kFIRInstanceIDCacheTimeKey];
|
||||
if (cacheTime && ![cacheTime isKindOfClass:[NSDate class]]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_authorizedEntity = authorizedEntity;
|
||||
_scope = scope;
|
||||
_token = token;
|
||||
_appVersion = appVersion;
|
||||
_firebaseAppID = firebaseAppID;
|
||||
_APNSInfo = APNSInfo;
|
||||
_cacheTime = cacheTime;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:self.authorizedEntity forKey:kFIRInstanceIDAuthorizedEntityKey];
|
||||
[aCoder encodeObject:self.scope forKey:kFIRInstanceIDScopeKey];
|
||||
[aCoder encodeObject:self.token forKey:kFIRInstanceIDTokenKey];
|
||||
[aCoder encodeObject:self.appVersion forKey:kFIRInstanceIDAppVersionKey];
|
||||
[aCoder encodeObject:self.firebaseAppID forKey:kFIRInstanceIDFirebaseAppIDKey];
|
||||
NSData *rawAPNSInfo;
|
||||
if (self.APNSInfo) {
|
||||
// TODO(chliangGoogle: Use the new API and secureCoding protocol.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
rawAPNSInfo = [NSKeyedArchiver archivedDataWithRootObject:self.APNSInfo];
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
[aCoder encodeObject:rawAPNSInfo forKey:kFIRInstanceIDAPNSInfoKey];
|
||||
}
|
||||
[aCoder encodeObject:self.cacheTime forKey:kFIRInstanceIDCacheTimeKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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 "FIRInstanceID.h"
|
||||
|
||||
@class FIRInstanceIDAuthService;
|
||||
@class FIRInstanceIDCheckinPreferences;
|
||||
@class FIRInstanceIDKeyPair;
|
||||
@class FIRInstanceIDTokenInfo;
|
||||
@class FIRInstanceIDStore;
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, FIRInstanceIDInvalidTokenReason) {
|
||||
FIRInstanceIDInvalidTokenReasonNone = 0, // 0
|
||||
FIRInstanceIDInvalidTokenReasonAppVersion = (1 << 0), // 0...00001
|
||||
FIRInstanceIDInvalidTokenReasonAPNSToken = (1 << 1), // 0...00010
|
||||
};
|
||||
|
||||
/**
|
||||
* Manager for the InstanceID token requests i.e `newToken` and `deleteToken`. This
|
||||
* manages the overall interaction of the `InstanceIDStore`, the token register
|
||||
* service and the callbacks associated with `GCMInstanceID`.
|
||||
*/
|
||||
@interface FIRInstanceIDTokenManager : NSObject
|
||||
|
||||
/// Expose the auth service, so it can be used by others
|
||||
@property(nonatomic, readonly, strong) FIRInstanceIDAuthService *authService;
|
||||
|
||||
/**
|
||||
* Fetch new token for the given authorizedEntity and scope. This makes an
|
||||
* asynchronous request to the InstanceID backend to create a new token for
|
||||
* the service and returns it. This will replace any old token for the given
|
||||
* authorizedEntity and scope that has been cached before.
|
||||
*
|
||||
* @param authorizedEntity The authorized entity for the token, should not be nil.
|
||||
* @param scope The scope for the token, should not be nil.
|
||||
* @param keyPair The keyPair that represents the app identity.
|
||||
* @param options The options to be added to the fetch request.
|
||||
* @param handler The handler to be invoked once we have the token or the
|
||||
* fetch request to InstanceID backend results in an error. Also
|
||||
* since it's a public handler it should always be called
|
||||
* asynchronously. This should be non-nil.
|
||||
*/
|
||||
- (void)fetchNewTokenWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair
|
||||
options:(NSDictionary *)options
|
||||
handler:(FIRInstanceIDTokenHandler)handler;
|
||||
|
||||
/**
|
||||
* Return the cached token info, if one exists, for the given authorizedEntity and scope.
|
||||
*
|
||||
* @param authorizedEntity The authorized entity for the token.
|
||||
* @param scope The scope for the token.
|
||||
*
|
||||
* @return The cached token info, if available, matching the parameters.
|
||||
*/
|
||||
- (FIRInstanceIDTokenInfo *)cachedTokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope;
|
||||
|
||||
/**
|
||||
* Delete the token for the given authorizedEntity and scope. If the token has
|
||||
* been cached, it will be deleted from the store. It will also make an
|
||||
* asynchronous request to the InstanceID backend to invalidate the token.
|
||||
*
|
||||
* @param authorizedEntity The authorized entity for the token, should not be nil.
|
||||
* @param scope The scope for the token, should not be nil.
|
||||
* @param keyPair The keyPair that represents the app identity.
|
||||
* @param handler The handler to be invoked once the delete request to
|
||||
* InstanceID backend has returned. If the request was
|
||||
* successful we invoke the handler with a nil error;
|
||||
* otherwise we call it with an appropriate error. Also since
|
||||
* it's a public handler it should always be called
|
||||
* asynchronously. This should be non-nil.
|
||||
*/
|
||||
- (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair
|
||||
handler:(FIRInstanceIDDeleteTokenHandler)handler;
|
||||
|
||||
/**
|
||||
* Deletes all cached tokens from the persistent store. This method should only be triggered
|
||||
* when InstanceID is deleted
|
||||
*
|
||||
* @param keyPair The keyPair for the given app.
|
||||
* @param handler The handler to be invoked once the delete request to InstanceID backend
|
||||
* has returned. If the request was successful we invoke the handler with
|
||||
* a nil error; else we pass in an appropriate error. This should be non-nil
|
||||
* and be called asynchronously.
|
||||
*/
|
||||
- (void)deleteAllTokensWithKeyPair:(FIRInstanceIDKeyPair *)keyPair
|
||||
handler:(FIRInstanceIDDeleteHandler)handler;
|
||||
|
||||
/**
|
||||
* Deletes all cached tokens from the persistent store.
|
||||
* @param handler The callback handler which is invoked when tokens deletion is complete,
|
||||
* with an error if there is any.
|
||||
*
|
||||
*/
|
||||
- (void)deleteAllTokensLocallyWithHandler:(void (^)(NSError *error))handler;
|
||||
|
||||
/**
|
||||
* Stop any ongoing token operations.
|
||||
*/
|
||||
- (void)stopAllTokenOperations;
|
||||
|
||||
#pragma mark - Invalidating Cached Tokens
|
||||
|
||||
/**
|
||||
* Invalidate any cached tokens, if the app version has changed since last launch or if the token
|
||||
* is cached for more than 7 days.
|
||||
*
|
||||
* @return Whether we should fetch default token from server.
|
||||
*
|
||||
* @discussion This should safely be called prior to any tokens being retrieved from
|
||||
* the cache or being fetched from the network.
|
||||
*/
|
||||
- (BOOL)checkForTokenRefreshPolicy;
|
||||
|
||||
/**
|
||||
* Upon being provided with different APNs or sandbox, any locally cached tokens
|
||||
* should be deleted, and the new APNs token should be cached.
|
||||
*
|
||||
* @discussion It is possible for this method to be called while token operations are
|
||||
* in-progress or queued. In this case, the in-flight token operations will have stale
|
||||
* APNs information. The default token is checked for being out-of-date by Instance ID,
|
||||
* and re-fetched. Custom tokens are not currently checked.
|
||||
*
|
||||
* @param deviceToken The APNS device token, provided by the operating system.
|
||||
* @param isSandbox YES if the device token is for the sandbox environment, NO otherwise.
|
||||
*
|
||||
* @return The array of FIRInstanceIDTokenInfo objects which were invalidated.
|
||||
*/
|
||||
- (NSArray<FIRInstanceIDTokenInfo *> *)updateTokensToAPNSDeviceToken:(NSData *)deviceToken
|
||||
isSandbox:(BOOL)isSandbox;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDTokenManager.h"
|
||||
|
||||
#import "FIRInstanceIDAuthKeyChain.h"
|
||||
#import "FIRInstanceIDAuthService.h"
|
||||
#import "FIRInstanceIDCheckinPreferences.h"
|
||||
#import "FIRInstanceIDConstants.h"
|
||||
#import "FIRInstanceIDDefines.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDStore.h"
|
||||
#import "FIRInstanceIDTokenDeleteOperation.h"
|
||||
#import "FIRInstanceIDTokenFetchOperation.h"
|
||||
#import "FIRInstanceIDTokenInfo.h"
|
||||
#import "FIRInstanceIDTokenOperation.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
@interface FIRInstanceIDTokenManager () <FIRInstanceIDStoreDelegate>
|
||||
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDStore *instanceIDStore;
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDAuthService *authService;
|
||||
@property(nonatomic, readonly, strong) NSOperationQueue *tokenOperations;
|
||||
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDAPNSInfo *currentAPNSInfo;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDTokenManager
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_instanceIDStore = [[FIRInstanceIDStore alloc] initWithDelegate:self];
|
||||
_authService = [[FIRInstanceIDAuthService alloc] initWithStore:_instanceIDStore];
|
||||
[self configureTokenOperations];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopAllTokenOperations];
|
||||
}
|
||||
|
||||
- (void)configureTokenOperations {
|
||||
_tokenOperations = [[NSOperationQueue alloc] init];
|
||||
_tokenOperations.name = @"com.google.iid-token-operations";
|
||||
// For now, restrict the operations to be serial, because in some cases (like if the
|
||||
// authorized entity and scope are the same), order matters.
|
||||
// If we have to deal with several different token requests simultaneously, it would be a good
|
||||
// idea to add some better intelligence around this (performing unrelated token operations
|
||||
// simultaneously, etc.).
|
||||
_tokenOperations.maxConcurrentOperationCount = 1;
|
||||
if ([_tokenOperations respondsToSelector:@selector(qualityOfService)]) {
|
||||
_tokenOperations.qualityOfService = NSOperationQualityOfServiceUtility;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)fetchNewTokenWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair
|
||||
options:(NSDictionary *)options
|
||||
handler:(FIRInstanceIDTokenHandler)handler {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenManager000,
|
||||
@"Fetch new token for authorizedEntity: %@, scope: %@", authorizedEntity,
|
||||
scope);
|
||||
FIRInstanceIDTokenFetchOperation *operation =
|
||||
[self createFetchOperationWithAuthorizedEntity:authorizedEntity
|
||||
scope:scope
|
||||
options:options
|
||||
keyPair:keyPair];
|
||||
FIRInstanceID_WEAKIFY(self);
|
||||
FIRInstanceIDTokenOperationCompletion completion =
|
||||
^(FIRInstanceIDTokenOperationResult result, NSString *_Nullable token,
|
||||
NSError *_Nullable error) {
|
||||
FIRInstanceID_STRONGIFY(self);
|
||||
if (error) {
|
||||
handler(nil, error);
|
||||
return;
|
||||
}
|
||||
NSString *firebaseAppID = options[kFIRInstanceIDTokenOptionsFirebaseAppIDKey];
|
||||
FIRInstanceIDTokenInfo *tokenInfo = [[FIRInstanceIDTokenInfo alloc]
|
||||
initWithAuthorizedEntity:authorizedEntity
|
||||
scope:scope
|
||||
token:token
|
||||
appVersion:FIRInstanceIDCurrentAppVersion()
|
||||
firebaseAppID:firebaseAppID];
|
||||
tokenInfo.APNSInfo = [[FIRInstanceIDAPNSInfo alloc] initWithTokenOptionsDictionary:options];
|
||||
|
||||
[self.instanceIDStore
|
||||
saveTokenInfo:tokenInfo
|
||||
handler:^(NSError *error) {
|
||||
if (!error) {
|
||||
// Do not send the token back in case the save was unsuccessful. Since with
|
||||
// the new asychronous fetch mechanism this can lead to infinite loops, for
|
||||
// example, we will return a valid token even though we weren't able to store
|
||||
// it in our cache. The first token will lead to a onTokenRefresh callback
|
||||
// wherein the user again calls `getToken` but since we weren't able to save
|
||||
// it we won't hit the cache but hit the server again leading to an infinite
|
||||
// loop.
|
||||
FIRInstanceIDLoggerDebug(
|
||||
kFIRInstanceIDMessageCodeTokenManager001,
|
||||
@"Token fetch successful, token: %@, authorizedEntity: %@, scope:%@",
|
||||
token, authorizedEntity, scope);
|
||||
|
||||
if (handler) {
|
||||
handler(token, nil);
|
||||
}
|
||||
} else {
|
||||
if (handler) {
|
||||
handler(nil, error);
|
||||
}
|
||||
}
|
||||
}];
|
||||
};
|
||||
// Add completion handler, and ensure it's called on the main queue
|
||||
[operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
|
||||
NSString *_Nullable token, NSError *_Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
completion(result, token, error);
|
||||
});
|
||||
}];
|
||||
[self.tokenOperations addOperation:operation];
|
||||
}
|
||||
|
||||
- (FIRInstanceIDTokenInfo *)cachedTokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope {
|
||||
return [self.instanceIDStore tokenInfoWithAuthorizedEntity:authorizedEntity scope:scope];
|
||||
}
|
||||
|
||||
- (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair
|
||||
handler:(FIRInstanceIDDeleteTokenHandler)handler {
|
||||
if ([self.instanceIDStore tokenInfoWithAuthorizedEntity:authorizedEntity scope:scope]) {
|
||||
[self.instanceIDStore removeCachedTokenWithAuthorizedEntity:authorizedEntity scope:scope];
|
||||
}
|
||||
// Does not matter if we cannot find it in the cache. Still make an effort to unregister
|
||||
// from the server.
|
||||
FIRInstanceIDCheckinPreferences *checkinPreferences = self.authService.checkinPreferences;
|
||||
FIRInstanceIDTokenDeleteOperation *operation =
|
||||
[self createDeleteOperationWithAuthorizedEntity:authorizedEntity
|
||||
scope:scope
|
||||
checkinPreferences:checkinPreferences
|
||||
keyPair:keyPair
|
||||
action:FIRInstanceIDTokenActionDeleteToken];
|
||||
|
||||
if (handler) {
|
||||
[operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
|
||||
NSString *_Nullable token, NSError *_Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
handler(error);
|
||||
});
|
||||
}];
|
||||
}
|
||||
[self.tokenOperations addOperation:operation];
|
||||
}
|
||||
|
||||
- (void)deleteAllTokensWithKeyPair:(FIRInstanceIDKeyPair *)keyPair
|
||||
handler:(FIRInstanceIDDeleteHandler)handler {
|
||||
// delete all tokens
|
||||
FIRInstanceIDCheckinPreferences *checkinPreferences = self.authService.checkinPreferences;
|
||||
if (!checkinPreferences) {
|
||||
// The checkin is already deleted. No need to trigger the token delete operation as client no
|
||||
// longer has the checkin information for server to delete.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
handler(nil);
|
||||
});
|
||||
return;
|
||||
}
|
||||
FIRInstanceIDTokenDeleteOperation *operation =
|
||||
[self createDeleteOperationWithAuthorizedEntity:kFIRInstanceIDKeychainWildcardIdentifier
|
||||
scope:kFIRInstanceIDKeychainWildcardIdentifier
|
||||
checkinPreferences:checkinPreferences
|
||||
keyPair:keyPair
|
||||
action:FIRInstanceIDTokenActionDeleteTokenAndIID];
|
||||
if (handler) {
|
||||
[operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
|
||||
NSString *_Nullable token, NSError *_Nullable error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
handler(error);
|
||||
});
|
||||
}];
|
||||
}
|
||||
[self.tokenOperations addOperation:operation];
|
||||
}
|
||||
|
||||
- (void)deleteAllTokensLocallyWithHandler:(void (^)(NSError *error))handler {
|
||||
[self.instanceIDStore removeAllCachedTokensWithHandler:handler];
|
||||
}
|
||||
|
||||
- (void)stopAllTokenOperations {
|
||||
[self.authService stopCheckinRequest];
|
||||
[self.tokenOperations cancelAllOperations];
|
||||
}
|
||||
|
||||
#pragma mark - FIRInstanceIDStoreDelegate
|
||||
|
||||
- (void)store:(FIRInstanceIDStore *)store
|
||||
didDeleteFCMScopedTokensForCheckin:(FIRInstanceIDCheckinPreferences *)checkin {
|
||||
// Make a best effort try to delete the old client related state on the FCM server. This is
|
||||
// required to delete old pubusb registrations which weren't cleared when the app was deleted.
|
||||
//
|
||||
// This is only a one time effort. If this call fails the client would still receive duplicate
|
||||
// pubsub notifications if he is again subscribed to the same topic.
|
||||
//
|
||||
// The client state should be cleared on the server for the provided checkin preferences.
|
||||
FIRInstanceIDTokenDeleteOperation *operation =
|
||||
[self createDeleteOperationWithAuthorizedEntity:nil
|
||||
scope:nil
|
||||
checkinPreferences:checkin
|
||||
keyPair:nil
|
||||
action:FIRInstanceIDTokenActionDeleteToken];
|
||||
[operation addCompletionHandler:^(FIRInstanceIDTokenOperationResult result,
|
||||
NSString *_Nullable token, NSError *_Nullable error) {
|
||||
if (error) {
|
||||
FIRInstanceIDMessageCode code =
|
||||
kFIRInstanceIDMessageCodeTokenManagerErrorDeletingFCMTokensOnAppReset;
|
||||
FIRInstanceIDLoggerDebug(code, @"Failed to delete GCM server registrations on app reset.");
|
||||
} else {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenManagerDeletedFCMTokensOnAppReset,
|
||||
@"Successfully deleted GCM server registrations on app reset");
|
||||
}
|
||||
}];
|
||||
|
||||
[self.tokenOperations addOperation:operation];
|
||||
}
|
||||
|
||||
#pragma mark - Unit Testing Stub Helpers
|
||||
// We really have this method so that we can more easily stub it out for unit testing
|
||||
- (FIRInstanceIDTokenFetchOperation *)
|
||||
createFetchOperationWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
options:(NSDictionary<NSString *, NSString *> *)options
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair {
|
||||
FIRInstanceIDCheckinPreferences *checkinPreferences = self.authService.checkinPreferences;
|
||||
FIRInstanceIDTokenFetchOperation *operation =
|
||||
[[FIRInstanceIDTokenFetchOperation alloc] initWithAuthorizedEntity:authorizedEntity
|
||||
scope:scope
|
||||
options:options
|
||||
checkinPreferences:checkinPreferences
|
||||
keyPair:keyPair];
|
||||
return operation;
|
||||
}
|
||||
|
||||
// We really have this method so that we can more easily stub it out for unit testing
|
||||
- (FIRInstanceIDTokenDeleteOperation *)
|
||||
createDeleteOperationWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair
|
||||
action:(FIRInstanceIDTokenAction)action {
|
||||
FIRInstanceIDTokenDeleteOperation *operation =
|
||||
[[FIRInstanceIDTokenDeleteOperation alloc] initWithAuthorizedEntity:authorizedEntity
|
||||
scope:scope
|
||||
checkinPreferences:checkinPreferences
|
||||
keyPair:keyPair
|
||||
action:action];
|
||||
return operation;
|
||||
}
|
||||
|
||||
#pragma mark - Invalidating Cached Tokens
|
||||
- (BOOL)checkForTokenRefreshPolicy {
|
||||
// We know at least one cached token exists.
|
||||
BOOL shouldFetchDefaultToken = NO;
|
||||
NSArray<FIRInstanceIDTokenInfo *> *tokenInfos = [self.instanceIDStore cachedTokenInfos];
|
||||
|
||||
NSMutableArray<FIRInstanceIDTokenInfo *> *tokenInfosToDelete =
|
||||
[NSMutableArray arrayWithCapacity:tokenInfos.count];
|
||||
for (FIRInstanceIDTokenInfo *tokenInfo in tokenInfos) {
|
||||
BOOL isTokenFresh = [tokenInfo isFresh];
|
||||
if (isTokenFresh) {
|
||||
// Token is fresh, do nothing.
|
||||
continue;
|
||||
}
|
||||
if ([tokenInfo.scope isEqualToString:kFIRInstanceIDDefaultTokenScope]) {
|
||||
// Default token is expired, do not mark for deletion. Fetch directly from server to
|
||||
// replace the current one.
|
||||
shouldFetchDefaultToken = YES;
|
||||
} else {
|
||||
// Non-default token is expired, mark for deletion.
|
||||
[tokenInfosToDelete addObject:tokenInfo];
|
||||
}
|
||||
FIRInstanceIDLoggerDebug(
|
||||
kFIRInstanceIDMessageCodeTokenManagerInvalidateStaleToken,
|
||||
@"Invalidating cached token for %@ (%@) due to token is no longer fresh.",
|
||||
tokenInfo.authorizedEntity, tokenInfo.scope);
|
||||
}
|
||||
for (FIRInstanceIDTokenInfo *tokenInfoToDelete in tokenInfosToDelete) {
|
||||
[self.instanceIDStore removeCachedTokenWithAuthorizedEntity:tokenInfoToDelete.authorizedEntity
|
||||
scope:tokenInfoToDelete.scope];
|
||||
}
|
||||
return shouldFetchDefaultToken;
|
||||
}
|
||||
|
||||
- (NSArray<FIRInstanceIDTokenInfo *> *)updateTokensToAPNSDeviceToken:(NSData *)deviceToken
|
||||
isSandbox:(BOOL)isSandbox {
|
||||
// Each cached IID token that is missing an APNSInfo, or has an APNSInfo associated should be
|
||||
// checked and invalidated if needed.
|
||||
FIRInstanceIDAPNSInfo *APNSInfo = [[FIRInstanceIDAPNSInfo alloc] initWithDeviceToken:deviceToken
|
||||
isSandbox:isSandbox];
|
||||
if ([self.currentAPNSInfo isEqualToAPNSInfo:APNSInfo]) {
|
||||
return @[];
|
||||
}
|
||||
self.currentAPNSInfo = APNSInfo;
|
||||
|
||||
NSArray<FIRInstanceIDTokenInfo *> *tokenInfos = [self.instanceIDStore cachedTokenInfos];
|
||||
NSMutableArray<FIRInstanceIDTokenInfo *> *tokenInfosToDelete =
|
||||
[NSMutableArray arrayWithCapacity:tokenInfos.count];
|
||||
for (FIRInstanceIDTokenInfo *cachedTokenInfo in tokenInfos) {
|
||||
// Check if the cached APNSInfo is nil, or if it is an old APNSInfo.
|
||||
if (!cachedTokenInfo.APNSInfo ||
|
||||
![cachedTokenInfo.APNSInfo isEqualToAPNSInfo:self.currentAPNSInfo]) {
|
||||
// Mark for invalidation.
|
||||
[tokenInfosToDelete addObject:cachedTokenInfo];
|
||||
}
|
||||
}
|
||||
for (FIRInstanceIDTokenInfo *tokenInfoToDelete in tokenInfosToDelete) {
|
||||
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenManagerAPNSChangedTokenInvalidated,
|
||||
@"Invalidating cached token for %@ (%@) due to APNs token change.",
|
||||
tokenInfoToDelete.authorizedEntity, tokenInfoToDelete.scope);
|
||||
[self.instanceIDStore removeCachedTokenWithAuthorizedEntity:tokenInfoToDelete.authorizedEntity
|
||||
scope:tokenInfoToDelete.scope];
|
||||
}
|
||||
return tokenInfosToDelete;
|
||||
}
|
||||
|
||||
@end
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDTokenOperation.h"
|
||||
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
|
||||
@class FIRInstanceIDKeyPair;
|
||||
@class FIRInstanceIDURLQueryItem;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FIRInstanceIDTokenOperation (Private)
|
||||
|
||||
@property(atomic, strong) NSURLSessionDataTask *dataTask;
|
||||
@property(readonly, strong)
|
||||
NSMutableArray<FIRInstanceIDTokenOperationCompletion> *completionHandlers;
|
||||
|
||||
// For testing only
|
||||
@property(nonatomic, readwrite, copy) FIRInstanceIDURLRequestTestBlock testBlock;
|
||||
|
||||
+ (NSURLSession *)sharedURLSession;
|
||||
|
||||
#pragma mark - Initialization
|
||||
- (instancetype)initWithAction:(FIRInstanceIDTokenAction)action
|
||||
forAuthorizedEntity:(nullable NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
options:(nullable NSDictionary<NSString *, NSString *> *)options
|
||||
checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair;
|
||||
|
||||
#pragma mark - Request Construction
|
||||
+ (NSMutableURLRequest *)requestWithAuthHeader:(NSString *)authHeaderString;
|
||||
+ (NSMutableArray<FIRInstanceIDURLQueryItem *> *)standardQueryItemsWithDeviceID:(NSString *)deviceID
|
||||
scope:(NSString *)scope;
|
||||
- (NSArray<FIRInstanceIDURLQueryItem *> *)queryItemsWithKeyPair:(FIRInstanceIDKeyPair *)keyPair;
|
||||
|
||||
#pragma mark - HTTP Headers
|
||||
/**
|
||||
* Given a valid checkin preferences object, it will return a string that can be used
|
||||
* in the "Authorization" HTTP header to authenticate this request.
|
||||
*
|
||||
* @param checkin The valid checkin preferences object, with a deviceID and secretToken.
|
||||
*/
|
||||
+ (NSString *)HTTPAuthHeaderFromCheckin:(FIRInstanceIDCheckinPreferences *)checkin;
|
||||
|
||||
#pragma mark - Result
|
||||
- (void)finishWithResult:(FIRInstanceIDTokenOperationResult)result
|
||||
token:(nullable NSString *)token
|
||||
error:(nullable NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class FIRInstanceIDKeyPair;
|
||||
@class FIRInstanceIDCheckinPreferences;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Represents the action taken on an FCM token.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, FIRInstanceIDTokenAction) {
|
||||
FIRInstanceIDTokenActionFetch,
|
||||
FIRInstanceIDTokenActionDeleteToken,
|
||||
FIRInstanceIDTokenActionDeleteTokenAndIID,
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the possible results of a token operation.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, FIRInstanceIDTokenOperationResult) {
|
||||
FIRInstanceIDTokenOperationSucceeded,
|
||||
FIRInstanceIDTokenOperationError,
|
||||
FIRInstanceIDTokenOperationCancelled,
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to invoke once the HTTP call to FIRMessaging backend for updating
|
||||
* subscription finishes.
|
||||
*
|
||||
* @param result The result of the operation.
|
||||
* @param token If the action for fetching a token and the request was successful, this will hold
|
||||
* the value of the token. Otherwise nil.
|
||||
* @param error The error which occurred while performing the token operation. This will be nil
|
||||
* in case the operation was successful, or if the operation was cancelled.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDTokenOperationCompletion)(FIRInstanceIDTokenOperationResult result,
|
||||
NSString *_Nullable token,
|
||||
NSError *_Nullable error);
|
||||
|
||||
@interface FIRInstanceIDTokenOperation : NSOperation
|
||||
|
||||
@property(nonatomic, readonly) FIRInstanceIDTokenAction action;
|
||||
@property(nonatomic, readonly, nullable) NSString *authorizedEntity;
|
||||
@property(nonatomic, readonly, nullable) NSString *scope;
|
||||
@property(nonatomic, readonly, nullable) NSDictionary<NSString *, NSString *> *options;
|
||||
@property(nonatomic, readonly, strong) FIRInstanceIDCheckinPreferences *checkinPreferences;
|
||||
@property(nonatomic, readonly, strong) FIRInstanceIDKeyPair *keyPair;
|
||||
|
||||
@property(nonatomic, readonly) FIRInstanceIDTokenOperationResult result;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
- (void)addCompletionHandler:(FIRInstanceIDTokenOperationCompletion)handler;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDTokenOperation.h"
|
||||
|
||||
#import "FIRInstanceIDCheckinPreferences.h"
|
||||
#import "FIRInstanceIDKeyPair.h"
|
||||
#import "FIRInstanceIDKeyPairUtilities.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
#import "FIRInstanceIDURLQueryItem.h"
|
||||
#import "FIRInstanceIDUtilities.h"
|
||||
#import "NSError+FIRInstanceID.h"
|
||||
|
||||
static const NSInteger kFIRInstanceIDPlatformVersionIOS = 2;
|
||||
|
||||
static NSString *const kFIRInstanceIDParamInstanceID = @"appid";
|
||||
// Scope parameter that defines the service using the token
|
||||
static NSString *const kFIRInstanceIDParamScope = @"X-scope";
|
||||
// Defines the SDK version
|
||||
static NSString *const kFIRInstanceIDParamFCMLibVersion = @"X-cliv";
|
||||
|
||||
@interface FIRInstanceIDTokenOperation () {
|
||||
BOOL _isFinished;
|
||||
BOOL _isExecuting;
|
||||
}
|
||||
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDCheckinPreferences *checkinPreferences;
|
||||
@property(nonatomic, readwrite, strong) FIRInstanceIDKeyPair *keyPair;
|
||||
|
||||
@property(atomic, strong) NSURLSessionDataTask *dataTask;
|
||||
@property(readonly, strong)
|
||||
NSMutableArray<FIRInstanceIDTokenOperationCompletion> *completionHandlers;
|
||||
|
||||
// For testing only
|
||||
@property(nonatomic, readwrite, copy) FIRInstanceIDURLRequestTestBlock testBlock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FIRInstanceIDTokenOperation
|
||||
|
||||
+ (NSURLSession *)sharedURLSession {
|
||||
static NSURLSession *tokenOperationSharedSession;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||||
config.timeoutIntervalForResource = 60.0f; // 1 minute
|
||||
tokenOperationSharedSession = [NSURLSession sessionWithConfiguration:config];
|
||||
tokenOperationSharedSession.sessionDescription = @"com.google.iid.tokens.session";
|
||||
});
|
||||
return tokenOperationSharedSession;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAction:(FIRInstanceIDTokenAction)action
|
||||
forAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
options:(NSDictionary<NSString *, NSString *> *)options
|
||||
checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
|
||||
keyPair:(FIRInstanceIDKeyPair *)keyPair {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_action = action;
|
||||
_authorizedEntity = [authorizedEntity copy];
|
||||
_scope = [scope copy];
|
||||
_options = [options copy];
|
||||
_checkinPreferences = checkinPreferences;
|
||||
_keyPair = keyPair;
|
||||
_completionHandlers = [NSMutableArray array];
|
||||
|
||||
_isExecuting = NO;
|
||||
_isFinished = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
_testBlock = nil;
|
||||
_authorizedEntity = nil;
|
||||
_scope = nil;
|
||||
_options = nil;
|
||||
_checkinPreferences = nil;
|
||||
_keyPair = nil;
|
||||
[_completionHandlers removeAllObjects];
|
||||
_completionHandlers = nil;
|
||||
}
|
||||
|
||||
- (void)addCompletionHandler:(FIRInstanceIDTokenOperationCompletion)handler {
|
||||
[self.completionHandlers addObject:handler];
|
||||
}
|
||||
|
||||
- (BOOL)isAsynchronous {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isExecuting {
|
||||
return _isExecuting;
|
||||
}
|
||||
|
||||
- (void)setExecuting:(BOOL)executing {
|
||||
[self willChangeValueForKey:@"isExecuting"];
|
||||
_isExecuting = executing;
|
||||
[self didChangeValueForKey:@"isExecuting"];
|
||||
}
|
||||
|
||||
- (BOOL)isFinished {
|
||||
return _isFinished;
|
||||
}
|
||||
|
||||
- (void)setFinished:(BOOL)finished {
|
||||
[self willChangeValueForKey:@"isFinished"];
|
||||
_isFinished = finished;
|
||||
[self didChangeValueForKey:@"isFinished"];
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
if (self.isCancelled) {
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationCancelled token:nil error:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
// Quickly validate whether or not the operation has all it needs to begin
|
||||
BOOL checkinfoAvailable = [self.checkinPreferences hasCheckinInfo];
|
||||
if (!checkinfoAvailable) {
|
||||
FIRInstanceIDErrorCode errorCode = kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn;
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationError
|
||||
token:nil
|
||||
error:[NSError errorWithFIRInstanceIDErrorCode:errorCode]];
|
||||
return;
|
||||
}
|
||||
|
||||
[self setExecuting:YES];
|
||||
|
||||
[self performTokenOperation];
|
||||
}
|
||||
|
||||
- (void)finishWithResult:(FIRInstanceIDTokenOperationResult)result
|
||||
token:(nullable NSString *)token
|
||||
error:(nullable NSError *)error {
|
||||
// Add a check to prevent this finish from being called more than once.
|
||||
if (self.isFinished) {
|
||||
return;
|
||||
}
|
||||
self.dataTask = nil;
|
||||
_result = result;
|
||||
// TODO(chliangGoogle): Call these in the main thread?
|
||||
for (FIRInstanceIDTokenOperationCompletion completionHandler in self.completionHandlers) {
|
||||
completionHandler(result, token, error);
|
||||
}
|
||||
|
||||
[self setExecuting:NO];
|
||||
[self setFinished:YES];
|
||||
}
|
||||
|
||||
- (void)cancel {
|
||||
[super cancel];
|
||||
[self.dataTask cancel];
|
||||
[self finishWithResult:FIRInstanceIDTokenOperationCancelled token:nil error:nil];
|
||||
}
|
||||
|
||||
- (void)performTokenOperation {
|
||||
}
|
||||
|
||||
#pragma mark - Request Construction
|
||||
+ (NSMutableURLRequest *)requestWithAuthHeader:(NSString *)authHeaderString {
|
||||
NSURL *url = [NSURL URLWithString:FIRInstanceIDRegisterServer()];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
||||
|
||||
// Add HTTP headers
|
||||
[request setValue:authHeaderString forHTTPHeaderField:@"Authorization"];
|
||||
[request setValue:FIRInstanceIDAppIdentifier() forHTTPHeaderField:@"app"];
|
||||
request.HTTPMethod = @"POST";
|
||||
return request;
|
||||
}
|
||||
|
||||
+ (NSMutableArray<FIRInstanceIDURLQueryItem *> *)standardQueryItemsWithDeviceID:(NSString *)deviceID
|
||||
scope:(NSString *)scope {
|
||||
NSMutableArray<FIRInstanceIDURLQueryItem *> *queryItems = [NSMutableArray arrayWithCapacity:8];
|
||||
|
||||
// E.g. X-osv=10.2.1
|
||||
NSString *systemVersion = FIRInstanceIDOperatingSystemVersion();
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"X-osv" value:systemVersion]];
|
||||
// E.g. device=
|
||||
if (deviceID) {
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"device" value:deviceID]];
|
||||
}
|
||||
// E.g. X-scope=fcm
|
||||
if (scope) {
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDParamScope
|
||||
value:scope]];
|
||||
}
|
||||
// E.g. plat=2
|
||||
NSString *platform = [NSString stringWithFormat:@"%ld", (long)kFIRInstanceIDPlatformVersionIOS];
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"plat" value:platform]];
|
||||
// E.g. app=com.myapp.foo
|
||||
NSString *appIdentifier = FIRInstanceIDAppIdentifier();
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"app" value:appIdentifier]];
|
||||
// E.g. app_ver=1.5
|
||||
NSString *appVersion = FIRInstanceIDCurrentAppVersion();
|
||||
[queryItems addObject:[FIRInstanceIDURLQueryItem queryItemWithName:@"app_ver" value:appVersion]];
|
||||
// E.g. X-cliv=fiid-1.2.3
|
||||
NSString *fcmLibraryVersion =
|
||||
[NSString stringWithFormat:@"fiid-%@", FIRInstanceIDCurrentGCMVersion()];
|
||||
if (fcmLibraryVersion.length) {
|
||||
FIRInstanceIDURLQueryItem *gcmLibVersion =
|
||||
[FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDParamFCMLibVersion
|
||||
value:fcmLibraryVersion];
|
||||
[queryItems addObject:gcmLibVersion];
|
||||
}
|
||||
|
||||
return queryItems;
|
||||
}
|
||||
|
||||
- (NSArray<FIRInstanceIDURLQueryItem *> *)queryItemsWithKeyPair:(FIRInstanceIDKeyPair *)keyPair {
|
||||
NSMutableArray<FIRInstanceIDURLQueryItem *> *items = [NSMutableArray arrayWithCapacity:3];
|
||||
// appid=
|
||||
NSString *instanceID = FIRInstanceIDAppIdentity(keyPair);
|
||||
[items addObject:[FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDParamInstanceID
|
||||
value:instanceID]];
|
||||
return items;
|
||||
}
|
||||
|
||||
#pragma mark - HTTP Header
|
||||
|
||||
+ (NSString *)HTTPAuthHeaderFromCheckin:(FIRInstanceIDCheckinPreferences *)checkin {
|
||||
NSString *deviceID = checkin.deviceID;
|
||||
NSString *secret = checkin.secretToken;
|
||||
return [NSString stringWithFormat:@"AidLogin %@:%@", deviceID, secret];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@class FIRInstanceIDAPNSInfo;
|
||||
@class FIRInstanceIDAuthKeychain;
|
||||
@class FIRInstanceIDTokenInfo;
|
||||
|
||||
/**
|
||||
* This class is responsible for retrieving and saving `FIRInstanceIDTokenInfo` objects from the
|
||||
* keychain. The keychain keys that are used are:
|
||||
* Account: <Main App Bundle ID> (e.g. com.mycompany.myapp)
|
||||
* Service: <Sender ID>:<Scope> (e.g. 1234567890:*)
|
||||
*/
|
||||
@interface FIRInstanceIDTokenStore : NSObject
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* Create a default InstanceID token store. Uses a valid Keychain object as it's
|
||||
* persistent backing store.
|
||||
*
|
||||
* @return A valid token store object.
|
||||
*/
|
||||
+ (instancetype)defaultStore;
|
||||
|
||||
- (instancetype)init __attribute__((unavailable("Use -initWithKeychain: instead.")));
|
||||
|
||||
/**
|
||||
* Initialize a token store object with a Keychain object. Used for testing.
|
||||
*
|
||||
* @param keychain The Keychain object to use as the backing store for tokens.
|
||||
*
|
||||
* @return A valid token store object with the given Keychain as backing store.
|
||||
*/
|
||||
- (instancetype)initWithKeychain:(FIRInstanceIDAuthKeychain *)keychain;
|
||||
|
||||
#pragma mark - Get
|
||||
|
||||
/**
|
||||
* Get the cached token from the Keychain.
|
||||
*
|
||||
* @param authorizedEntity The authorized entity for the token.
|
||||
* @param scope The scope for the token.
|
||||
*
|
||||
* @return The cached token info if any for the given authorizedEntity and scope else
|
||||
* nil.
|
||||
*/
|
||||
- (nullable FIRInstanceIDTokenInfo *)tokenInfoWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope;
|
||||
|
||||
/**
|
||||
* Return all cached token infos from the Keychain.
|
||||
*
|
||||
* @return The cached token infos, if any, that are stored in the Keychain.
|
||||
*/
|
||||
- (NSArray<FIRInstanceIDTokenInfo *> *)cachedTokenInfos;
|
||||
|
||||
#pragma mark - Save
|
||||
|
||||
/**
|
||||
* Save the instanceID token info to the persistent store.
|
||||
*
|
||||
* @param tokenInfo The token info to store.
|
||||
* @param handler The callback handler which is invoked when token saving is complete,
|
||||
* with an error if there is any.
|
||||
*/
|
||||
- (void)saveTokenInfo:(FIRInstanceIDTokenInfo *)tokenInfo
|
||||
handler:(nullable void (^)(NSError *))handler;
|
||||
|
||||
#pragma mark - Delete
|
||||
|
||||
/**
|
||||
* Remove the cached token from Keychain.
|
||||
*
|
||||
* @param authorizedEntity The authorized entity for the token.
|
||||
* @param scope The scope for the token.
|
||||
*
|
||||
*/
|
||||
- (void)removeTokenWithAuthorizedEntity:(NSString *)authorizedEntity scope:(NSString *)scope;
|
||||
|
||||
/**
|
||||
* Remove all the cached tokens from the Keychain.
|
||||
* @param handler The callback handler which is invoked when tokens deletion is complete,
|
||||
* with an error if there is any.
|
||||
*
|
||||
*/
|
||||
- (void)removeAllTokensWithHandler:(nullable void (^)(NSError *))handler;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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<FIRInstanceIDTokenInfo *> *)cachedTokenInfos {
|
||||
NSString *account = FIRInstanceIDAppIdentifier();
|
||||
NSArray<NSData *> *items =
|
||||
[self.keychain itemsMatchingService:kFIRInstanceIDKeychainWildcardIdentifier account:account];
|
||||
NSMutableArray<FIRInstanceIDTokenInfo *> *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: <Main App Bundle ID> (e.g. com.mycompany.myapp)
|
||||
// Service: <Sender ID>:<Scope> (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
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Stand-in for NSURLQueryItem, which is only available on iOS 8.0 and up.
|
||||
@interface FIRInstanceIDURLQueryItem : NSObject
|
||||
|
||||
@property(nonatomic, readonly) NSString *name;
|
||||
@property(nonatomic, readonly) NSString *value;
|
||||
|
||||
+ (instancetype)queryItemWithName:(NSString *)name value:(NSString *)value;
|
||||
- (instancetype)initWithName:(NSString *)name value:(NSString *)value;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Given an array of query items, construct a URL query. On iOS 8.0 and above, this will use
|
||||
* NSURLQueryItems internally to perform the string creation, and will be done manually in iOS
|
||||
* 7 and below.
|
||||
*/
|
||||
NSString *FIRInstanceIDQueryFromQueryItems(NSArray<FIRInstanceIDURLQueryItem *> *queryItems);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDURLQueryItem.h"
|
||||
|
||||
@implementation FIRInstanceIDURLQueryItem
|
||||
|
||||
+ (instancetype)queryItemWithName:(NSString *)name value:(NSString *)value {
|
||||
return [[[self class] alloc] initWithName:name value:value];
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name value:(NSString *)value {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = [name copy];
|
||||
_value = [value copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
NSString *FIRInstanceIDQueryFromQueryItems(NSArray<FIRInstanceIDURLQueryItem *> *queryItems) {
|
||||
if ([NSURLQueryItem class]) {
|
||||
// We are iOS 8.0 and above. Convert to NSURLQueryItems and get query that way
|
||||
// to take advantage of any automatic encoding
|
||||
NSMutableArray<NSURLQueryItem *> *urlItems =
|
||||
[NSMutableArray arrayWithCapacity:queryItems.count];
|
||||
for (FIRInstanceIDURLQueryItem *queryItem in queryItems) {
|
||||
[urlItems addObject:[NSURLQueryItem queryItemWithName:queryItem.name value:queryItem.value]];
|
||||
}
|
||||
NSURLComponents *components = [[NSURLComponents alloc] init];
|
||||
components.queryItems = urlItems;
|
||||
return components.query;
|
||||
} else {
|
||||
// We are on iOS 7.0. Manually create the query string
|
||||
NSMutableArray<NSString *> *pairs = [NSMutableArray arrayWithCapacity:queryItems.count];
|
||||
for (FIRInstanceIDURLQueryItem *queryItem in queryItems) {
|
||||
[pairs addObject:[NSString stringWithFormat:@"%@=%@", queryItem.name, queryItem.value]];
|
||||
}
|
||||
return [pairs componentsJoinedByString:@"&"];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
/// FIRMessaging Class that responds to the FIRMessaging SDK version selector.
|
||||
/// Verify at runtime if the class exists and implements the required method.
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDFCMSDKClassString;
|
||||
|
||||
/// locale key stored in GULUserDefaults
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDUserDefaultsKeyLocale;
|
||||
|
||||
#pragma mark - Test Blocks
|
||||
|
||||
/**
|
||||
* Response block for mock registration requests made during tests.
|
||||
*
|
||||
* @param data The data as returned by the mock request.
|
||||
* @param response The response as returned by the mock request.
|
||||
* @param error The error if any as returned by the mock request.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDURLRequestTestResponseBlock)(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error);
|
||||
|
||||
/**
|
||||
* Test block to mock registration requests response.
|
||||
*
|
||||
* @param request The request to mock response for.
|
||||
* @param response The response block for the mocked request.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDURLRequestTestBlock)(NSURLRequest *request,
|
||||
FIRInstanceIDURLRequestTestResponseBlock response);
|
||||
|
||||
#pragma mark - URL Helpers
|
||||
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDRegisterServer(void);
|
||||
|
||||
#pragma mark - Time
|
||||
|
||||
FOUNDATION_EXPORT int64_t FIRInstanceIDCurrentTimestampInSeconds(void);
|
||||
FOUNDATION_EXPORT int64_t FIRInstanceIDCurrentTimestampInMilliseconds(void);
|
||||
|
||||
#pragma mark - App Info
|
||||
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDCurrentAppVersion(void);
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDAppIdentifier(void);
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDFirebaseAppID(void);
|
||||
|
||||
#pragma mark - Device Info
|
||||
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDDeviceModel(void);
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDOperatingSystemVersion(void);
|
||||
FOUNDATION_EXPORT BOOL FIRInstanceIDHasLocaleChanged(void);
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
FOUNDATION_EXPORT BOOL FIRInstanceIDIsValidGCMScope(NSString *scope);
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDStringForAPNSDeviceToken(NSData *deviceToken);
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDAPNSTupleStringForTokenAndServerType(NSData *deviceToken,
|
||||
BOOL isSandbox);
|
||||
|
||||
#pragma mark - GCM Helpers
|
||||
/// Returns the current GCM version if GCM library is found else returns nil.
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDCurrentGCMVersion(void);
|
||||
|
||||
/// Returns the current locale. If GCM is present it queries GCM for a
|
||||
/// Context Manager specific locale. Otherwise, it returns the system's first
|
||||
/// preferred language (which may be set independently from locale). If the
|
||||
/// system returns no preferred languages, this method returns the most common
|
||||
/// language for the user's given locale. Guaranteed to return a nonnull value.
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDCurrentLocale(void);
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDUtilities.h"
|
||||
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
#import <sys/utsname.h>
|
||||
|
||||
#import <FirebaseCore/FIROptions.h>
|
||||
#import <GoogleUtilities/GULUserDefaults.h>
|
||||
#import "FIRInstanceID.h"
|
||||
#import "FIRInstanceIDConstants.h"
|
||||
#import "FIRInstanceIDLogger.h"
|
||||
|
||||
// Convert the macro to a string
|
||||
#define STR_EXPAND(x) #x
|
||||
#define STR(x) STR_EXPAND(x)
|
||||
|
||||
static NSString *const kFIRInstanceIDAPNSSandboxPrefix = @"s_";
|
||||
static NSString *const kFIRInstanceIDAPNSProdPrefix = @"p_";
|
||||
|
||||
/// FIRMessaging Class that responds to the FIRMessaging SDK version selector.
|
||||
/// Verify at runtime if the class exists and implements the required method.
|
||||
NSString *const kFIRInstanceIDFCMSDKClassString = @"FIRMessaging";
|
||||
|
||||
/// FIRMessaging selector that returns the current FIRMessaging library version.
|
||||
static NSString *const kFIRInstanceIDFCMSDKVersionSelectorString = @"FIRMessagingSDKVersion";
|
||||
|
||||
/// FIRMessaging selector that returns the current device locale.
|
||||
static NSString *const kFIRInstanceIDFCMSDKLocaleSelectorString = @"FIRMessagingSDKCurrentLocale";
|
||||
|
||||
NSString *const kFIRInstanceIDUserDefaultsKeyLocale =
|
||||
@"com.firebase.instanceid.user_defaults.locale"; // locale key stored in GULUserDefaults
|
||||
|
||||
/// Static values which will be populated once retrieved using
|
||||
/// |FIRInstanceIDRetrieveEnvironmentInfoFromFirebaseCore|.
|
||||
static NSString *operatingSystemVersion;
|
||||
static NSString *hardwareDeviceModel;
|
||||
|
||||
#pragma mark - URL Helpers
|
||||
|
||||
NSString *FIRInstanceIDRegisterServer() {
|
||||
return @"https://fcmtoken.googleapis.com/register";
|
||||
}
|
||||
|
||||
#pragma mark - Time
|
||||
|
||||
int64_t FIRInstanceIDCurrentTimestampInSeconds() {
|
||||
return (int64_t)[[NSDate date] timeIntervalSince1970];
|
||||
}
|
||||
|
||||
int64_t FIRInstanceIDCurrentTimestampInMilliseconds() {
|
||||
return (int64_t)(FIRInstanceIDCurrentTimestampInSeconds() * 1000.0);
|
||||
}
|
||||
|
||||
#pragma mark - App Info
|
||||
|
||||
NSString *FIRInstanceIDCurrentAppVersion() {
|
||||
NSString *version = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
|
||||
if (![version length]) {
|
||||
return @"";
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDAppIdentifier() {
|
||||
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
|
||||
if (!bundleIdentifier.length) {
|
||||
FIRInstanceIDLoggerError(kFIRInstanceIDMessageCodeUtilitiesMissingBundleIdentifier,
|
||||
@"The mainBundle's bundleIdentifier returned '%@'. Bundle identifier "
|
||||
@"expected to be non-empty.",
|
||||
bundleIdentifier);
|
||||
return @"";
|
||||
}
|
||||
return bundleIdentifier;
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDFirebaseAppID() {
|
||||
return [FIROptions defaultOptions].googleAppID;
|
||||
}
|
||||
|
||||
#pragma mark - Device Info
|
||||
// Get the device model from Firebase Core's App Environment Util
|
||||
NSString *FIRInstanceIDDeviceModel() {
|
||||
static dispatch_once_t once;
|
||||
dispatch_once(&once, ^{
|
||||
struct utsname systemInfo;
|
||||
if (uname(&systemInfo) == 0) {
|
||||
hardwareDeviceModel = [NSString stringWithUTF8String:systemInfo.machine];
|
||||
}
|
||||
});
|
||||
return hardwareDeviceModel;
|
||||
}
|
||||
|
||||
// Get the system version from Firebase Core's App Environment Util
|
||||
NSString *FIRInstanceIDOperatingSystemVersion() {
|
||||
#if TARGET_OS_IOS || TARGET_OS_TV
|
||||
return [UIDevice currentDevice].systemVersion;
|
||||
#elif TARGET_OS_OSX
|
||||
return [NSProcessInfo processInfo].operatingSystemVersionString;
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOL FIRInstanceIDHasLocaleChanged() {
|
||||
NSString *lastLocale =
|
||||
[[GULUserDefaults standardUserDefaults] stringForKey:kFIRInstanceIDUserDefaultsKeyLocale];
|
||||
NSString *currentLocale = FIRInstanceIDCurrentLocale();
|
||||
if (lastLocale) {
|
||||
if ([currentLocale isEqualToString:lastLocale]) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
BOOL FIRInstanceIDIsValidGCMScope(NSString *scope) {
|
||||
return [scope compare:kFIRInstanceIDScopeFirebaseMessaging
|
||||
options:NSCaseInsensitiveSearch] == NSOrderedSame;
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDStringForAPNSDeviceToken(NSData *deviceToken) {
|
||||
NSMutableString *APNSToken = [NSMutableString string];
|
||||
unsigned char *bytes = (unsigned char *)[deviceToken bytes];
|
||||
for (int i = 0; i < (int)deviceToken.length; i++) {
|
||||
[APNSToken appendFormat:@"%02x", bytes[i]];
|
||||
}
|
||||
return APNSToken;
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDAPNSTupleStringForTokenAndServerType(NSData *deviceToken, BOOL isSandbox) {
|
||||
if (deviceToken == nil) {
|
||||
// A nil deviceToken leads to an invalid tuple string, so return nil.
|
||||
return nil;
|
||||
}
|
||||
NSString *prefix = isSandbox ? kFIRInstanceIDAPNSSandboxPrefix : kFIRInstanceIDAPNSProdPrefix;
|
||||
NSString *APNSString = FIRInstanceIDStringForAPNSDeviceToken(deviceToken);
|
||||
NSString *APNSTupleString = [NSString stringWithFormat:@"%@%@", prefix, APNSString];
|
||||
|
||||
return APNSTupleString;
|
||||
}
|
||||
|
||||
#pragma mark - GCM Helpers
|
||||
|
||||
NSString *FIRInstanceIDCurrentGCMVersion() {
|
||||
Class versionClass = NSClassFromString(kFIRInstanceIDFCMSDKClassString);
|
||||
SEL versionSelector = NSSelectorFromString(kFIRInstanceIDFCMSDKVersionSelectorString);
|
||||
if ([versionClass respondsToSelector:versionSelector]) {
|
||||
IMP getVersionIMP = [versionClass methodForSelector:versionSelector];
|
||||
NSString *(*getVersion)(id, SEL) = (void *)getVersionIMP;
|
||||
return getVersion(versionClass, versionSelector);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDCurrentLocale() {
|
||||
Class localeClass = NSClassFromString(kFIRInstanceIDFCMSDKClassString);
|
||||
SEL localeSelector = NSSelectorFromString(kFIRInstanceIDFCMSDKLocaleSelectorString);
|
||||
|
||||
if ([localeClass respondsToSelector:localeSelector]) {
|
||||
IMP getLocaleIMP = [localeClass methodForSelector:localeSelector];
|
||||
NSString *(*getLocale)(id, SEL) = (void *)getLocaleIMP;
|
||||
NSString *fcmLocale = getLocale(localeClass, localeSelector);
|
||||
if (fcmLocale != nil) {
|
||||
return fcmLocale;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *systemLanguage = [[NSLocale preferredLanguages] firstObject];
|
||||
if (systemLanguage != nil) {
|
||||
return systemLanguage;
|
||||
}
|
||||
|
||||
if (@available(macOS 10.12, iOS 10.0, *)) {
|
||||
return [NSLocale currentLocale].languageCode;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Parsing utility for InstanceID Library versions. InstanceID lib follows semantic versioning.
|
||||
* This provides utilities to parse the library versions to enable features and do
|
||||
* updates based on appropriate library versions.
|
||||
*
|
||||
* Some example semantic versions are 1.0.1, 2.1.0, 2.1.1, 2.2.0-alpha1, 2.2.1-beta1
|
||||
*/
|
||||
|
||||
FOUNDATION_EXPORT NSString *FIRInstanceIDCurrentLibraryVersion(void);
|
||||
/// Returns the current Major version of GCM library.
|
||||
FOUNDATION_EXPORT int FIRInstanceIDCurrentLibraryVersionMajor(void);
|
||||
/// Returns the current Minor version of GCM library.
|
||||
FOUNDATION_EXPORT int FIRInstanceIDCurrentLibraryVersionMinor(void);
|
||||
/// Returns the current Patch version of GCM library.
|
||||
FOUNDATION_EXPORT int FIRInstanceIDCurrentLibraryVersionPatch(void);
|
||||
/// Returns YES if current library version is `beta` else NO.
|
||||
FOUNDATION_EXPORT BOOL FIRInstanceIDCurrentLibraryVersionIsBeta(void);
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 "FIRInstanceIDVersionUtilities.h"
|
||||
|
||||
// Convert the macro to a string
|
||||
#define STR(x) STR_EXPAND(x)
|
||||
#define STR_EXPAND(x) #x
|
||||
|
||||
static NSString *const kSemanticVersioningSeparator = @".";
|
||||
static NSString *const kBetaVersionPrefix = @"-beta";
|
||||
|
||||
static NSString *libraryVersion;
|
||||
|
||||
static int majorVersion;
|
||||
static int minorVersion;
|
||||
static int patchVersion;
|
||||
static int betaVersion;
|
||||
|
||||
void FIRInstanceIDParseCurrentLibraryVersion() {
|
||||
static NSArray *allVersions;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSMutableString *daylightVersion =
|
||||
[NSMutableString stringWithUTF8String:STR(FIRInstanceID_LIB_VERSION)];
|
||||
// Parse versions
|
||||
// major, minor, patch[-beta#]
|
||||
allVersions = [daylightVersion componentsSeparatedByString:kSemanticVersioningSeparator];
|
||||
if (allVersions.count == 3) {
|
||||
majorVersion = [allVersions[0] intValue];
|
||||
minorVersion = [allVersions[1] intValue];
|
||||
|
||||
// Parse patch and beta versions
|
||||
NSArray *patchAndBetaVersion =
|
||||
[allVersions[2] componentsSeparatedByString:kBetaVersionPrefix];
|
||||
if (patchAndBetaVersion.count == 2) {
|
||||
patchVersion = [patchAndBetaVersion[0] intValue];
|
||||
betaVersion = [patchAndBetaVersion[1] intValue];
|
||||
} else if (patchAndBetaVersion.count == 1) {
|
||||
patchVersion = [patchAndBetaVersion[0] intValue];
|
||||
}
|
||||
}
|
||||
|
||||
// Copy library version
|
||||
libraryVersion = [daylightVersion copy];
|
||||
});
|
||||
}
|
||||
|
||||
NSString *FIRInstanceIDCurrentLibraryVersion() {
|
||||
FIRInstanceIDParseCurrentLibraryVersion();
|
||||
return libraryVersion;
|
||||
}
|
||||
|
||||
int FIRInstanceIDCurrentLibraryVersionMajor() {
|
||||
FIRInstanceIDParseCurrentLibraryVersion();
|
||||
return majorVersion;
|
||||
}
|
||||
|
||||
int FIRInstanceIDCurrentLibraryVersionMinor() {
|
||||
FIRInstanceIDParseCurrentLibraryVersion();
|
||||
return minorVersion;
|
||||
}
|
||||
|
||||
int FIRInstanceIDCurrentLibraryVersionPatch() {
|
||||
FIRInstanceIDParseCurrentLibraryVersion();
|
||||
return patchVersion;
|
||||
}
|
||||
|
||||
BOOL FIRInstanceIDCurrentLibraryVersionIsBeta() {
|
||||
FIRInstanceIDParseCurrentLibraryVersion();
|
||||
return betaVersion > 0;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDDomain;
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FIRInstanceIDErrorCode) {
|
||||
// Unknown error.
|
||||
kFIRInstanceIDErrorCodeUnknown = 0,
|
||||
|
||||
// Http related errors.
|
||||
kFIRInstanceIDErrorCodeAuthentication = 1,
|
||||
kFIRInstanceIDErrorCodeNoAccess = 2,
|
||||
kFIRInstanceIDErrorCodeTimeout = 3,
|
||||
kFIRInstanceIDErrorCodeNetwork = 4,
|
||||
|
||||
// Another operation is in progress.
|
||||
kFIRInstanceIDErrorCodeOperationInProgress = 5,
|
||||
|
||||
// Failed to perform device check in.
|
||||
kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn = 6,
|
||||
|
||||
kFIRInstanceIDErrorCodeInvalidRequest = 7,
|
||||
|
||||
// InstanceID generic errors
|
||||
kFIRInstanceIDErrorCodeMissingDeviceID = 501,
|
||||
|
||||
// InstanceID Token specific errors
|
||||
kFIRInstanceIDErrorCodeMissingAPNSToken = 1001,
|
||||
kFIRInstanceIDErrorCodeMissingAPNSServerType = 1002,
|
||||
kFIRInstanceIDErrorCodeInvalidAuthorizedEntity = 1003,
|
||||
kFIRInstanceIDErrorCodeInvalidScope = 1004,
|
||||
kFIRInstanceIDErrorCodeInvalidStart = 1005,
|
||||
kFIRInstanceIDErrorCodeInvalidKeyPair = 1006,
|
||||
|
||||
// InstanceID Identity specific errors
|
||||
// Generic InstanceID keypair error
|
||||
kFIRInstanceIDErrorCodeMissingKeyPair = 2001,
|
||||
kFIRInstanceIDErrorCodeInvalidKeyPairTags = 2002,
|
||||
kFIRInstanceIDErrorCodeInvalidKeyPairCreationTime = 2005,
|
||||
kFIRInstanceIDErrorCodeInvalidIdentity = 2006,
|
||||
|
||||
};
|
||||
|
||||
@interface NSError (FIRInstanceID)
|
||||
|
||||
@property(nonatomic, readonly) FIRInstanceIDErrorCode instanceIDErrorCode;
|
||||
|
||||
+ (NSError *)errorWithFIRInstanceIDErrorCode:(FIRInstanceIDErrorCode)errorCode;
|
||||
|
||||
+ (NSError *)errorWithFIRInstanceIDErrorCode:(FIRInstanceIDErrorCode)errorCode
|
||||
userInfo:(NSDictionary *)userInfo;
|
||||
|
||||
+ (NSError *)FIRInstanceIDErrorMissingCheckin;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 "NSError+FIRInstanceID.h"
|
||||
|
||||
NSString *const kFIRInstanceIDDomain = @"com.firebase.iid";
|
||||
|
||||
@implementation NSError (FIRInstanceID)
|
||||
|
||||
- (FIRInstanceIDErrorCode)instanceIDErrorCode {
|
||||
return (FIRInstanceIDErrorCode)self.code;
|
||||
}
|
||||
|
||||
+ (NSError *)errorWithFIRInstanceIDErrorCode:(FIRInstanceIDErrorCode)errorCode {
|
||||
return [NSError errorWithFIRInstanceIDErrorCode:errorCode userInfo:nil];
|
||||
}
|
||||
|
||||
+ (NSError *)errorWithFIRInstanceIDErrorCode:(FIRInstanceIDErrorCode)errorCode
|
||||
userInfo:(NSDictionary *)userInfo {
|
||||
return [NSError errorWithDomain:kFIRInstanceIDDomain code:errorCode userInfo:userInfo];
|
||||
}
|
||||
|
||||
+ (NSError *)FIRInstanceIDErrorMissingCheckin {
|
||||
NSDictionary *userInfo = @{@"msg" : @"Missing device credentials. Retry later."};
|
||||
|
||||
return [NSError errorWithDomain:kFIRInstanceIDDomain
|
||||
code:kFIRInstanceIDErrorCodeMissingDeviceID
|
||||
userInfo:userInfo];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 <FirebaseInstanceID/FIRInstanceID.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class FIRInstanceIDCheckinPreferences;
|
||||
/**
|
||||
* Private API used by other Firebase SDKs.
|
||||
*/
|
||||
@interface FIRInstanceID ()
|
||||
|
||||
@property(nonatomic, readonly, strong) NSString *deviceAuthID;
|
||||
@property(nonatomic, readonly, strong) NSString *secretToken;
|
||||
@property(nonatomic, readonly, strong) NSString *versionInfo;
|
||||
|
||||
/**
|
||||
* Private initializer.
|
||||
*/
|
||||
- (instancetype)initPrivately;
|
||||
|
||||
/**
|
||||
* Returns a Firebase Messaging scoped token for the firebase app.
|
||||
*
|
||||
* @return Returns the stored token if the device has registered with Firebase Messaging, otherwise
|
||||
* returns nil.
|
||||
*/
|
||||
- (nullable NSString *)token;
|
||||
|
||||
/**
|
||||
* Verify if valid checkin preferences have been loaded in memory.
|
||||
*
|
||||
* @return YES if valid checkin preferences exist in memory else NO.
|
||||
*/
|
||||
- (BOOL)hasValidCheckinInfo;
|
||||
|
||||
/**
|
||||
* Try to load prefetched checkin preferences from the cache. This supports the use case where
|
||||
* InstanceID library has already obtained a valid checkin and we should be using that.
|
||||
*
|
||||
* This should be used as a last gasp effort to retreive any cached checkin preferences before
|
||||
* hitting the FIRMessaging backend to retrieve new preferences.
|
||||
*
|
||||
* Note this is only required because InstanceID and FIRMessaging both require checkin preferences
|
||||
* which need to be synced with each other.
|
||||
*
|
||||
* @return YES if successfully loaded cached checkin preferences into memory else NO.
|
||||
*/
|
||||
- (BOOL)tryToLoadValidCheckinInfo;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class FIRInstanceIDResult;
|
||||
/**
|
||||
* @memberof FIRInstanceID
|
||||
*
|
||||
* The scope to be used when fetching/deleting a token for Firebase Messaging.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDScopeFirebaseMessaging
|
||||
NS_SWIFT_NAME(InstanceIDScopeFirebaseMessaging);
|
||||
|
||||
#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
/**
|
||||
* Called when the system determines that tokens need to be refreshed.
|
||||
* This method is also called if Instance ID has been reset in which
|
||||
* case, tokens and FCM topic subscriptions also need to be refreshed.
|
||||
*
|
||||
* Instance ID service will throttle the refresh event across all devices
|
||||
* to control the rate of token updates on application servers.
|
||||
*/
|
||||
FOUNDATION_EXPORT const NSNotificationName kFIRInstanceIDTokenRefreshNotification
|
||||
NS_SWIFT_NAME(InstanceIDTokenRefresh);
|
||||
#else
|
||||
/**
|
||||
* Called when the system determines that tokens need to be refreshed.
|
||||
* This method is also called if Instance ID has been reset in which
|
||||
* case, tokens and FCM topic subscriptions also need to be refreshed.
|
||||
*
|
||||
* Instance ID service will throttle the refresh event across all devices
|
||||
* to control the rate of token updates on application servers.
|
||||
*/
|
||||
FOUNDATION_EXPORT NSString *const kFIRInstanceIDTokenRefreshNotification
|
||||
NS_SWIFT_NAME(InstanceIDTokenRefreshNotification);
|
||||
#endif // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
|
||||
/**
|
||||
* @related FIRInstanceID
|
||||
*
|
||||
* The completion handler invoked when the InstanceID token returns. If
|
||||
* the call fails we return the appropriate `error code` as described below.
|
||||
*
|
||||
* @param token The valid token as returned by InstanceID backend.
|
||||
*
|
||||
* @param error The error describing why generating a new token
|
||||
* failed. See the error codes below for a more detailed
|
||||
* description.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDTokenHandler)(NSString *__nullable token, NSError *__nullable error)
|
||||
NS_SWIFT_NAME(InstanceIDTokenHandler);
|
||||
|
||||
/**
|
||||
* @related FIRInstanceID
|
||||
*
|
||||
* The completion handler invoked when the InstanceID `deleteToken` returns. If
|
||||
* the call fails we return the appropriate `error code` as described below
|
||||
*
|
||||
* @param error The error describing why deleting the token failed.
|
||||
* See the error codes below for a more detailed description.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDDeleteTokenHandler)(NSError *error)
|
||||
NS_SWIFT_NAME(InstanceIDDeleteTokenHandler);
|
||||
|
||||
/**
|
||||
* @related FIRInstanceID
|
||||
*
|
||||
* The completion handler invoked when the app identity is created. If the
|
||||
* identity wasn't created for some reason we return the appropriate error code.
|
||||
*
|
||||
* @param identity A valid identity for the app instance, nil if there was an error
|
||||
* while creating an identity.
|
||||
* @param error The error if fetching the identity fails else nil.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDHandler)(NSString *__nullable identity, NSError *__nullable error)
|
||||
NS_SWIFT_NAME(InstanceIDHandler);
|
||||
|
||||
/**
|
||||
* @related FIRInstanceID
|
||||
*
|
||||
* The completion handler invoked when the app identity and all the tokens associated
|
||||
* with it are deleted. Returns a valid error object in case of failure else nil.
|
||||
*
|
||||
* @param error The error if deleting the identity and all the tokens associated with
|
||||
* it fails else nil.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDDeleteHandler)(NSError *__nullable error)
|
||||
NS_SWIFT_NAME(InstanceIDDeleteHandler);
|
||||
|
||||
/**
|
||||
* @related FIRInstanceID
|
||||
*
|
||||
* The completion handler invoked when the app identity and token are fetched. If the
|
||||
* identity wasn't created for some reason we return the appropriate error code.
|
||||
*
|
||||
* @param result The result containing an identity for the app instance and a valid token,
|
||||
* nil if there was an error while creating the result.
|
||||
* @param error The error if fetching the identity or token fails else nil.
|
||||
*/
|
||||
typedef void (^FIRInstanceIDResultHandler)(FIRInstanceIDResult *__nullable result,
|
||||
NSError *__nullable error)
|
||||
NS_SWIFT_NAME(InstanceIDResultHandler);
|
||||
|
||||
/**
|
||||
* Public errors produced by InstanceID.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, FIRInstanceIDError) {
|
||||
// Http related errors.
|
||||
|
||||
/// Unknown error.
|
||||
FIRInstanceIDErrorUnknown = 0,
|
||||
|
||||
/// Auth Error -- GCM couldn't validate request from this client.
|
||||
FIRInstanceIDErrorAuthentication = 1,
|
||||
|
||||
/// NoAccess -- InstanceID service cannot be accessed.
|
||||
FIRInstanceIDErrorNoAccess = 2,
|
||||
|
||||
/// Timeout -- Request to InstanceID backend timed out.
|
||||
FIRInstanceIDErrorTimeout = 3,
|
||||
|
||||
/// Network -- No network available to reach the servers.
|
||||
FIRInstanceIDErrorNetwork = 4,
|
||||
|
||||
/// OperationInProgress -- Another similar operation in progress,
|
||||
/// bailing this one.
|
||||
FIRInstanceIDErrorOperationInProgress = 5,
|
||||
|
||||
/// InvalidRequest -- Some parameters of the request were invalid.
|
||||
FIRInstanceIDErrorInvalidRequest = 7,
|
||||
} NS_SWIFT_NAME(InstanceIDError);
|
||||
|
||||
/**
|
||||
* A class contains the results of InstanceID and token query.
|
||||
*/
|
||||
NS_SWIFT_NAME(InstanceIDResult)
|
||||
@interface FIRInstanceIDResult : NSObject <NSCopying>
|
||||
|
||||
/**
|
||||
* An instanceID uniquely identifies the app instance.
|
||||
*/
|
||||
@property(nonatomic, readonly, copy) NSString *instanceID;
|
||||
|
||||
/*
|
||||
* Returns a Firebase Messaging scoped token for the firebase app.
|
||||
*/
|
||||
@property(nonatomic, readonly, copy) NSString *token;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Instance ID provides a unique identifier for each app instance and a mechanism
|
||||
* to authenticate and authorize actions (for example, sending an FCM message).
|
||||
*
|
||||
* Once an InstanceID is generated, the library periodically sends information about the
|
||||
* application and the device where it's running to the Firebase backend. To stop this. see
|
||||
* `[FIRInstanceID deleteIDWithHandler:]`.
|
||||
*
|
||||
* Instance ID is long lived but, may be reset if the device is not used for
|
||||
* a long time or the Instance ID service detects a problem.
|
||||
* If Instance ID is reset, the app will be notified via
|
||||
* `kFIRInstanceIDTokenRefreshNotification`.
|
||||
*
|
||||
* If the Instance ID has become invalid, the app can request a new one and
|
||||
* send it to the app server.
|
||||
* To prove ownership of Instance ID and to allow servers to access data or
|
||||
* services associated with the app, call
|
||||
* `[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler]`.
|
||||
*/
|
||||
NS_SWIFT_NAME(InstanceID)
|
||||
@interface FIRInstanceID : NSObject
|
||||
|
||||
/**
|
||||
* FIRInstanceID.
|
||||
*
|
||||
* @return A shared instance of FIRInstanceID.
|
||||
*/
|
||||
+ (instancetype)instanceID NS_SWIFT_NAME(instanceID());
|
||||
|
||||
/**
|
||||
* Unavailable. Use +instanceID instead.
|
||||
*/
|
||||
- (instancetype)init __attribute__((unavailable("Use +instanceID instead.")));
|
||||
|
||||
#pragma mark - Tokens
|
||||
|
||||
/**
|
||||
* Returns a result of app instance identifier InstanceID and a Firebase Messaging scoped token.
|
||||
* param handler The callback handler invoked when an app instanceID and a default token
|
||||
* are generated and returned. If instanceID and token fetching fail for some
|
||||
* reason the callback is invoked with nil `result` and the appropriate error.
|
||||
*/
|
||||
- (void)instanceIDWithHandler:(FIRInstanceIDResultHandler)handler;
|
||||
|
||||
/**
|
||||
* Returns a token that authorizes an Entity (example: cloud service) to perform
|
||||
* an action on behalf of the application identified by Instance ID.
|
||||
*
|
||||
* This is similar to an OAuth2 token except, it applies to the
|
||||
* application instance instead of a user.
|
||||
*
|
||||
* This is an asynchronous call. If the token fetching fails for some reason
|
||||
* we invoke the completion callback with nil `token` and the appropriate
|
||||
* error.
|
||||
*
|
||||
* This generates an Instance ID if it does not exist yet, which starts periodically sending
|
||||
* information to the Firebase backend (see `[FIRInstanceID getIDWithHandler:]`).
|
||||
*
|
||||
* Note, you can only have one `token` or `deleteToken` call for a given
|
||||
* authorizedEntity and scope at any point of time. Making another such call with the
|
||||
* same authorizedEntity and scope before the last one finishes will result in an
|
||||
* error with code `OperationInProgress`.
|
||||
*
|
||||
* @see FIRInstanceID deleteTokenWithAuthorizedEntity:scope:handler:
|
||||
*
|
||||
* @param authorizedEntity Entity authorized by the token.
|
||||
* @param scope Action authorized for authorizedEntity.
|
||||
* @param options The extra options to be sent with your token request. The
|
||||
* value for the `apns_token` should be the NSData object
|
||||
* passed to the UIApplicationDelegate's
|
||||
* `didRegisterForRemoteNotificationsWithDeviceToken` method.
|
||||
* The value for `apns_sandbox` should be a boolean (or an
|
||||
* NSNumber representing a BOOL in Objective-C) set to true if
|
||||
* your app is a debug build, which means that the APNs
|
||||
* device token is for the sandbox environment. It should be
|
||||
* set to false otherwise. If the `apns_sandbox` key is not
|
||||
* provided, an automatically-detected value shall be used.
|
||||
* @param handler The callback handler which is invoked when the token is
|
||||
* successfully fetched. In case of success a valid `token` and
|
||||
* `nil` error are returned. In case of any error the `token`
|
||||
* is nil and a valid `error` is returned. The valid error
|
||||
* codes have been documented above.
|
||||
*/
|
||||
- (void)tokenWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
options:(nullable NSDictionary *)options
|
||||
handler:(FIRInstanceIDTokenHandler)handler;
|
||||
|
||||
/**
|
||||
* Revokes access to a scope (action) for an entity previously
|
||||
* authorized by `[FIRInstanceID tokenWithAuthorizedEntity:scope:options:handler]`.
|
||||
*
|
||||
* This is an asynchronous call. Call this on the main thread since InstanceID lib
|
||||
* is not thread safe. In case token deletion fails for some reason we invoke the
|
||||
* `handler` callback passed in with the appropriate error code.
|
||||
*
|
||||
* Note, you can only have one `token` or `deleteToken` call for a given
|
||||
* authorizedEntity and scope at a point of time. Making another such call with the
|
||||
* same authorizedEntity and scope before the last one finishes will result in an error
|
||||
* with code `OperationInProgress`.
|
||||
*
|
||||
* @param authorizedEntity Entity that must no longer have access.
|
||||
* @param scope Action that entity is no longer authorized to perform.
|
||||
* @param handler The handler that is invoked once the unsubscribe call ends.
|
||||
* In case of error an appropriate error object is returned
|
||||
* else error is nil.
|
||||
*/
|
||||
- (void)deleteTokenWithAuthorizedEntity:(NSString *)authorizedEntity
|
||||
scope:(NSString *)scope
|
||||
handler:(FIRInstanceIDDeleteTokenHandler)handler;
|
||||
|
||||
#pragma mark - Identity
|
||||
|
||||
/**
|
||||
* Asynchronously fetch a stable identifier that uniquely identifies the app
|
||||
* instance. If the identifier has been revoked or has expired, this method will
|
||||
* return a new identifier.
|
||||
*
|
||||
* Once an InstanceID is generated, the library periodically sends information about the
|
||||
* application and the device where it's running to the Firebase backend. To stop this. see
|
||||
* `[FIRInstanceID deleteIDWithHandler:]`.
|
||||
*
|
||||
* @param handler The handler to invoke once the identifier has been fetched.
|
||||
* In case of error an appropriate error object is returned else
|
||||
* a valid identifier is returned and a valid identifier for the
|
||||
* application instance.
|
||||
*/
|
||||
- (void)getIDWithHandler:(FIRInstanceIDHandler)handler NS_SWIFT_NAME(getID(handler:));
|
||||
|
||||
/**
|
||||
* Resets Instance ID and revokes all tokens.
|
||||
*
|
||||
* This method also triggers a request to fetch a new Instance ID and Firebase Messaging scope
|
||||
* token. Please listen to kFIRInstanceIDTokenRefreshNotification when the new ID and token are
|
||||
* ready.
|
||||
*
|
||||
* This stops the periodic sending of data to the Firebase backend that began when the Instance ID
|
||||
* was generated. No more data is sent until another library calls Instance ID internally again
|
||||
* (like FCM, RemoteConfig or Analytics) or user explicitly calls Instance ID APIs to get an
|
||||
* Instance ID and token again.
|
||||
*/
|
||||
- (void)deleteIDWithHandler:(FIRInstanceIDDeleteHandler)handler NS_SWIFT_NAME(deleteID(handler:));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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 "FIRInstanceID.h"
|
||||
Generated
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
Generated
+223
@@ -0,0 +1,223 @@
|
||||
# Firebase iOS Open Source Development [](https://travis-ci.org/firebase/firebase-ios-sdk)
|
||||
|
||||
This repository contains a subset of the Firebase iOS SDK source. It currently
|
||||
includes FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore,
|
||||
FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging,
|
||||
FirebaseInAppMessagingDisplay, FirebaseMessaging and FirebaseStorage.
|
||||
|
||||
The repository also includes GoogleUtilities source. The
|
||||
[GoogleUtilities](GoogleUtilities/README.md) pod is
|
||||
a set of utilities used by Firebase and other Google products.
|
||||
|
||||
Firebase is an app development platform with tools to help you build, grow and
|
||||
monetize your app. More information about Firebase can be found at
|
||||
[https://firebase.google.com](https://firebase.google.com).
|
||||
|
||||
## Installation
|
||||
|
||||
See the three subsections for details about three different installation methods.
|
||||
1. [Standard pod install](README.md#standard-pod-install)
|
||||
1. [Installing from the GitHub repo](README.md#installing-from-github)
|
||||
1. [Experimental Carthage](README.md#carthage-ios-only)
|
||||
|
||||
### Standard pod install
|
||||
|
||||
Go to
|
||||
[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).
|
||||
|
||||
### Installing from GitHub
|
||||
|
||||
For releases starting with 5.0.0, the source for each release is also deployed
|
||||
to CocoaPods master and available via standard
|
||||
[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod).
|
||||
|
||||
These instructions can be used to access the Firebase repo at other branches,
|
||||
tags, or commits.
|
||||
|
||||
#### Background
|
||||
|
||||
See
|
||||
[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
|
||||
for instructions and options about overriding pod source locations.
|
||||
|
||||
#### Accessing Firebase Source Snapshots
|
||||
|
||||
All of the official releases are tagged in this repo and available via CocoaPods. To access a local
|
||||
source snapshot or unreleased branch, use Podfile directives like the following:
|
||||
|
||||
To access FirebaseFirestore via a branch:
|
||||
```
|
||||
pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
|
||||
pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
|
||||
```
|
||||
|
||||
To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do:
|
||||
|
||||
```
|
||||
pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
|
||||
pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
|
||||
```
|
||||
|
||||
### Carthage (iOS only)
|
||||
|
||||
Instructions for the experimental Carthage distribution are at
|
||||
[Carthage](Carthage.md).
|
||||
|
||||
### Rome
|
||||
|
||||
Instructions for installing binary frameworks via
|
||||
[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md).
|
||||
|
||||
## Development
|
||||
|
||||
To develop Firebase software in this repository, ensure that you have at least
|
||||
the following software:
|
||||
|
||||
* Xcode 10.1 (or later)
|
||||
* CocoaPods 1.7.2 (or later)
|
||||
|
||||
For the pod that you want to develop:
|
||||
|
||||
`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open`
|
||||
|
||||
Firestore and Functions have self contained Xcode projects. See
|
||||
[Firestore/README.md](Firestore/README.md) and
|
||||
[Functions/README.md](Functions/README.md).
|
||||
|
||||
### Adding a New Firebase Pod
|
||||
|
||||
See [AddNewPod.md](AddNewPod.md).
|
||||
|
||||
### Code Formatting
|
||||
|
||||
To ensure that the code is formatted consistently, run the script
|
||||
[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh)
|
||||
before creating a PR.
|
||||
|
||||
Travis will verify that any code changes are done in a style compliant way. Install
|
||||
`clang-format` and `swiftformat`.
|
||||
These commands will get the right versions:
|
||||
|
||||
```
|
||||
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb
|
||||
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/3dfea1004e0736754bbf49673cca8aaed8a94089/Formula/swiftformat.rb
|
||||
```
|
||||
|
||||
Note: if you already have a newer version of these installed you may need to
|
||||
`brew switch` to this version.
|
||||
|
||||
### Running Unit Tests
|
||||
|
||||
Select a scheme and press Command-u to build a component and run its unit tests.
|
||||
|
||||
#### Viewing Code Coverage
|
||||
|
||||
First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`.
|
||||
|
||||
After running the `AllUnitTests_iOS` scheme in Xcode, execute
|
||||
`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output`
|
||||
at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results.
|
||||
|
||||
### Running Sample Apps
|
||||
In order to run the sample apps and integration tests, you'll need valid
|
||||
`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist
|
||||
files without real values, but can be replaced with real plist files. To get your own
|
||||
`GoogleService-Info.plist` files:
|
||||
|
||||
1. Go to the [Firebase Console](https://console.firebase.google.com/)
|
||||
2. Create a new Firebase project, if you don't already have one
|
||||
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
|
||||
identifier (e.g. `com.google.Database-Example`)
|
||||
4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file
|
||||
(e.g. in [Example/Database/App/](Example/Database/App/));
|
||||
|
||||
Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require
|
||||
special Apple capabilities, and you will have to change the sample app to use a unique bundle
|
||||
identifier that you can control in your own Apple Developer account.
|
||||
|
||||
## Specific Component Instructions
|
||||
See the sections below for any special instructions for those components.
|
||||
|
||||
### Firebase Auth
|
||||
|
||||
If you're doing specific Firebase Auth development, see
|
||||
[the Auth Sample README](Example/Auth/README.md) for instructions about
|
||||
building and running the FirebaseAuth pod along with various samples and tests.
|
||||
|
||||
### Firebase Database
|
||||
|
||||
To run the Database Integration tests, make your database authentication rules
|
||||
[public](https://firebase.google.com/docs/database/security/quickstart).
|
||||
|
||||
### Firebase Storage
|
||||
|
||||
To run the Storage Integration tests, follow the instructions in
|
||||
[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m).
|
||||
|
||||
#### Push Notifications
|
||||
|
||||
Push notifications can only be delivered to specially provisioned App IDs in the developer portal.
|
||||
In order to actually test receiving push notifications, you will need to:
|
||||
|
||||
1. Change the bundle identifier of the sample app to something you own in your Apple Developer
|
||||
account, and enable that App ID for push notifications.
|
||||
2. You'll also need to
|
||||
[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs)
|
||||
at **Project Settings > Cloud Messaging > [Your Firebase App]**.
|
||||
3. Ensure your iOS device is added to your Apple Developer portal as a test device.
|
||||
|
||||
#### iOS Simulator
|
||||
|
||||
The iOS Simulator cannot register for remote notifications, and will not receive push notifications.
|
||||
In order to receive push notifications, you'll have to follow the steps above and run the app on a
|
||||
physical device.
|
||||
|
||||
## Community Supported Efforts
|
||||
|
||||
We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are
|
||||
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
|
||||
participate in the Firebase community.
|
||||
|
||||
### macOS and tvOS
|
||||
Thanks to contributions from the community, FirebaseAuth, FirebaseCore, FirebaseDatabase, FirebaseMessaging,
|
||||
FirebaseFirestore, FirebaseFunctions and FirebaseStorage now compile, run unit tests, and work on
|
||||
macOS and tvOS.
|
||||
|
||||
For tvOS, checkout the [Sample](Example/tvOSSample).
|
||||
|
||||
Keep in mind that macOS and tvOS are not officially supported by Firebase, and this repository is
|
||||
actively developed primarily for iOS. While we can catch basic unit test issues with Travis, there
|
||||
may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter
|
||||
this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
|
||||
|
||||
Note that the Firebase pod is not available for macOS and tvOS.
|
||||
|
||||
To install, add a subset of the following to the Podfile:
|
||||
|
||||
```
|
||||
pod 'FirebaseAuth'
|
||||
pod 'FirebaseCore'
|
||||
pod 'FirebaseDatabase'
|
||||
pod 'FirebaseFirestore'
|
||||
pod 'FirebaseFunctions'
|
||||
pod 'FirebaseMessaging'
|
||||
pod 'FirebaseStorage'
|
||||
```
|
||||
|
||||
## Roadmap
|
||||
|
||||
See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source
|
||||
plans and directions.
|
||||
|
||||
## Contributing
|
||||
|
||||
See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase
|
||||
iOS SDK.
|
||||
|
||||
## License
|
||||
|
||||
The contents of this repository is licensed under the
|
||||
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
Your use of Firebase is governed by the
|
||||
[Terms of Service for Firebase Services](https://firebase.google.com/terms/).
|
||||
Reference in New Issue
Block a user