Archived
Push from command line
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user