mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
given the good feedback we got, this is take 2 of the BITAuthenticator interface. It's simplified, cleaned up and now allows full control over the authentication process. Major changes: * manual mode Authenticator provides the bits to show the viewController to identify the user as well as to trigger validation on behalf of the developer. * process separation identification and app-usage-restriction are now 2 completely separated things. * public identifier Authenticator now allows the developer to query parts of the information, e.g. the UDID or the user's emailaddy once identified
677 lines
30 KiB
Objective-C
677 lines
30 KiB
Objective-C
/*
|
|
* Author: Stephan Diederich
|
|
*
|
|
* Copyright (c) 2013 HockeyApp, Bit Stadium GmbH.
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
|
|
#import "BITAuthenticator.h"
|
|
#import "HockeySDK.h"
|
|
#import "HockeySDKPrivate.h"
|
|
#import "BITAuthenticator_Private.h"
|
|
#import "BITHTTPOperation.h"
|
|
#import "BITHockeyAppClient.h"
|
|
#import "BITHockeyHelper.h"
|
|
|
|
static NSString* const kBITAuthenticatorUUIDKey = @"BITAuthenticatorUUIDKey";
|
|
static NSString* const kBITAuthenticatorIdentifierKey = @"BITAuthenticatorIdentifierKey";
|
|
static NSString* const kBITAuthenticatorIdentifierTypeKey = @"BITAuthenticatorIdentifierTypeKey";
|
|
static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthenticatorLastAuthenticatedVersionKey";
|
|
static NSString* const kBITAuthenticatorUserEmailKey = @"BITAuthenticatorUserEmailKey";
|
|
|
|
//deprecated
|
|
static NSString* const kBITAuthenticatorAuthTokenKey = @"BITAuthenticatorAuthTokenKey";
|
|
static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAuthTokenTypeKey";
|
|
|
|
|
|
@implementation BITAuthenticator {
|
|
id _appDidBecomeActiveObserver;
|
|
id _appWillResignActiveObserver;
|
|
UIViewController *_authenticationController;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self unregisterObservers];
|
|
}
|
|
|
|
- (instancetype) initWithAppIdentifier:(NSString *)appIdentifier isAppStoreEnvironment:(BOOL)isAppStoreEnvironment {
|
|
self = [super initWithAppIdentifier:appIdentifier isAppStoreEnvironment:isAppStoreEnvironment];
|
|
if( self ) {
|
|
_webpageURL = [NSURL URLWithString:@"https://rink.hockeyapp.net/"];
|
|
|
|
_identificationType = BITAuthenticatorIdentificationTypeAnonymous;
|
|
_automaticMode = YES;
|
|
_restrictApplicationUsage = NO;
|
|
_restrictionEnforcementFrequency = BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark - BITHockeyBaseManager overrides
|
|
- (void)startManager {
|
|
//disabled in the appStore
|
|
if([self isAppStoreEnvironment]) return;
|
|
|
|
switch ([[UIApplication sharedApplication] applicationState]) {
|
|
case UIApplicationStateActive:
|
|
[self authenticate];
|
|
break;
|
|
case UIApplicationStateBackground:
|
|
case UIApplicationStateInactive:
|
|
// do nothing, wait for active state
|
|
break;
|
|
}
|
|
|
|
[self registerObservers];
|
|
}
|
|
|
|
#pragma mark -
|
|
- (void) authenticate {
|
|
//when running in manual mode, we don't actually do anything ourselves
|
|
if(!self.automaticMode) return;
|
|
|
|
[self identifyWithCompletion:^(BOOL identified, NSError *error) {
|
|
if(identified) {
|
|
if([self needsValidation]) {
|
|
[self validateWithCompletion:^(BOOL validated, NSError *error) {
|
|
if(validated) {
|
|
[_authenticationController dismissViewControllerAnimated:YES completion:nil];
|
|
_authenticationController = nil;
|
|
} else {
|
|
[self storeInstallationIdentifier:nil withType:self.identificationType];
|
|
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
|
|
message:error.localizedDescription
|
|
delegate:nil
|
|
cancelButtonTitle:BITHockeyLocalizedString(@"HockeyOK")
|
|
otherButtonTitles:nil];
|
|
[alertView show];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
[self authenticate];
|
|
});
|
|
}
|
|
}];
|
|
} else {
|
|
[_authenticationController dismissViewControllerAnimated:YES completion:nil];
|
|
_authenticationController = nil;
|
|
}
|
|
} else {
|
|
BITHockeyLog(@"Failed to identify. Error: %@", error);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (BOOL) needsValidation {
|
|
if(BITAuthenticatorIdentificationTypeAnonymous == self.identificationType) {
|
|
return NO;
|
|
}
|
|
if(NO == self.restrictApplicationUsage) {
|
|
return NO;
|
|
}
|
|
if(YES == self.isValidated) {
|
|
return NO;
|
|
}
|
|
if(self.restrictionEnforcementFrequency == BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch &&
|
|
![self.executableUUID isEqualToString:self.lastAuthenticatedVersion]) {
|
|
return YES;
|
|
}
|
|
if(self.restrictionEnforcementFrequency == BITAuthenticatorAppRestrictionEnforcementOnAppActive) {
|
|
return YES;
|
|
}
|
|
return NO;
|
|
}
|
|
- (void) identifyWithCompletion:(void (^)(BOOL identified, NSError *))completion {
|
|
if(_authenticationController) {
|
|
BITHockeyLog(@"Authentication controller already visible. Ingoring identify request");
|
|
if(completion) completion(NO, nil);
|
|
return;
|
|
}
|
|
//first check if the stored identification type matches the one currently configured
|
|
NSString *storedTypeString = [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierTypeKey];
|
|
NSString *configuredTypeString = [self.class stringForIdentificationType:self.identificationType];
|
|
if(storedTypeString && ![storedTypeString isEqualToString:configuredTypeString]) {
|
|
BITHockeyLog(@"Identification type mismatch for stored auth-token. Resetting.");
|
|
[self storeInstallationIdentifier:nil withType:BITAuthenticatorIdentificationTypeAnonymous];
|
|
}
|
|
|
|
NSString *identification = [self installationIdentifier];
|
|
|
|
if(identification) {
|
|
self.identified = YES;
|
|
if(completion) completion(YES, nil);
|
|
return;
|
|
}
|
|
|
|
//it's not identified yet, do it now
|
|
BITAuthenticationViewController *viewController = nil;
|
|
switch (self.identificationType) {
|
|
case BITAuthenticatorIdentificationTypeAnonymous:
|
|
[self storeInstallationIdentifier:bit_UUID() withType:BITAuthenticatorIdentificationTypeAnonymous];
|
|
self.identified = YES;
|
|
if(completion) completion(YES, nil);
|
|
return;
|
|
break;
|
|
case BITAuthenticatorIdentificationTypeHockeyAppUser:
|
|
viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
|
|
viewController.requirePassword = YES;
|
|
break;
|
|
case BITAuthenticatorIdentificationTypeDevice:
|
|
viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
|
|
viewController.requirePassword = NO;
|
|
viewController.showsLoginViaWebButton = YES;
|
|
break;
|
|
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
|
|
if(nil == self.authenticationSecret) {
|
|
NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAuthorizationSecretMissing
|
|
userInfo:@{NSLocalizedDescriptionKey : @"For email identification, the authentication secret must be set"}];
|
|
if(completion) completion(NO, error);
|
|
return;
|
|
}
|
|
viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
|
|
viewController.requirePassword = NO;
|
|
break;
|
|
}
|
|
|
|
if([self.delegate respondsToSelector:@selector(authenticator:willShowAuthenticationController:)]) {
|
|
[self.delegate authenticator:self willShowAuthenticationController:viewController];
|
|
}
|
|
|
|
NSAssert(viewController, @"ViewController should've been created");
|
|
|
|
viewController.email = [self stringValueFromKeychainForKey:kBITAuthenticatorUserEmailKey];
|
|
_authenticationController = viewController;
|
|
_identificationCompletion = completion;
|
|
[self showView:viewController];
|
|
}
|
|
|
|
#pragma mark - Validation
|
|
- (void) validateWithCompletion:(void (^)(BOOL validated, NSError *))completion {
|
|
BOOL requirementsFulfilled = YES;
|
|
NSError *error = nil;
|
|
switch(self.identificationType) {
|
|
case BITAuthenticatorIdentificationTypeAnonymous: {
|
|
error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorErrorUnknown
|
|
userInfo:@{NSLocalizedDescriptionKey : @"Anonymous users can't be validated"}];
|
|
requirementsFulfilled = NO;
|
|
break;
|
|
}
|
|
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
|
|
if(nil == self.authenticationSecret) {
|
|
error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAuthorizationSecretMissing
|
|
userInfo:@{NSLocalizedDescriptionKey : @"For email validation, the authentication secret must be set"}];
|
|
requirementsFulfilled = NO;
|
|
break;
|
|
}
|
|
//no break
|
|
case BITAuthenticatorIdentificationTypeDevice:
|
|
case BITAuthenticatorIdentificationTypeHockeyAppUser:
|
|
if(nil == self.installationIdentifier) {
|
|
error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorNotIdentified
|
|
userInfo:@{NSLocalizedDescriptionKey : @"Make sure to identify the installation first"}];
|
|
requirementsFulfilled = NO;
|
|
}
|
|
break;
|
|
}
|
|
if(NO == requirementsFulfilled) {
|
|
if(completion) {
|
|
completion(NO, error);
|
|
}
|
|
return;
|
|
}
|
|
|
|
NSString *validationPath = [NSString stringWithFormat:@"api/3/apps/%@/identity/validate", self.encodedAppIdentifier];
|
|
__weak typeof (self) weakSelf = self;
|
|
[self.hockeyAppClient getPath:validationPath
|
|
parameters:[self validationParameters]
|
|
completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) {
|
|
typeof (self) strongSelf = weakSelf;
|
|
if(nil == responseData) {
|
|
NSDictionary *userInfo = nil;
|
|
if(error) {
|
|
userInfo = @{NSUnderlyingErrorKey : error};
|
|
}
|
|
NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorNetworkError
|
|
userInfo:userInfo];
|
|
self.validated = NO;
|
|
if(completion) completion(NO, error);
|
|
} else {
|
|
NSError *validationParseError = nil;
|
|
BOOL valid = [strongSelf.class isValidationResponseValid:responseData error:&validationParseError];
|
|
strongSelf.validated = valid;
|
|
if(valid) {
|
|
[self setLastAuthenticatedVersion:self.executableUUID];
|
|
}
|
|
if(completion) completion(valid, validationParseError);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (NSDictionary*) validationParameters {
|
|
NSParameterAssert(self.installationIdentifier);
|
|
NSParameterAssert(self.installationIdentifierTypeString);
|
|
return @{self.installationIdentifierTypeString : self.installationIdentifier};
|
|
}
|
|
|
|
+ (BOOL) isValidationResponseValid:(id) response error:(NSError **) error {
|
|
NSParameterAssert(response);
|
|
|
|
NSError *jsonParseError = nil;
|
|
id jsonObject = [NSJSONSerialization JSONObjectWithData:response
|
|
options:0
|
|
error:&jsonParseError];
|
|
if(nil == jsonObject) {
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAPIServerReturnedInvalidResponse
|
|
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
|
|
}
|
|
return NO;
|
|
}
|
|
if(![jsonObject isKindOfClass:[NSDictionary class]]) {
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAPIServerReturnedInvalidResponse
|
|
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
NSString *status = jsonObject[@"status"];
|
|
if([status isEqualToString:@"not authorized"]) {
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorNotAuthorized
|
|
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationNotMember")}];
|
|
}
|
|
return NO;
|
|
} else if([status isEqualToString:@"not found"]) {
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorUnknownApplicationID
|
|
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationContactDeveloper")}];
|
|
}
|
|
return NO;
|
|
} else if([status isEqualToString:@"validated"]) {
|
|
return YES;
|
|
} else {
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAPIServerReturnedInvalidResponse
|
|
userInfo:@{NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
|
|
}
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
#pragma mark - AuthenticationViewControllerDelegate
|
|
- (void)authenticationViewController:(UIViewController *)viewController
|
|
handleAuthenticationWithEmail:(NSString *)email
|
|
password:(NSString *)password
|
|
completion:(void (^)(BOOL, NSError *))completion {
|
|
NSParameterAssert(email && email.length);
|
|
NSParameterAssert(self.identificationType == BITAuthenticatorIdentificationTypeHockeyAppEmail || (password && password.length));
|
|
NSURLRequest* request = [self requestForAuthenticationEmail:email password:password];
|
|
__weak typeof (self) weakSelf = self;
|
|
BITHTTPOperation *operation = [self.hockeyAppClient operationWithURLRequest:request
|
|
completion:^(BITHTTPOperation *operation, NSData* responseData, NSError *error) {
|
|
typeof (self) strongSelf = weakSelf;
|
|
NSError *authParseError = nil;
|
|
NSString *authToken = [strongSelf.class authenticationTokenFromURLResponse:operation.response
|
|
data:responseData
|
|
error:&authParseError];
|
|
BOOL identified;
|
|
if(authToken) {
|
|
identified = YES;
|
|
[strongSelf storeInstallationIdentifier:authToken withType:strongSelf.identificationType];
|
|
[strongSelf->_authenticationController dismissViewControllerAnimated:YES
|
|
completion:nil];
|
|
strongSelf->_authenticationController = nil;
|
|
[self addStringValueToKeychain:email forKey:kBITAuthenticatorUserEmailKey];
|
|
} else {
|
|
identified = NO;
|
|
}
|
|
self.identified = identified;
|
|
completion(identified, authParseError);
|
|
if(strongSelf.identificationCompletion) strongSelf.identificationCompletion(identified, authParseError);
|
|
strongSelf.identificationCompletion = nil;
|
|
|
|
}];
|
|
[self.hockeyAppClient enqeueHTTPOperation:operation];
|
|
}
|
|
|
|
- (NSURLRequest *) requestForAuthenticationEmail:(NSString*) email password:(NSString*) password {
|
|
NSString *authenticationPath = [self authenticationPath];
|
|
NSDictionary *params = nil;
|
|
|
|
if(BITAuthenticatorIdentificationTypeHockeyAppEmail == self.identificationType) {
|
|
NSString *authCode = BITHockeyMD5([NSString stringWithFormat:@"%@%@",
|
|
self.authenticationSecret ? : @"",
|
|
email ? : @""]);
|
|
params = @{
|
|
@"email" : email ? : @"",
|
|
@"authcode" : authCode.lowercaseString,
|
|
};
|
|
}
|
|
|
|
NSMutableURLRequest *request = [self.hockeyAppClient requestWithMethod:@"POST"
|
|
path:authenticationPath
|
|
parameters:params];
|
|
if(BITAuthenticatorIdentificationTypeHockeyAppUser == self.identificationType) {
|
|
NSString *authStr = [NSString stringWithFormat:@"%@:%@", email, password];
|
|
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
|
|
NSString *authValue = [NSString stringWithFormat:@"Basic %@", bit_base64String(authData, authData.length)];
|
|
[request setValue:authValue forHTTPHeaderField:@"Authorization"];
|
|
}
|
|
return request;
|
|
}
|
|
|
|
- (NSString *) authenticationPath {
|
|
if(BITAuthenticatorIdentificationTypeHockeyAppUser == self.identificationType) {
|
|
return [NSString stringWithFormat:@"api/3/apps/%@/identity/authorize", self.encodedAppIdentifier];
|
|
} else {
|
|
return [NSString stringWithFormat:@"api/3/apps/%@/identity/check", self.encodedAppIdentifier];
|
|
}
|
|
}
|
|
|
|
+ (NSString *) authenticationTokenFromURLResponse:(NSHTTPURLResponse*) urlResponse data:(NSData*) data error:(NSError **) error {
|
|
NSParameterAssert(urlResponse);
|
|
if(nil == urlResponse) {
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAPIServerReturnedInvalidResponse
|
|
userInfo:@{ NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
switch (urlResponse.statusCode) {
|
|
case 401:
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorNotAuthorized
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationWrongEmailPassword")
|
|
}];
|
|
}
|
|
break;
|
|
case 200:
|
|
case 404:
|
|
//Do nothing, handled below
|
|
break;
|
|
default:
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAPIServerReturnedInvalidResponse
|
|
userInfo:@{ NSLocalizedDescriptionKey : BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
|
|
|
|
}
|
|
break;
|
|
}
|
|
if(200 != urlResponse.statusCode && 404 != urlResponse.statusCode) {
|
|
//make sure we have an error created if user wanted to have one
|
|
NSParameterAssert(nil == error || *error);
|
|
return nil;
|
|
}
|
|
|
|
NSError *jsonParseError = nil;
|
|
id jsonObject = [NSJSONSerialization JSONObjectWithData:data
|
|
options:0
|
|
error:&jsonParseError];
|
|
//no json or unexpected json
|
|
if(nil == jsonObject || ![jsonObject isKindOfClass:[NSDictionary class]]) {
|
|
if(error) {
|
|
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")};
|
|
if(jsonParseError) {
|
|
NSMutableDictionary *userInfoMutable = [userInfo mutableCopy];
|
|
userInfoMutable[NSUnderlyingErrorKey] = jsonParseError;
|
|
userInfo = userInfoMutable;
|
|
}
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAPIServerReturnedInvalidResponse
|
|
userInfo:userInfo];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
NSString *status = jsonObject[@"status"];
|
|
NSString *authToken = nil;
|
|
if([status isEqualToString:@"identified"]) {
|
|
authToken = jsonObject[@"iuid"];
|
|
} else if([status isEqualToString:@"authorized"]) {
|
|
authToken = jsonObject[@"auid"];
|
|
} else if([status isEqualToString:@"not authorized"]) {
|
|
if(error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorNotAuthorized
|
|
userInfo:@{NSLocalizedDescriptionKey: BITHockeyLocalizedString(@"HockeyAuthenticationNotMember")}];
|
|
|
|
}
|
|
}
|
|
//if no error is set yet, but error parameter is given, return a generic error
|
|
if(nil == authToken && error && nil == *error) {
|
|
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorAPIServerReturnedInvalidResponse
|
|
userInfo:@{NSLocalizedDescriptionKey: BITHockeyLocalizedString(@"HockeyAuthenticationFailedAuthenticate")}];
|
|
}
|
|
return authToken;
|
|
}
|
|
|
|
- (void)authenticationViewControllerDidTapWebButton:(UIViewController *)viewController {
|
|
NSURL *hockeyWebbasedLoginURL = [self.webpageURL URLByAppendingPathComponent:[NSString stringWithFormat:@"apps/%@/authorize", self.encodedAppIdentifier]];
|
|
[[UIApplication sharedApplication] openURL:hockeyWebbasedLoginURL];
|
|
}
|
|
|
|
- (BOOL) handleOpenURL:(NSURL *) url
|
|
sourceApplication:(NSString *) sourceApplication
|
|
annotation:(id) annotation {
|
|
BOOL isValidURL = NO;
|
|
NSString *udid = [self UDIDFromOpenURL:url annotation:annotation isValidURL:&isValidURL];
|
|
if(NO == isValidURL) {
|
|
//do nothing, was not for us
|
|
return NO;
|
|
}
|
|
|
|
if(udid){
|
|
if(NO == self.restrictApplicationUsage) {
|
|
[_authenticationController dismissViewControllerAnimated:YES completion:nil];
|
|
_authenticationController = nil;
|
|
}
|
|
[self storeInstallationIdentifier:udid withType:BITAuthenticatorIdentificationTypeDevice];
|
|
self.identified = YES;
|
|
if(self.identificationCompletion) {
|
|
self.identificationCompletion(YES, nil);
|
|
self.identificationCompletion = nil;
|
|
}
|
|
} else {
|
|
//reset token
|
|
[self storeInstallationIdentifier:nil withType:BITAuthenticatorIdentificationTypeDevice];
|
|
self.identified = NO;
|
|
if(self.identificationCompletion) {
|
|
NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
|
|
code:BITAuthenticatorErrorUnknown
|
|
userInfo:@{NSLocalizedDescriptionKey : @"Failed to retrieve UDID from URL"}];
|
|
self.identificationCompletion(NO, error);
|
|
self.identificationCompletion = nil;
|
|
}
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (NSString *) UDIDFromOpenURL:(NSURL *) url annotation:(id) annotation isValidURL:(BOOL*) isValid{
|
|
NSString *urlScheme = [NSString stringWithFormat:@"ha%@", self.appIdentifier];
|
|
if([[url scheme] isEqualToString:urlScheme]) {
|
|
if(isValid) {
|
|
*isValid = YES;
|
|
}
|
|
NSString *query = [url query];
|
|
NSString *udid = nil;
|
|
//there should actually only one
|
|
static NSString * const UDIDQuerySpecifier = @"udid";
|
|
for(NSString *queryComponents in [query componentsSeparatedByString:@"&"]) {
|
|
NSArray *parameterComponents = [queryComponents componentsSeparatedByString:@"="];
|
|
if(2 == parameterComponents.count && [parameterComponents[0] isEqualToString:UDIDQuerySpecifier]) {
|
|
udid = parameterComponents[1];
|
|
break;
|
|
}
|
|
}
|
|
return udid;
|
|
} else {
|
|
if(isValid) {
|
|
*isValid = NO;
|
|
}
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Private helpers
|
|
- (void) cleanupInternalStorage {
|
|
[self removeKeyFromKeychain:kBITAuthenticatorIdentifierTypeKey];
|
|
[self removeKeyFromKeychain:kBITAuthenticatorIdentifierKey];
|
|
[self removeKeyFromKeychain:kBITAuthenticatorUUIDKey];
|
|
[self removeKeyFromKeychain:kBITAuthenticatorUserEmailKey];
|
|
[self setLastAuthenticatedVersion:nil];
|
|
|
|
//cleanup values stored from 3.5 Beta1..Beta3
|
|
[self removeKeyFromKeychain:kBITAuthenticatorAuthTokenKey];
|
|
[self removeKeyFromKeychain:kBITAuthenticatorAuthTokenTypeKey];
|
|
}
|
|
|
|
#pragma mark - KVO
|
|
- (void) registerObservers {
|
|
__weak typeof(self) weakSelf = self;
|
|
if(nil == _appDidBecomeActiveObserver) {
|
|
_appDidBecomeActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification
|
|
object:nil
|
|
queue:NSOperationQueue.mainQueue
|
|
usingBlock:^(NSNotification *note) {
|
|
typeof(self) strongSelf = weakSelf;
|
|
[strongSelf applicationDidBecomeActive:note];
|
|
}];
|
|
}
|
|
if(nil == _appWillResignActiveObserver) {
|
|
_appWillResignActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification
|
|
object:nil
|
|
queue:NSOperationQueue.mainQueue
|
|
usingBlock:^(NSNotification *note) {
|
|
typeof(self) strongSelf = weakSelf;
|
|
[strongSelf applicationWillResignActive:note];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void) unregisterObservers {
|
|
if(_appDidBecomeActiveObserver) {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_appDidBecomeActiveObserver];
|
|
_appDidBecomeActiveObserver = nil;
|
|
}
|
|
if(_appWillResignActiveObserver) {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_appWillResignActiveObserver];
|
|
_appWillResignActiveObserver = nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark - Property overrides
|
|
- (void)storeInstallationIdentifier:(NSString *)installationIdentifier withType:(BITAuthenticatorIdentificationType) type {
|
|
if(nil == installationIdentifier) {
|
|
[self removeKeyFromKeychain:kBITAuthenticatorIdentifierKey];
|
|
[self removeKeyFromKeychain:kBITAuthenticatorIdentifierTypeKey];
|
|
} else {
|
|
BOOL success = [self addStringValueToKeychainForThisDeviceOnly:installationIdentifier
|
|
forKey:kBITAuthenticatorIdentifierKey];
|
|
NSParameterAssert(success);
|
|
success = [self addStringValueToKeychainForThisDeviceOnly:[self.class stringForIdentificationType:type]
|
|
forKey:kBITAuthenticatorIdentifierTypeKey];
|
|
NSParameterAssert(success);
|
|
}
|
|
}
|
|
|
|
- (NSString*) installationIdentifier {
|
|
NSString *identifier = [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierKey];
|
|
return identifier;
|
|
}
|
|
|
|
- (void)setLastAuthenticatedVersion:(NSString *)lastAuthenticatedVersion {
|
|
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
|
|
if(nil == lastAuthenticatedVersion){
|
|
[defaults removeObjectForKey:kBITAuthenticatorLastAuthenticatedVersionKey];
|
|
} else {
|
|
[defaults setObject:lastAuthenticatedVersion
|
|
forKey:kBITAuthenticatorLastAuthenticatedVersionKey];
|
|
[defaults synchronize];
|
|
}
|
|
}
|
|
|
|
- (NSString *)lastAuthenticatedVersion {
|
|
return [[NSUserDefaults standardUserDefaults] objectForKey:kBITAuthenticatorLastAuthenticatedVersionKey];
|
|
}
|
|
|
|
- (NSString *)installationIdentifierTypeString {
|
|
return [self.class stringForIdentificationType:self.identificationType];
|
|
}
|
|
|
|
+ (NSString *)stringForIdentificationType:(BITAuthenticatorIdentificationType) identificationType {
|
|
switch(identificationType) {
|
|
case BITAuthenticatorIdentificationTypeHockeyAppEmail: return @"iuid";
|
|
case BITAuthenticatorIdentificationTypeHockeyAppUser: return @"auid";
|
|
case BITAuthenticatorIdentificationTypeDevice: return @"udid";
|
|
case BITAuthenticatorIdentificationTypeAnonymous: return @"uuid";
|
|
}
|
|
}
|
|
|
|
- (void)setIdentificationType:(BITAuthenticatorIdentificationType)identificationType {
|
|
if(_identificationType != identificationType) {
|
|
_identificationType = identificationType;
|
|
self.identified = NO;
|
|
self.validated = NO;
|
|
}
|
|
}
|
|
|
|
- (NSString *)publicInstallationIdentifier {
|
|
switch (self.identificationType) {
|
|
case BITAuthenticatorIdentificationTypeHockeyAppEmail:
|
|
case BITAuthenticatorIdentificationTypeHockeyAppUser:
|
|
return [self stringValueFromKeychainForKey:kBITAuthenticatorUserEmailKey];
|
|
case BITAuthenticatorIdentificationTypeAnonymous:
|
|
case BITAuthenticatorIdentificationTypeDevice:
|
|
return [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierKey];
|
|
}
|
|
}
|
|
|
|
#pragma mark - Application Lifecycle
|
|
- (void)applicationDidBecomeActive:(NSNotification *)note {
|
|
[self authenticate];
|
|
}
|
|
|
|
- (void)applicationWillResignActive:(NSNotification *)note {
|
|
if(BITAuthenticatorAppRestrictionEnforcementOnAppActive == self.restrictionEnforcementFrequency) {
|
|
self.validated = NO;
|
|
}
|
|
}
|
|
@end
|