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.
 
 
 
 

267 lines
9.5 KiB

/*
* Copyright 2017 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 "FIRAuthAPNSTokenManager.h"
#import <FirebaseCore/FIRLogger.h>
#import "FIRAuthAPNSToken.h"
#import "FIRAuthGlobalWorkQueue.h"
NS_ASSUME_NONNULL_BEGIN
/** @var kRegistrationTimeout
@brief Timeout for registration for remote notification.
@remarks Once we start to handle `application:didFailToRegisterForRemoteNotificationsWithError:`
we probably don't have to use timeout at all.
*/
static const NSTimeInterval kRegistrationTimeout = 5;
/** @var kLegacyRegistrationTimeout
@brief Timeout for registration for remote notification on iOS 7.
*/
static const NSTimeInterval kLegacyRegistrationTimeout = 30;
@implementation FIRAuthAPNSTokenManager {
/** @var _application
@brief The @c UIApplication to request the token from.
*/
UIApplication *_application;
/** @var _pendingCallbacks
@brief The list of all pending callbacks for the APNs token.
*/
NSMutableArray<FIRAuthAPNSTokenCallback> *_pendingCallbacks;
}
- (instancetype)initWithApplication:(UIApplication *)application {
self = [super init];
if (self) {
_application = application;
_timeout = [_application respondsToSelector:@selector(registerForRemoteNotifications)] ?
kRegistrationTimeout : kLegacyRegistrationTimeout;
}
return self;
}
- (void)getTokenWithCallback:(FIRAuthAPNSTokenCallback)callback {
if (_token) {
callback(_token, nil);
return;
}
if (_pendingCallbacks) {
[_pendingCallbacks addObject:callback];
return;
}
_pendingCallbacks =
[[NSMutableArray<FIRAuthAPNSTokenCallback> alloc] initWithObjects:callback, nil];
dispatch_async(dispatch_get_main_queue(), ^{
if ([self->_application respondsToSelector:@selector(registerForRemoteNotifications)]) {
[self->_application registerForRemoteNotifications];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#if TARGET_OS_IOS
[self->_application registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert];
#endif // TARGET_OS_IOS
#pragma clang diagnostic pop
}
});
NSArray<FIRAuthAPNSTokenCallback> *applicableCallbacks = _pendingCallbacks;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_timeout * NSEC_PER_SEC)),
FIRAuthGlobalWorkQueue(), ^{
// Only cancel if the pending callbacks remain the same, i.e., not triggered yet.
if (applicableCallbacks == self->_pendingCallbacks) {
[self callBackWithToken:nil error:nil];
}
});
}
- (void)setToken:(nullable FIRAuthAPNSToken *)token {
if (!token) {
_token = nil;
return;
}
if (token.type == FIRAuthAPNSTokenTypeUnknown) {
static FIRAuthAPNSTokenType detectedTokenType = FIRAuthAPNSTokenTypeUnknown;
if (detectedTokenType == FIRAuthAPNSTokenTypeUnknown) {
detectedTokenType =
[[self class] isProductionApp] ? FIRAuthAPNSTokenTypeProd : FIRAuthAPNSTokenTypeSandbox;
}
token = [[FIRAuthAPNSToken alloc] initWithData:token.data type:detectedTokenType];
}
_token = token;
[self callBackWithToken:token error:nil];
}
- (void)cancelWithError:(NSError *)error {
[self callBackWithToken:nil error:error];
}
#pragma mark - Internal methods
/** @fn callBack
@brief Calls back all pending callbacks with APNs token or error.
@param token The APNs token if one is available.
@param error The error occurred, if any.
*/
- (void)callBackWithToken:(nullable FIRAuthAPNSToken *)token error:(nullable NSError *)error {
if (!_pendingCallbacks) {
return;
}
NSArray<FIRAuthAPNSTokenCallback> *allCallbacks = _pendingCallbacks;
_pendingCallbacks = nil;
for (FIRAuthAPNSTokenCallback callback in allCallbacks) {
callback(token, error);
}
};
/** @fn isProductionApp
@brief Whether or not the app has production (versus sandbox) provisioning profile.
@remarks This method is adapted from @c FIRInstanceID .
*/
+ (BOOL)isProductionApp {
const BOOL defaultAppTypeProd = YES;
NSError *error = nil;
Class envClass = NSClassFromString(@"FIRAppEnvironmentUtil");
SEL isSimulatorSelector = NSSelectorFromString(@"isSimulator");
if ([envClass respondsToSelector:isSimulatorSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([envClass performSelector:isSimulatorSelector]) {
#pragma clang diagnostic pop
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000006",
@"Assuming prod APNs token type on simulator.");
return defaultAppTypeProd;
}
}
NSString *path = [[[NSBundle mainBundle] bundlePath]
stringByAppendingPathComponent:@"embedded.mobileprovision"];
// Apps distributed via AppStore or TestFlight use the Production APNS certificates.
SEL isFromAppStoreSelector = NSSelectorFromString(@"isFromAppStore");
if ([envClass respondsToSelector:isFromAppStoreSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([envClass performSelector:isFromAppStoreSelector]) {
#pragma clang diagnostic pop
return defaultAppTypeProd;
}
}
SEL isAppStoreReceiptSandboxSelector = NSSelectorFromString(@"isAppStoreReceiptSandbox");
if ([envClass respondsToSelector:isAppStoreReceiptSandboxSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([envClass performSelector:isAppStoreReceiptSandboxSelector] && !path.length) {
#pragma clang diagnostic pop
// Distributed via TestFlight
return defaultAppTypeProd;
}
}
NSMutableData *profileData = [NSMutableData dataWithContentsOfFile:path options:0 error:&error];
if (!profileData.length || error) {
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000007",
@"Error while reading embedded mobileprovision %@", error);
return defaultAppTypeProd;
}
// The "embedded.mobileprovision" sometimes contains characters with value 0, which signals the
// end of a c-string and halts the ASCII parser, or with value > 127, which violates strict 7-bit
// ASCII. Replace any 0s or invalid characters in the input.
uint8_t *profileBytes = (uint8_t *)profileData.bytes;
for (int i = 0; i < profileData.length; i++) {
uint8_t currentByte = profileBytes[i];
if (!currentByte || currentByte > 127) {
profileBytes[i] = '.';
}
}
NSString *embeddedProfile = [[NSString alloc] initWithBytesNoCopy:profileBytes
length:profileData.length
encoding:NSASCIIStringEncoding
freeWhenDone:NO];
if (error || !embeddedProfile.length) {
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000008",
@"Error while reading embedded mobileprovision %@", error);
return defaultAppTypeProd;
}
NSScanner *scanner = [NSScanner scannerWithString:embeddedProfile];
NSString *plistContents;
if ([scanner scanUpToString:@"<plist" intoString:nil]) {
if ([scanner scanUpToString:@"</plist>" intoString:&plistContents]) {
plistContents = [plistContents stringByAppendingString:@"</plist>"];
}
}
if (!plistContents.length) {
return defaultAppTypeProd;
}
NSData *data = [plistContents dataUsingEncoding:NSUTF8StringEncoding];
if (!data.length) {
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000009",
@"Couldn't read plist fetched from embedded mobileprovision");
return defaultAppTypeProd;
}
NSError *plistMapError;
id plistData = [NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListImmutable
format:nil
error:&plistMapError];
if (plistMapError || ![plistData isKindOfClass:[NSDictionary class]]) {
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000010",
@"Error while converting assumed plist to dict %@",
plistMapError.localizedDescription);
return defaultAppTypeProd;
}
NSDictionary *plistMap = (NSDictionary *)plistData;
if ([plistMap valueForKeyPath:@"ProvisionedDevices"]) {
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000011",
@"Provisioning profile has specifically provisioned devices, "
@"most likely a Dev profile.");
}
NSString *apsEnvironment = [plistMap valueForKeyPath:@"Entitlements.aps-environment"];
FIRLogDebug(kFIRLoggerAuth, @"I-AUT000012",
@"APNS Environment in profile: %@", apsEnvironment);
// No aps-environment in the profile.
if (!apsEnvironment.length) {
FIRLogInfo(kFIRLoggerAuth, @"I-AUT000013",
@"No aps-environment set. If testing on a device APNS is not "
@"correctly configured. Please recheck your provisioning profiles.");
return defaultAppTypeProd;
}
if ([apsEnvironment isEqualToString:@"development"]) {
return NO;
}
return defaultAppTypeProd;
}
@end
NS_ASSUME_NONNULL_END