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

258 lines
8.9 KiB

/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstanceIDTokenOperation.h"
#import <FirebaseInstallations/FirebaseInstallations.h>
#import "FIRInstanceIDCheckinPreferences.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) NSString *instanceID;
@property(atomic, strong) NSURLSessionDataTask *dataTask;
@property(readonly, strong)
NSMutableArray<FIRInstanceIDTokenOperationCompletion> *completionHandlers;
@property(atomic, strong, nullable) NSString *FISAuthToken;
// 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
instanceID:(NSString *)instanceID {
self = [super init];
if (self) {
_action = action;
_authorizedEntity = [authorizedEntity copy];
_scope = [scope copy];
_options = [options copy];
_checkinPreferences = checkinPreferences;
_instanceID = instanceID;
_completionHandlers = [NSMutableArray array];
_isExecuting = NO;
_isFinished = NO;
}
return self;
}
- (void)dealloc {
_testBlock = nil;
_authorizedEntity = nil;
_scope = nil;
_options = nil;
_checkinPreferences = nil;
_instanceID = 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];
[[FIRInstallations installations]
authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult,
NSError *_Nullable error) {
if (tokenResult.authToken.length > 0) {
self.FISAuthToken = tokenResult.authToken;
[self performTokenOperation];
} else {
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error];
}
}];
}
- (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 {
}
- (NSMutableURLRequest *)tokenRequest {
NSString *authHeader =
[FIRInstanceIDTokenOperation HTTPAuthHeaderFromCheckin:self.checkinPreferences];
return [[self class] requestWithAuthHeader:authHeader FISAuthToken:self.FISAuthToken];
}
#pragma mark - Request Construction
+ (NSMutableURLRequest *)requestWithAuthHeader:(NSString *)authHeaderString
FISAuthToken:(NSString *)FISAuthToken {
NSURL *url = [NSURL URLWithString:FIRInstanceIDRegisterServer()];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// Add HTTP headers
[request setValue:authHeaderString forHTTPHeaderField:@"Authorization"];
[request setValue:FIRInstanceIDAppIdentifier() forHTTPHeaderField:@"app"];
if (FISAuthToken) {
[request setValue:FISAuthToken forHTTPHeaderField:@"x-goog-firebase-installations-auth"];
}
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 *> *)queryItemsWithInstanceID:(NSString *)instanceID {
return @[ [FIRInstanceIDURLQueryItem queryItemWithName:kFIRInstanceIDParamInstanceID
value:instanceID] ];
}
#pragma mark - HTTP Header
+ (NSString *)HTTPAuthHeaderFromCheckin:(FIRInstanceIDCheckinPreferences *)checkin {
NSString *deviceID = checkin.deviceID;
NSString *secret = checkin.secretToken;
return [NSString stringWithFormat:@"AidLogin %@:%@", deviceID, secret];
}
@end