diff --git a/Classes/BITAuthenticationViewController.h b/Classes/BITAuthenticationViewController.h index cb70ffb083..9df4b0ae20 100644 --- a/Classes/BITAuthenticationViewController.h +++ b/Classes/BITAuthenticationViewController.h @@ -54,14 +54,12 @@ */ @property (nonatomic, assign) BOOL requirePassword; -/** configure if user can skip authentication or not - * - * defaults to YES - */ -@property (nonatomic, assign) BOOL showsSkipButton; - @property (nonatomic, weak) id delegate; +/** + * allows to pre-fill the email-addy + */ +@property (nonatomic, copy) NSString* email; @end /** @@ -69,13 +67,6 @@ */ @protocol BITAuthenticationViewControllerDelegate -/** - * called then the user skipped the auth-dialgo - * - * @param viewController the delegating viewcontroller - */ -- (void) authenticationViewControllerDidSkip:(UIViewController*) viewController; - - (void) authenticationViewControllerDidTapWebButton:(UIViewController*) viewController; /** diff --git a/Classes/BITAuthenticationViewController.m b/Classes/BITAuthenticationViewController.m index acca3d6541..01a062cfb5 100644 --- a/Classes/BITAuthenticationViewController.m +++ b/Classes/BITAuthenticationViewController.m @@ -34,9 +34,9 @@ @interface BITAuthenticationViewController () { UIStatusBarStyle _statusBarStyle; + __weak UITextField *_emailField; } -@property (nonatomic, copy) NSString *email; @property (nonatomic, copy) NSString *password; @end @@ -48,7 +48,6 @@ if (self) { self.title = BITHockeyLocalizedString(@"HockeyAuthenticatorViewControllerTitle"); _delegate = delegate; - _showsSkipButton = YES; } return self; } @@ -85,22 +84,8 @@ } #pragma mark - Property overrides -- (void)setShowsSkipButton:(BOOL)showsSkipButton { - if(_showsSkipButton != showsSkipButton) { - _showsSkipButton = showsSkipButton; - [self updateBarButtons]; - } -} - (void) updateBarButtons { - if(self.showsSkipButton) { - self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:BITHockeyLocalizedString(@"Skip") - style:UIBarButtonItemStyleBordered - target:self - action:@selector(dismissAction:)]; - } else { - self.navigationItem.leftBarButtonItem = nil; - } if(self.showsLoginViaWebButton) { self.navigationItem.rightBarButtonItem = nil; } else { @@ -149,6 +134,13 @@ - (IBAction) handleWebLoginButton:(id)sender { [self.delegate authenticationViewControllerDidTapWebButton:self]; } + +- (void)setEmail:(NSString *)email { + _email = email; + if(self.isViewLoaded) { + _emailField.text = email; + } +} #pragma mark - UIViewController Rotation - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { @@ -222,6 +214,7 @@ if (0 == [indexPath row]) { textField.placeholder = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerEmailPlaceholder"); textField.text = self.email; + _emailField = textField; textField.keyboardType = UIKeyboardTypeEmailAddress; if ([self requirePassword]) @@ -296,10 +289,6 @@ } #pragma mark - Actions -- (void)dismissAction:(id)sender { - [self.delegate authenticationViewControllerDidSkip:self]; -} - - (void)saveAction:(id)sender { [self setLoginUIEnabled:NO]; diff --git a/Classes/BITAuthenticator.h b/Classes/BITAuthenticator.h index dba5da198e..ee95f87e9a 100644 --- a/Classes/BITAuthenticator.h +++ b/Classes/BITAuthenticator.h @@ -31,122 +31,83 @@ #import "BITHockeyBaseManager.h" /** - * Auth types + * Identification Types */ -typedef NS_ENUM(NSUInteger, BITAuthenticatorAuthType) { +typedef NS_ENUM(NSUInteger, BITAuthenticatorIdentificationType) { + /** + * Assigns this app an anonymous user id + */ + BITAuthenticatorIdentificationTypeAnonymous, /** * Ask for the HockeyApp account email */ - BITAuthenticatorAuthTypeEmail, + BITAuthenticatorIdentificationTypeHockeyAppEmail, /** - * Ask for the HockeyApp account email and password + * Ask for the HockeyApp account by email and password */ - BITAuthenticatorAuthTypeEmailAndPassword, + BITAuthenticatorIdentificationTypeHockeyAppUser, /** - * Request the device UDID + * Identifies the current device */ - BITAuthenticatorAuthTypeUDIDProvider + BITAuthenticatorIdentificationTypeDevice, }; /** - * Validation types + * BITAuthenticatorAppRestrictionEnforcementFrequency + * Specifies how often the Authenticator checks if the user is allowed to use + * use this app. */ -typedef NS_ENUM(NSUInteger, BITAuthenticatorValidationType) { +typedef NS_ENUM(NSUInteger, BITAuthenticatorAppRestrictionEnforcementFrequency) { /** - * Never validate if the user is allowed to run the app + * Check if the user is allowed to use the app the first time a version is started */ - BITAuthenticatorValidationTypeNever = 0, + BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch, /** - * Optionally validate if the user is authorized; user can skip the process + * Check if the user is allowed to use the app everytime the app becomes active */ - BITAuthenticatorValidationTypeOptional, - /** - * Check if the user is authenticated at the first time a new version is started - */ - BITAuthenticatorValidationTypeOnFirstLaunch, - /** - * Check if the user is authenticated everytime the app becomes active - */ - BITAuthenticatorValidationTypeOnAppActive, + BITAuthenticatorAppRestrictionEnforcementOnAppActive, }; -typedef void(^tAuthenticationCompletion)(NSString* authenticationToken, NSError *error); -typedef void(^tValidationCompletion)(BOOL validated, NSError *error); - @protocol BITAuthenticatorDelegate; -/** - * Authenticator module used to identify and optionally authenticate the current app user - * - * This is the HockeySDK module for handling authentication when using Ad-Hoc or Enterprise provisioning profiles. - * This module allows you to make sure the current app installation is done on an authorized device by choosing from - * various authentication and validation mechanisms which provide different levels of authentication. - * - * This does not provide DRM or copy protection in any form. Each authentication type and validation type provide - * a different level of user authorization. Validation is the process of checking against the HockeyApp server if - * the provided/existing authorization is still valid. - * - * This module automatically disables itself when running in an App Store build by default! - * - * Authentication is a 2 step process: - * - * 1. authenticate: - * a token is acquired depending on the `authenticationType` - * 2. validation: - * the acquired token from step 1 is validated depending the `validationType` - * - * There are currently 3 ways of authentication (`BITAuthenticatorAuthType`): - * - * 1. authenticate the user via email only (`BITAuthenticatorAuthTypeEmail`) - * 2. authenticate the user via email & password (`BITAuthenticatorAuthTypeEmailAndPassword`) - * 3. authenticate the device via its UDID (_Default_) (`BITAuthenticatorAuthTypeUDIDProvider`) - * - * There are currently 4 ways of validation (`BITAuthenticatorValidationType`): - * - * 1. never (_Default_) (`BITAuthenticatorValidationTypeNever`) - * 2. optional (`BITAuthenticatorValidationTypeOptional`) - * 3. on first launch of a new app version (`BITAuthenticatorValidationTypeOnFirstLaunch`) - * 4. every time the app becomes active (needs internet connection) (`BITAuthenticatorValidationTypeOnAppActive`) - * - * We have created a detailed guide on how to use this class: [Authenticating Users on iOS](HowTo-Authenticating-Users-on-iOS) - * - */ @interface BITAuthenticator : BITHockeyBaseManager #pragma mark - Configuration /** - * Defines the authentication mechanism to be used + * Defines the identification mechanism to be used * - * The values are listed here: `BITAuthenticatorAuthType`: + * _Default_: `BITAuthenticatorIdentificationTypeAnonymous` * - * 1. `BITAuthenticatorAuthTypeEmail`: authenticate the user via email only - * 2. `BITAuthenticatorAuthTypeEmailAndPassword`: authenticate the user via email & password - * 3. `BITAuthenticatorAuthTypeUDIDProvider`: authenticate the device via its UDID (_Default_) - * - * _Default_: `BITAuthenticatorAuthTypeUDIDProvider` - * - * @see BITAuthenticatorAuthType + * @see BITAuthenticatorIdentificationType */ -@property (nonatomic, assign) BITAuthenticatorAuthType authenticationType; +@property (nonatomic, assign) BITAuthenticatorIdentificationType identificationType; /** - * Defines the validation mechanism to be used + * Defines if the BITAuthenticator automatically identifies the user and also + * checks if he's still allowed to use the app (depending on `restrictApplicationUsage`) * - * The values are listed here: `BITAuthenticatorValidationType`: + * _Default_: `YES` * - * 1. `BITAuthenticatorValidationTypeNever`: never (_Default_) - * 2. `BITAuthenticatorValidationTypeOptional`: optional - * 3. `BITAuthenticatorValidationTypeOnFirstLaunch`: on first launch of a new app version - * 4. `BITAuthenticatorValidationTypeOnAppActive`: every time the app becomes active (needs internet connection) - * - * _Default_: `BITAuthenticatorValidationTypeNever` - * - * @see BITAuthenticatorValidationType */ -@property (nonatomic, assign) BITAuthenticatorValidationType validationType; +@property (nonatomic, assign) BOOL automaticMode; -@property (nonatomic, weak) id delegate; +/** + * Enables or disables checking if the user is allowed to run this app + * + * _Default_: `YES` + */ +@property (nonatomic, assign) BOOL restrictApplicationUsage; + +/** + * Defines how often the BITAuthenticator checks if the user is allowed + * to run this application + * + * _Default_: `BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch` + * + * @see BITAuthenticatorAppRestrictionEnforcementFrequency + */ +@property (nonatomic, assign) BITAuthenticatorAppRestrictionEnforcementFrequency restrictionEnforcementFrequency; /** * The authentication secret from HockeyApp. To find the right secret, click on your app on the HockeyApp dashboard, @@ -156,8 +117,13 @@ typedef void(^tValidationCompletion)(BOOL validated, NSError *error); */ @property (nonatomic, copy) NSString *authenticationSecret; -#pragma mark - UDID auth +/** + * Delegate that can be used to do any last minute configurations on the presented viewController. + */ +@property (nonatomic, weak) id delegate; + +#pragma mark - UDID auth /** * baseURL of the webpage the user is redirected to if authenticationType is BITAuthenticatorAuthTypeUDIDProvider * defaults to https://rink.hockeyapp.net @@ -166,7 +132,7 @@ typedef void(^tValidationCompletion)(BOOL validated, NSError *error); /** Should be used by the app-delegate to forward handle application:openURL:sourceApplication:annotation: calls - + Sample usage (in AppDelegate): - (BOOL)application:(UIApplication *)application @@ -180,19 +146,59 @@ typedef void(^tValidationCompletion)(BOOL validated, NSError *error); //do your own URL handling, return appropriate value } return NO; - } + } - @param url The URL that was passed to the app - @param sourceApplication sourceApplication that was passed to the app - @param annotation annotation that was passed to the app + @param url The URL that was passed to the app + @param sourceApplication sourceApplication that was passed to the app + @param annotation annotation that was passed to the app - @return YES if the URL request was handled, NO if the URL could not be handled/identified + @return YES if the URL request was handled, NO if the URL could not be handled/identified */ - (BOOL) handleOpenURL:(NSURL *) url sourceApplication:(NSString *) sourceApplication annotation:(id) annotation; +#pragma mark - Authentication + +/** + * Identifies the user according to the type specified in `identificationType` + * If the BITAuthenticator is in manual mode, it's your responsibility to call + * this method. Depending on the `identificationType`, this method + * might present a viewController to let the user enter his/her credentials. + * If the Authenticator is in auto-mode, this is called by the authenticator itself + * once needed. + */ +- (void) identifyWithCompletion:(void(^)(BOOL identified, NSError *error)) completion; + +/** + * returns YES if this app is identified according to the setting in `identificationType` + */ +@property (nonatomic, assign, readonly, getter = isIdentified) BOOL identified; + +/** + * Validates if the identified user is allowed to run this application. This checks + * with the HockeyApp backend and calls the completion-block once completed. + * If the BITAuthenticator is in manual mode, it's your responsibility to call + * this method. If the application is not yet identified, validation is not possible + * and the completion-block is called with an error set. + * If the Authenticator is in auto-mode, this is called by the authenticator itself + * once needed. + */ +- (void) validateWithCompletion:(void(^)(BOOL validated, NSError *error)) completion; + +@property (nonatomic, assign, readonly, getter = isValidated) BOOL validated; + +/** + * removes all previously stored authentication tokens, UDIDs, etc + */ +- (void) cleanupInternalStorage; + +/** + * can be used by the application to identify the user. + * returns different values depending on `identificationType`. + */ +- (NSString*) publicInstallationIdentifier; @end #pragma mark - Protocol @@ -204,13 +210,12 @@ typedef void(^tValidationCompletion)(BOOL validated, NSError *error); @optional /** - * If the authentication (or validation) needs to authenticate the user, + * If the authentication (or validation) needs to identify the user, * this delegate method is called with the viewController that we'll present. * * @param authenticator authenticator object - * @param viewController viewcontroller used to authenticate the user + * @param viewController viewcontroller used to identify the user * */ - (void) authenticator:(BITAuthenticator *)authenticator willShowAuthenticationController:(UIViewController*) viewController; - @end diff --git a/Classes/BITAuthenticator.m b/Classes/BITAuthenticator.m index fc953d3853..73cf4b8bda 100644 --- a/Classes/BITAuthenticator.m +++ b/Classes/BITAuthenticator.m @@ -35,10 +35,16 @@ #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"; -static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthenticatorLastAuthenticatedVersionKey"; -static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticatorDidSkipOptionalLogin"; + @implementation BITAuthenticator { id _appDidBecomeActiveObserver; @@ -55,8 +61,10 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato if( self ) { _webpageURL = [NSURL URLWithString:@"https://rink.hockeyapp.net/"]; - _authenticationType = BITAuthenticatorAuthTypeUDIDProvider; - _validationType = BITAuthenticatorValidationTypeNever; + _identificationType = BITAuthenticatorIdentificationTypeAnonymous; + _automaticMode = YES; + _restrictApplicationUsage = NO; + _restrictionEnforcementFrequency = BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch; } return self; } @@ -68,114 +76,211 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato switch ([[UIApplication sharedApplication] applicationState]) { case UIApplicationStateActive: - [self triggerAuthentication]; + [self authenticate]; break; case UIApplicationStateBackground: case UIApplicationStateInactive: // do nothing, wait for active state break; } - + [self registerObservers]; } #pragma mark - -- (void) triggerAuthentication { - switch (self.validationType) { - case BITAuthenticatorValidationTypeOnAppActive: - [self validateInstallationWithCompletion:[self defaultValidationCompletionBlock]]; - break; - case BITAuthenticatorValidationTypeOnFirstLaunch: - if(![self.lastAuthenticatedVersion isEqualToString:self.executableUUID]) { - self.installationIdentificationValidated = NO; - [self validateInstallationWithCompletion:[self defaultValidationCompletionBlock]]; +- (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 { - self.installationIdentificationValidated = YES; + [_authenticationController dismissViewControllerAnimated:YES completion:nil]; + _authenticationController = nil; } - break; - case BITAuthenticatorValidationTypeOptional: - if(NO == self.didSkipOptionalLogin) { - [self validateInstallationWithCompletion:[self defaultValidationCompletionBlock]]; - } else { - self.installationIdentificationValidated = YES; - } - break; - case BITAuthenticatorValidationTypeNever: - self.installationIdentificationValidated = YES; - break; - } + } else { + BITHockeyLog(@"Failed to identify. Error: %@", error); + } + }]; } -#pragma mark - -- (NSString *)installationIdentification { - NSString *authToken = self.authenticationToken; - if(authToken) { - return authToken; +- (BOOL) needsValidation { + if(BITAuthenticatorIdentificationTypeAnonymous == self.identificationType) { + return NO; } - return bit_appAnonID(); + 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; } - -- (NSString*) installationIdentificationType { - NSString *authToken = self.authenticationToken; - if(nil == authToken) { - return @"udid"; - } else { - return [self authenticationTokenType]; +- (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) validateInstallationWithCompletion:(tValidationCompletion) completion { - if(nil == self.authenticationToken) { - [self authenticateWithCompletion:^(NSString *authenticationToken, NSError *error) { - if(nil == authenticationToken) { - //if authentication fails, there's nothing to validate - NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorNotAuthorized - userInfo:nil]; - if(completion) completion(NO, error); - } else { - if(completion) completion(YES, nil); +- (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; } - }]; - } else { - 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]; - [strongSelf validationFailedWithError:error completion:completion]; - } else { - NSError *validationParseError = nil; - BOOL isValidated = [strongSelf.class isValidationResponseValid:responseData error:&validationParseError]; - if(isValidated) { - [strongSelf validationSucceededWithCompletion:completion]; - } else { - [strongSelf validationFailedWithError:validationParseError completion:completion]; - } - } - }]; + //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.authenticationToken); - NSParameterAssert(self.installationIdentificationType); - return @{self.installationIdentificationType : self.authenticationToken}; + 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 @@ -224,105 +329,13 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato } } -#pragma mark - Authentication - -- (void)authenticateWithCompletion:(tAuthenticationCompletion)completion { - if(_authenticationController) { - BITHockeyLog(@"Already authenticating. Ignoring request"); - return; - } - if(_authenticationType == BITAuthenticatorAuthTypeEmail && (nil == _authenticationSecret || !_authenticationSecret.length)) { - if(completion) { - NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAuthorizationSecretMissing - userInfo:@{NSLocalizedDescriptionKey: @"HockeyAuthenticationAuthSecretMissing"}]; - completion(nil, error); - } - return; - } - - BITAuthenticationViewController *viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self]; - switch (self.authenticationType) { - case BITAuthenticatorAuthTypeEmailAndPassword: - viewController.requirePassword = YES; - break; - case BITAuthenticatorAuthTypeEmail: - viewController.requirePassword = NO; - break; - case BITAuthenticatorAuthTypeUDIDProvider: - viewController.requirePassword = NO; - viewController.showsLoginViaWebButton = YES; - break; - } - - switch (self.validationType) { - case BITAuthenticatorValidationTypeNever: - case BITAuthenticatorValidationTypeOptional: - viewController.showsSkipButton = YES; - break; - case BITAuthenticatorValidationTypeOnAppActive: - case BITAuthenticatorValidationTypeOnFirstLaunch: - viewController.showsSkipButton = NO; - break; - } - - if([self.delegate respondsToSelector:@selector(authenticator:willShowAuthenticationController:)]) { - [self.delegate authenticator:self willShowAuthenticationController:viewController]; - } - - _authenticationController = viewController; - _authenticationCompletionBlock = completion; - [self showView:viewController]; -} - -- (void) didAuthenticateWithToken:(NSString*) token { - [_authenticationController dismissViewControllerAnimated:YES completion:nil]; - _authenticationController = nil; - [self setAuthenticationToken:token withType:[self.class stringForAuthenticationType:self.authenticationType]]; - self.installationIdentificationValidated = YES; - self.lastAuthenticatedVersion = [self executableUUID]; - if(self.authenticationCompletionBlock) { - self.authenticationCompletionBlock(self.authenticationToken, nil); - self.authenticationCompletionBlock = nil; - } -} - -+ (NSString*) stringForAuthenticationType:(BITAuthenticatorAuthType) authType { - switch (authType) { - case BITAuthenticatorAuthTypeEmail: return @"iuid"; - case BITAuthenticatorAuthTypeEmailAndPassword: return @"auid"; - case BITAuthenticatorAuthTypeUDIDProvider: - //fallthrough - default: - return @"udid"; - break; - } -} #pragma mark - AuthenticationViewControllerDelegate -- (void) authenticationViewControllerDidSkip:(UIViewController *)viewController { - [viewController dismissViewControllerAnimated:YES completion:nil]; - - _authenticationController = nil; - [self setAuthenticationToken:nil withType:nil]; - if(self.validationType == BITAuthenticatorValidationTypeOptional) { - self.didSkipOptionalLogin = YES; - self.installationIdentificationValidated = YES; - } - NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAuthenticationCancelled - userInfo:nil]; - if(self.authenticationCompletionBlock) { - self.authenticationCompletionBlock(self.authenticationToken, error); - self.authenticationCompletionBlock = nil; - } -} - - (void)authenticationViewController:(UIViewController *)viewController handleAuthenticationWithEmail:(NSString *)email password:(NSString *)password completion:(void (^)(BOOL, NSError *))completion { NSParameterAssert(email && email.length); - NSParameterAssert(self.authenticationType == BITAuthenticatorAuthTypeEmail || (password && password.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 @@ -331,13 +344,24 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato NSError *authParseError = nil; NSString *authToken = [strongSelf.class authenticationTokenFromURLResponse:operation.response data:responseData - error:&authParseError]; - if(nil == authToken) { - completion(NO, authParseError); + 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 { - //no need to call completion, we're dismissing it anyways - [self didAuthenticateWithToken:authToken]; - }}]; + identified = NO; + } + self.identified = identified; + completion(identified, authParseError); + if(strongSelf.identificationCompletion) strongSelf.identificationCompletion(identified, authParseError); + strongSelf.identificationCompletion = nil; + + }]; [self.hockeyAppClient enqeueHTTPOperation:operation]; } @@ -345,20 +369,20 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato NSString *authenticationPath = [self authenticationPath]; NSDictionary *params = nil; - if(BITAuthenticatorAuthTypeEmail == self.authenticationType) { + if(BITAuthenticatorIdentificationTypeHockeyAppEmail == self.identificationType) { NSString *authCode = BITHockeyMD5([NSString stringWithFormat:@"%@%@", self.authenticationSecret ? : @"", email ? : @""]); params = @{ - @"email" : email ? : @"", - @"authcode" : authCode.lowercaseString, - }; + @"email" : email ? : @"", + @"authcode" : authCode.lowercaseString, + }; } - + NSMutableURLRequest *request = [self.hockeyAppClient requestWithMethod:@"POST" path:authenticationPath parameters:params]; - if(BITAuthenticatorAuthTypeEmailAndPassword == self.authenticationType) { + 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)]; @@ -368,14 +392,13 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato } - (NSString *) authenticationPath { - if(BITAuthenticatorAuthTypeEmailAndPassword == self.authenticationType) { + 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) { @@ -412,7 +435,7 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato } if(200 != urlResponse.statusCode && 404 != urlResponse.statusCode) { //make sure we have an error created if user wanted to have one - NSParameterAssert(0 == error || *error); + NSParameterAssert(nil == error || *error); return nil; } @@ -475,18 +498,27 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato } if(udid){ - [self setAuthenticationToken:udid withType:[self.class stringForAuthenticationType:BITAuthenticatorAuthTypeUDIDProvider]]; - [self validateInstallationWithCompletion:^(BOOL validated, NSError *error) { - if(validated) { - [_authenticationController dismissViewControllerAnimated:YES completion:nil]; - _authenticationController = nil; - } else { - //TODO: show why validation failed - } - }]; + 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 auth-token - [self setAuthenticationToken:nil withType:nil]; + //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; } @@ -517,32 +549,20 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato } } -#pragma mark - Validation Pseudo-Delegate -- (void)validationFailedWithError:(NSError *)validationError completion:(tValidationCompletion) completion{ - if(completion) { - completion(NO, validationError); - } -} - -- (void)validationSucceededWithCompletion:(tValidationCompletion) completion { - self.installationIdentificationValidated = YES; - if(completion) { - completion(YES, nil); - } -} - #pragma mark - Private helpers -- (UIDevice *)currentDevice { - return _currentDevice ? : [UIDevice currentDevice];; -} - - (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]; - [[NSUserDefaults standardUserDefaults] removeObjectForKey:kBITAuthenticatorDidSkipOptionalLogin]; - [self setLastAuthenticatedVersion:nil]; } +#pragma mark - KVO - (void) registerObservers { __weak typeof(self) weakSelf = self; if(nil == _appDidBecomeActiveObserver) { @@ -556,12 +576,12 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato } if(nil == _appWillResignActiveObserver) { _appWillResignActiveObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillResignActiveNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification *note) { - typeof(self) strongSelf = weakSelf; - [strongSelf applicationWillResignActive:note]; - }]; + object:nil + queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *note) { + typeof(self) strongSelf = weakSelf; + [strongSelf applicationWillResignActive:note]; + }]; } } @@ -577,38 +597,23 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato } #pragma mark - Property overrides -- (void)setAuthenticationToken:(NSString *)authenticationToken withType:(NSString*) authenticationTokenType { - NSParameterAssert(nil == authenticationToken || nil != authenticationTokenType); - if(![self.authenticationToken isEqualToString:authenticationToken] || ![self.authenticationTokenType isEqualToString:authenticationTokenType]) { - [self willChangeValueForKey:@"installationIdentification"]; - if(nil == authenticationToken) { - [self removeKeyFromKeychain:kBITAuthenticatorAuthTokenKey]; - [self removeKeyFromKeychain:kBITAuthenticatorAuthTokenTypeKey]; - } else { - [self addStringValueToKeychainForThisDeviceOnly:authenticationToken forKey:kBITAuthenticatorAuthTokenKey]; - [self addStringValueToKeychainForThisDeviceOnly:authenticationTokenType forKey:kBITAuthenticatorAuthTokenTypeKey]; - } - [self didChangeValueForKey:@"installationIdentification"]; +- (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 *)authenticationToken { - NSString *authToken = [self stringValueFromKeychainForKey:kBITAuthenticatorAuthTokenKey]; - if(nil == authToken) return nil; - - //check if the auth token matches the current setting - if(![self.authenticationTokenType isEqualToString:[self.class stringForAuthenticationType:self.authenticationType]]) { - BITHockeyLog(@"Auth type mismatch for stored auth-token. Resetting."); - [self removeKeyFromKeychain:kBITAuthenticatorAuthTokenKey]; - return nil; - } - - return authToken; -} - -- (NSString *)authenticationTokenType { - NSString *authTokenType = [self stringValueFromKeychainForKey:kBITAuthenticatorAuthTokenTypeKey]; - return authTokenType; +- (NSString*) installationIdentifier { + NSString *identifier = [self stringValueFromKeychainForKey:kBITAuthenticatorIdentifierKey]; + return identifier; } - (void)setLastAuthenticatedVersion:(NSString *)lastAuthenticatedVersion { @@ -626,46 +631,46 @@ static NSString* const kBITAuthenticatorDidSkipOptionalLogin = @"BITAuthenticato return [[NSUserDefaults standardUserDefaults] objectForKey:kBITAuthenticatorLastAuthenticatedVersionKey]; } -- (void)setDidSkipOptionalLogin:(BOOL)didSkipOptionalLogin { - NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; - if(NO == didSkipOptionalLogin){ - [defaults removeObjectForKey:kBITAuthenticatorDidSkipOptionalLogin]; - } else { - [defaults setObject:@(YES) - forKey:kBITAuthenticatorDidSkipOptionalLogin]; - [defaults synchronize]; +- (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"; } } -- (BOOL)didSkipOptionalLogin { - return [[NSUserDefaults standardUserDefaults] objectForKey:kBITAuthenticatorDidSkipOptionalLogin]; +- (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 triggerAuthentication]; + [self authenticate]; } - (void)applicationWillResignActive:(NSNotification *)note { - if(BITAuthenticatorValidationTypeOnAppActive == self.validationType) { - self.installationIdentificationValidated = NO; + if(BITAuthenticatorAppRestrictionEnforcementOnAppActive == self.restrictionEnforcementFrequency) { + self.validated = NO; } } - -#pragma mark - -- (tValidationCompletion) defaultValidationCompletionBlock { - return ^(BOOL validated, NSError *error) { - switch (self.validationType) { - case BITAuthenticatorValidationTypeNever: - case BITAuthenticatorValidationTypeOptional: - break; - case BITAuthenticatorValidationTypeOnAppActive: - case BITAuthenticatorValidationTypeOnFirstLaunch: - if(!validated) { - [self authenticateWithCompletion:nil]; - } - break; - } - }; -}; @end diff --git a/Classes/BITAuthenticator_Private.h b/Classes/BITAuthenticator_Private.h index b469473167..36f87fff52 100644 --- a/Classes/BITAuthenticator_Private.h +++ b/Classes/BITAuthenticator_Private.h @@ -42,71 +42,22 @@ */ @property (nonatomic, strong) BITHockeyAppClient *hockeyAppClient; -#pragma mark - Identification - -/** - * Provides an identification for the current app installation - * - * During Alpha and Beta-phase HockeyApp tries to uniquely identify each app installation - * to provide better error reporting & analytics. If authenticator is configured to login - * (@see BITAuthenticatorValidationType), this identifier is retrieved from HockeyApp. In case - * it is disabled, it returns the vendorIdentifier provided by UIKit. - * KVO'able - * - * @return a string identifying this app installation - */ -@property (nonatomic, readonly) NSString *installationIdentification; - -/** - * Specifies if the installationIdentification has been validated - * - * Depending on authenticationType and validationType this flag switches between - * YES/NO during application runtime. If it's YES, the installationIdentification has been - * validated (either against the backend on app launch / initial run of this version) or validation - * is not required. The flag is first updated (and stays NO) until the manager has been started. - * KVO'able - */ -@property (nonatomic, readwrite) BOOL installationIdentificationValidated; - -/** - * Specifies the type of installation identification for the current app installation - */ -@property (nonatomic, readonly) NSString *installationIdentificationType; - #pragma mark - - -//can be set for testing -@property (nonatomic) UIDevice *currentDevice; - -#pragma mark - -/** - * if set, this serves as the installationIdentifier. - * This is retrieved from the hockeyApp backend - * @see installationIdentification - */ -@property (nonatomic, readonly) NSString *authenticationToken; - -/** - * store the authentication token with the given type - * if authToken is not nil, authentikationTokenType must also be non nil - * - * @param authenticationToken The authentication token - * @param authenticationTokenType The authentication token type - */ -- (void)setAuthenticationToken:(NSString *)authenticationToken withType:(NSString*) authenticationTokenType; - /** * holds the identifier of the last version that was authenticated * only used if validation is set BITAuthenticatorValidationTypeOnFirstLaunch */ @property (nonatomic, copy) NSString *lastAuthenticatedVersion; -@property (nonatomic, copy) tAuthenticationCompletion authenticationCompletionBlock; +/** + * return the string used to identify this app against the HockeyApp backend. + */ +@property (nonatomic, copy, readonly) NSString *installationIdentifierTypeString; /** - * removes all previously stored authentication tokens, UDIDs, etc + * returns the string used to identify this app against the HockeyApp backend. */ -- (void) cleanupInternalStorage; +@property (nonatomic, copy, readonly) NSString *installationIdentifier; /** * method registered as observer for applicationWillBecomeInactive events @@ -122,49 +73,14 @@ */ - (void) applicationDidBecomeActive:(NSNotification*) note; -/** - * once the user skipped the optional login, this is set to YES - * (and thus the optional login should never be shown again) - * persisted to disk. Defaults to NO - */ -@property (nonatomic, assign) BOOL didSkipOptionalLogin; +@property (nonatomic, copy) void(^identificationCompletion)(BOOL identified, NSError* error); -#pragma mark - Authentication -/** - * Authenticate this app installation - * - * Depending on 'authenticationType', this tries to authenticate the app installation - * against the HockeyApp server. - * You should not need to call this, as it's done automatically once the manager has - * been started, depending on validationType. - * - * @param completion if nil, success/failure is reported via the delegate, if not nil, the - * delegate methods are not called. - */ -- (void) authenticateWithCompletion:(tAuthenticationCompletion) completion; +#pragma mark - Overrides +@property (nonatomic, assign, readwrite, getter = isIdentified) BOOL identified; +@property (nonatomic, assign, readwrite, getter = isValidated) BOOL validated; -#pragma mark - Internal Auth callbacks -- (void) didAuthenticateWithToken:(NSString*) token; - -#pragma mark - Validation -/** - * Validate the app installation - * - * Depending on @see validationType, this is called by the manager after the app becomes active - * and tries to revalidate the installation. - * You should not need to call this, as it's done automatically once the manager has - * been started, depending on validationType. - * - * @param completion if nil, success/failure is reported via the delegate, if not nil, the - * delegate methods are not called - */ -- (void) validateInstallationWithCompletion:(tValidationCompletion) completion; - - -#pragma mark - Validation callbacks -- (void) validationSucceededWithCompletion:(tValidationCompletion) completion; -- (void) validationFailedWithError:(NSError *) validationError completion:(tValidationCompletion) completion; - -#pragma mark - Helpers for testing -- (tValidationCompletion) defaultValidationCompletionBlock; +#pragma mark - Testing +- (void) storeInstallationIdentifier:(NSString*) identifier withType:(BITAuthenticatorIdentificationType) type; +- (BOOL) needsValidation; +- (void) authenticate; @end diff --git a/Classes/BITHockeyManager.m b/Classes/BITHockeyManager.m index 5baa7abab6..e573ddb7fb 100644 --- a/Classes/BITHockeyManager.m +++ b/Classes/BITHockeyManager.m @@ -210,7 +210,7 @@ // start Authenticator if (![self isAppStoreEnvironment]) { // hook into manager with kvo! - [_authenticator addObserver:self forKeyPath:@"installationIdentificationValidated" options:0 context:nil]; + [_authenticator addObserver:self forKeyPath:@"identified" options:0 context:nil]; BITHockeyLog(@"INFO: Start Authenticator"); if (_serverURL) { @@ -226,7 +226,7 @@ || [[self class] isJMCPresent] #endif /* HOCKEYSDK_FEATURE_JIRA_MOBILE_CONNECT */ ) { - if ([self.authenticator installationIdentificationValidated]) { + if ([self.authenticator isIdentified]) { [self invokeStartUpdateManager]; } } @@ -283,11 +283,11 @@ #if HOCKEYSDK_FEATURE_UPDATES - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([keyPath isEqualToString:@"installationIdentificationValidated"] && - [object valueForKey:@"installationIdentificationValidated"] ) { + if ([keyPath isEqualToString:@"identified"] && + [object valueForKey:@"isIdentified"] ) { if ((![self isAppStoreEnvironment] && ![self isUpdateManagerDisabled])) { - BOOL isValidated = [(NSNumber *)[object valueForKey:@"installationIdentificationValidated"] boolValue]; - if (isValidated) { + BOOL identified = [(NSNumber *)[object valueForKey:@"isIdentified"] boolValue]; + if (identified) { [self invokeStartUpdateManager]; } } @@ -330,9 +330,9 @@ [_updateManager setServerURL:_serverURL]; } if (_authenticator) { - [_updateManager setInstallationIdentification:[self.authenticator installationIdentification]]; - [_updateManager setInstallationIdentificationType:[self.authenticator installationIdentificationType]]; - [_updateManager setInstallationIdentificationValidated:[self.authenticator installationIdentificationValidated]]; + [_updateManager setInstallationIdentification:[self.authenticator installationIdentifier]]; + [_updateManager setInstallationIdentificationType:[self.authenticator installationIdentifierTypeString]]; + [_updateManager setInstallationIdentified:[self.authenticator isIdentified]]; } [_updateManager performSelector:@selector(startManager) withObject:nil afterDelay:0.5f]; } diff --git a/Classes/BITUpdateManager.m b/Classes/BITUpdateManager.m index 9924cb6430..6605eba477 100644 --- a/Classes/BITUpdateManager.m +++ b/Classes/BITUpdateManager.m @@ -535,7 +535,7 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { - (void)checkForUpdate { if (![self isAppStoreEnvironment] && ![self isUpdateManagerDisabled]) { if ([self expiryDateReached]) return; - if (![self installationIdentificationValidated]) return; + if (![self installationIdentified]) return; if (self.isUpdateAvailable && [self hasNewerMandatoryVersion]) { [self showCheckForUpdateAlert]; @@ -881,12 +881,6 @@ typedef NS_ENUM(NSInteger, BITUpdateAlertViewTag) { } } -- (void)setInstallationIdentificationValidated:(BOOL)installationIdentificationValidated { - if (installationIdentificationValidated != _installationIdentificationValidated) { - _installationIdentificationValidated = installationIdentificationValidated; - } -} - - (void)setInstallationIdentification:(NSString *)installationIdentification { if (![_installationIdentification isEqualToString:installationIdentification]) { if (installationIdentification) { diff --git a/Classes/BITUpdateManagerPrivate.h b/Classes/BITUpdateManagerPrivate.h index 35a3d6a661..17b8dd05ff 100644 --- a/Classes/BITUpdateManagerPrivate.h +++ b/Classes/BITUpdateManagerPrivate.h @@ -64,7 +64,7 @@ @property (nonatomic, strong) NSString *installationIdentificationType; -@property (nonatomic) BOOL installationIdentificationValidated; +@property (nonatomic) BOOL installationIdentified; // if YES, the API will return an existing JMC config // if NO, the API will return only version information diff --git a/Classes/HockeySDK.h b/Classes/HockeySDK.h index fc553d49ee..17d9ddfbc7 100644 --- a/Classes/HockeySDK.h +++ b/Classes/HockeySDK.h @@ -174,10 +174,6 @@ typedef NS_ENUM(NSInteger, BITAuthenticatorReason) { * Not Authorized */ BITAuthenticatorNotAuthorized, - /** - * Authorization cancelled - */ - BITAuthenticatorAuthenticationCancelled, /** * Unknown Application ID (configuration error) */ @@ -186,6 +182,10 @@ typedef NS_ENUM(NSInteger, BITAuthenticatorReason) { * Authorization secret missing */ BITAuthenticatorAuthorizationSecretMissing, + /** + * Not yet identified + */ + BITAuthenticatorNotIdentified, }; extern NSString *const __attribute__((unused)) kBITAuthenticatorErrorDomain; diff --git a/Support/HockeySDKTests/BITAuthenticatorTests.m b/Support/HockeySDKTests/BITAuthenticatorTests.m index 47e72fb881..774f63bb31 100644 --- a/Support/HockeySDKTests/BITAuthenticatorTests.m +++ b/Support/HockeySDKTests/BITAuthenticatorTests.m @@ -19,6 +19,7 @@ #import "BITAuthenticator_Private.h" #import "BITHTTPOperation.h" #import "BITTestHelper.h" +#import "BITHockeyAppClient.h" @interface MyDevice : NSObject - (NSString*) uniqueIdentifier; @@ -56,7 +57,6 @@ static void *kInstallationIdentification = &kInstallationIdentification; [super setUp]; _sut = [[BITAuthenticator alloc] initWithAppIdentifier:nil isAppStoreEnvironment:NO]; - _sut.authenticationType = BITAuthenticatorAuthTypeEmailAndPassword; } - (void)tearDown { @@ -88,12 +88,6 @@ static void *kInstallationIdentification = &kInstallationIdentification; } #pragma mark - Persistence Tests -- (void) testThatAuthenticationTokenIsPersisted { - [_sut setAuthenticationToken:@"SuperToken" withType:@"udid"]; - _sut = [[BITAuthenticator alloc] initWithAppIdentifier:nil isAppStoreEnvironment:YES]; - assertThat(_sut.authenticationToken, equalTo(@"SuperToken")); -} - - (void) testThatLastAuthenticatedVersionIsPersisted { _sut.lastAuthenticatedVersion = @"1.2.1"; _sut = [[BITAuthenticator alloc] initWithAppIdentifier:nil isAppStoreEnvironment:YES]; @@ -101,224 +95,206 @@ static void *kInstallationIdentification = &kInstallationIdentification; } - (void) testThatCleanupWorks { - [_sut setAuthenticationToken:@"MyToken" withType:@"udid"]; _sut.lastAuthenticatedVersion = @"1.2"; - [_sut setDidSkipOptionalLogin:YES]; [_sut cleanupInternalStorage]; - assertThat(_sut.authenticationToken, equalTo(nil)); assertThat(_sut.lastAuthenticatedVersion, equalTo(nil)); - assertThat(_sut.installationIdentificationType, equalTo(@"udid")); - assertThatBool(_sut.didSkipOptionalLogin, equalToBool(NO)); + assertThat(_sut.installationIdentifier, equalTo(nil)); } -- (void) testThatSkipLoginIsPersisted { - [_sut setDidSkipOptionalLogin:YES]; - _sut = [[BITAuthenticator alloc] initWithAppIdentifier:nil isAppStoreEnvironment:YES]; - assertThatBool(_sut.didSkipOptionalLogin, equalToBool(YES)); - [_sut setDidSkipOptionalLogin:NO]; - _sut = [[BITAuthenticator alloc] initWithAppIdentifier:nil isAppStoreEnvironment:YES]; - assertThatBool(_sut.didSkipOptionalLogin, equalToBool(NO)); -} - -#pragma mark - Identification Tests -- (void) testIdentificationReturnsTheVendorIdentifierIfAvailable { - STAssertEqualObjects([_sut installationIdentification], [[UIDevice currentDevice] identifierForVendor].UUIDString, - @"Freshly initialized, it should return the vendor identifier"); -} - -- (void) testIdentificationReturnsTheAuthTokenIfSet { - [_sut setAuthenticationType:BITAuthenticatorAuthTypeUDIDProvider]; - [_sut setAuthenticationToken:@"PeterPan" withType:@"udid"]; - assertThat(_sut.installationIdentification, equalTo(@"PeterPan")); -} - -#pragma mark - authentication tests -- (void) testThatAuthenticateWithTypeEmailShowsAViewController { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.authenticationSecret = @"myscret"; - _sut.authenticationType = BITAuthenticatorAuthTypeEmail; - - [_sut authenticateWithCompletion:nil]; - - [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; -} - -- (void) testThatAuthenticateWithTypeEmailShowsAViewControllerOnlyIfAuthenticationSecretIsSet { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.authenticationSecret = nil; - _sut.authenticationType = BITAuthenticatorAuthTypeEmail; - - [_sut authenticateWithCompletion:nil]; - - [verifyCount(delegateMock, times(0)) authenticator:_sut willShowAuthenticationController:(id)anything()]; -} - - -- (void) testThatAuthenticateWithTypeEmailAndPasswordShowsAViewController { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.authenticationType = BITAuthenticatorAuthTypeEmailAndPassword; - - [_sut authenticateWithCompletion:nil]; - - [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; +#pragma mark - Initial defaults +- (void) testDefaultValues { + assertThatBool(_sut.automaticMode, equalToBool(YES)); + assertThatBool(_sut.restrictApplicationUsage, equalToBool(NO)); + assertThatBool(_sut.isIdentified, equalToBool(NO)); + assertThatBool(_sut.isValidated, equalToBool(NO)); + assertThat(_sut.authenticationSecret, equalTo(nil)); + assertThat(_sut.installationIdentifier, equalTo(nil)); + assertThat(_sut.installationIdentifierTypeString, equalTo(@"uuid")); } +#pragma mark - General identification tests - (void) testThatIsDoesntShowMoreThanOneAuthenticationController { id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); _sut.delegate = delegateMock; - _sut.authenticationType = BITAuthenticatorAuthTypeEmailAndPassword; + _sut.identificationType = BITAuthenticatorIdentificationTypeDevice; - [_sut authenticateWithCompletion:nil]; - [_sut authenticateWithCompletion:nil]; - [_sut authenticateWithCompletion:nil]; + [_sut identifyWithCompletion:nil]; + [_sut identifyWithCompletion:nil]; + [_sut identifyWithCompletion:nil]; [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; } -- (void) testThatSuccessfulAuthenticationStoresTheToken { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - - //this will prepare everything and show the viewcontroller - [_sut authenticateWithCompletion:nil]; - //fake delegate call from the viewcontroller - [_sut didAuthenticateWithToken:@"SuperToken"]; - - assertThat(_sut.authenticationToken, equalTo(@"SuperToken")); +- (void) testThatChangingIdentificationTypeResetsIdentifiedFlag { + _sut.identified = YES; + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppUser; + assertThatBool(_sut.identified, equalToBool(NO)); } -- (void) testThatSuccessfulAuthenticationCallsTheBlock { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - [_sut setAuthenticationToken:@"Test" withType:@"adid"]; - __block BOOL didAuthenticate = NO; - [_sut authenticateWithCompletion:^(NSString *authenticationToken, NSError *error) { - if(authenticationToken) didAuthenticate = YES; - }]; - [_sut didAuthenticateWithToken:@"SuperToken"]; - - assertThatBool(didAuthenticate, equalToBool(YES)); +- (void) testThatAfterChangingIdentificationTypeIdentificationIsRedone { + [_sut storeInstallationIdentifier:@"meh" withType:BITAuthenticatorIdentificationTypeHockeyAppEmail]; + _sut.identified = YES; + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppUser; + [_sut identifyWithCompletion:nil]; + assertThatBool(_sut.identified, equalToBool(NO)); + assertThat(_sut.installationIdentifier, nilValue()); } -- (void) testThatCancelledAuthenticationSetsProperError { +- (void) testThatIdentifyingAnAlreadyIdentifiedInstanceDoesNothing { id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); _sut.delegate = delegateMock; - //this will prepare everything and show the viewcontroller - __block BOOL didAuthenticateCalled = NO; - __block NSError *authenticationError = nil; - [_sut authenticateWithCompletion:^(NSString *authenticationToken, NSError *error) { - didAuthenticateCalled = YES; - authenticationError = error; - }]; - - //fake delegate call from the viewcontroller - [_sut authenticationViewControllerDidSkip:nil]; - - assertThatBool(didAuthenticateCalled, equalToBool(YES)); - assertThat(authenticationError, equalTo([NSError errorWithDomain:kBITAuthenticatorErrorDomain - code:BITAuthenticatorAuthenticationCancelled - userInfo:nil])); -} - -- (void) testThatCancelledAuthenticationResetsTheToken { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - [_sut setAuthenticationToken:@"Meh" withType:@"bdid"]; + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppEmail; + [_sut storeInstallationIdentifier:@"meh" withType:BITAuthenticatorIdentificationTypeHockeyAppEmail]; + _sut.identified = YES; - //this will prepare everything and show the viewcontroller - [_sut authenticateWithCompletion:nil]; - //fake delegate call from the viewcontroller - [_sut authenticationViewControllerDidSkip:nil]; - - assertThat(_sut.authenticationToken, equalTo(nil)); -} - -- (void) testThatKVOWorksOnApplicationIdentification { - //this will prepare everything and show the viewcontroller - [_sut authenticateWithCompletion:nil]; - - [_sut addObserver:self forKeyPath:@"installationIdentification" - options:0 - context:kInstallationIdentification]; - - //fake delegate call from the viewcontroller - [_sut authenticationViewControllerDidSkip:nil]; - assertThatBool(_KVOCalled, equalToBool(YES)); - [_sut removeObserver:self forKeyPath:@"installationIdentification"]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if(kInstallationIdentification == context) { - _KVOCalled = YES; - } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - -#pragma mark - InstallationIdentificationType -- (void) testThatEmailAuthSetsTheProperInstallationIdentificationType { - _sut.authenticationType = BITAuthenticatorAuthTypeEmail; - //fake delegate call from the viewcontroller - [_sut didAuthenticateWithToken:@"SuperToken"]; - - assertThat(_sut.installationIdentificationType, equalTo(@"iuid")); -} - -- (void) testThatPasswordAuthSetsTheProperInstallationIdentificationType { - _sut.authenticationType = BITAuthenticatorAuthTypeEmailAndPassword; - //fake delegate call from the viewcontroller - [_sut didAuthenticateWithToken:@"SuperToken"]; - - assertThat(_sut.installationIdentificationType, equalTo(@"auid")); -} - -- (void) testThatUDIDAuthSetsTheProperInstallationIdentificationType { - _sut.authenticationType = BITAuthenticatorAuthTypeUDIDProvider; - //fake delegate call from the viewcontroller - [_sut didAuthenticateWithToken:@"SuperToken"]; - - assertThat(_sut.installationIdentificationType, equalTo(@"udid")); -} - -- (void) testThatDefaultAuthReturnsProperInstallationIdentificationType { - _sut.authenticationType = BITAuthenticatorAuthTypeEmailAndPassword; - //fake delegate call from the viewcontroller - [_sut authenticationViewControllerDidSkip:nil]; - - assertThat(_sut.installationIdentificationType, equalTo(@"udid")); -} -#pragma mark - validation tests -- (void) testThatValidationWithoutTokenWantsToShowTheAuthenticationViewController { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOptional; - - [_sut validateInstallationWithCompletion:nil]; - - [verify(delegateMock) authenticator:_sut willShowAuthenticationController:(id)anything()]; -} - -#pragma mark - Lifetime checks -- (void) testThatValidationDoesntTriggerIfDisabled { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeNever; - - [_sut startManager]; + [_sut identifyWithCompletion:nil]; [verifyCount(delegateMock, never()) authenticator:_sut willShowAuthenticationController:(id)anything()]; } -- (void) testThatValidationTriggersOnStart { + +#pragma mark - Anonymous identification type +- (void) testAnonymousIdentification { + _sut.identificationType = BITAuthenticatorIdentificationTypeAnonymous; + assertThatBool(_sut.isIdentified, equalToBool(NO)); + [_sut identifyWithCompletion:^(BOOL identified, NSError *error) { + assertThatBool(identified, equalToBool(YES)); + assertThat(error, equalTo(nil)); + }]; + assertThatBool(_sut.isIdentified, equalToBool(YES)); + assertThat(_sut.installationIdentifier, notNilValue()); +} + +//anoynmous users can't be validated +- (void) testAnonymousValidation { + _sut.identificationType = BITAuthenticatorIdentificationTypeAnonymous; + assertThatBool(_sut.isValidated, equalToBool(NO)); + [_sut validateWithCompletion:^(BOOL validated, NSError *error) { + assertThatBool(_sut.validated, equalToBool(NO)); + assertThat(error, notNilValue()); + }]; + assertThatBool(_sut.isValidated, equalToBool(NO)); +} + +#pragma mark - Device identification type +- (void) testDeviceIdentificationShowsViewController { + _sut.identificationType = BITAuthenticatorIdentificationTypeDevice; id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; + + [_sut identifyWithCompletion:nil]; + + [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; +} + +#pragma mark - Email identification type +- (void) testEmailIdentificationFailsWithMissingSecret { + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppEmail; + [_sut identifyWithCompletion:^(BOOL identified, NSError *error) { + assertThatBool(identified, equalToBool(NO)); + assertThat(error, notNilValue()); + }]; +} + +- (void) testEmailIdentificationShowsViewController { + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppEmail; + _sut.authenticationSecret = @"mySecret"; + id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); + _sut.delegate = delegateMock; + + [_sut identifyWithCompletion:nil]; + + [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; +} + +- (void) testEmailValidationFailsWithMissingSecret { + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppEmail; + [_sut validateWithCompletion:^(BOOL validated, NSError *error) { + assertThatBool(validated, equalToBool(NO)); + assertThat(error, notNilValue()); + }]; +} + +- (void) testThatEmailIdentificationQueuesAnOperation { + id httpClientMock = mock(BITHockeyAppClient.class); + _sut.hockeyAppClient = httpClientMock; + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppEmail; + [_sut storeInstallationIdentifier:@"meh" withType:BITAuthenticatorIdentificationTypeHockeyAppEmail]; + _sut.authenticationSecret = @"double"; + [_sut authenticationViewController:nil + handleAuthenticationWithEmail:@"stephan@dd.de" + password:@"nopass" + completion:nil]; + [verify(httpClientMock) enqeueHTTPOperation:anything()]; +} + +#pragma mark - User identification type +- (void) testUserIdentificationShowsViewController { + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppUser; + id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); + _sut.delegate = delegateMock; + + [_sut identifyWithCompletion:nil]; + + [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; +} + + +#pragma mark - Generic validation tests +- (void) testThatValidationFailsIfNotIdentified { + _sut.identified = NO; + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppUser; + [_sut validateWithCompletion:^(BOOL validated, NSError *error) { + assertThatBool(validated, equalToBool(NO)); + assertThatInt(error.code, equalToInt(BITAuthenticatorNotIdentified)); + }]; +} + +- (void) testThatValidationCreatesAGETRequest { + id httpClientMock = mock(BITHockeyAppClient.class); + _sut.hockeyAppClient = httpClientMock; + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppEmail; + [_sut storeInstallationIdentifier:@"meh" withType:BITAuthenticatorIdentificationTypeHockeyAppEmail]; + _sut.authenticationSecret = @"double"; + [_sut validateWithCompletion:nil]; + [verify(httpClientMock) getPath:(id)anything() + parameters:(id)anything() + completion:(id)anything()]; +} + +#pragma mark - Authentication +- (void) testThatEnabledRestrictionTriggersValidation { + id clientMock = mock(BITHockeyAppClient.class); + _sut.hockeyAppClient = clientMock; + _sut.authenticationSecret = @"sekret"; + _sut.restrictApplicationUsage = YES; + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppEmail; + [_sut storeInstallationIdentifier:@"asd" withType:BITAuthenticatorIdentificationTypeHockeyAppEmail]; + [_sut authenticate]; + + [verify(clientMock) getPath:(id)anything() parameters:(id)anything() completion:(id)anything()]; +} + +- (void) testThatDisabledRestrictionDoesntTriggerValidation { + id clientMock = mock(BITHockeyAppClient.class); + _sut.hockeyAppClient = clientMock; + _sut.authenticationSecret = @"sekret"; + _sut.restrictApplicationUsage = NO; + _sut.identificationType = BITAuthenticatorIdentificationTypeHockeyAppEmail; + [_sut storeInstallationIdentifier:@"asd" withType:BITAuthenticatorIdentificationTypeHockeyAppEmail]; + [_sut authenticate]; + + [verifyCount(clientMock, never()) getPath:(id)anything() parameters:(id)anything() completion:(id)anything()]; +} + +#pragma mark - Lifetime checks +- (void) testThatAuthenticationTriggersOnStart { + id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); + _sut.delegate = delegateMock; + _sut.identificationType = BITAuthenticatorIdentificationTypeDevice; [_sut startManager]; @@ -328,135 +304,44 @@ static void *kInstallationIdentification = &kInstallationIdentification; - (void) testThatValidationTriggersOnDidBecomeActive { id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOnAppActive; + _sut.identificationType = BITAuthenticatorIdentificationTypeDevice; + _sut.restrictApplicationUsage = YES; [_sut applicationDidBecomeActive:nil]; [verify(delegateMock) authenticator:_sut willShowAuthenticationController:(id)anything()]; } +#pragma mark - Validation helper checks - (void) testThatValidationTriggersOnNewVersion { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; + _sut.restrictApplicationUsage = YES; + _sut.restrictionEnforcementFrequency = BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch; + _sut.validated = YES; _sut.lastAuthenticatedVersion = @"111xxx"; - - [_sut startManager]; - - [verify(delegateMock) authenticator:_sut willShowAuthenticationController:(id)anything()]; + assertThatBool(_sut.needsValidation, equalToBool(YES)); } - (void) testThatValidationDoesNotTriggerOnSameVersion { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; + _sut.restrictApplicationUsage = YES; + _sut.restrictionEnforcementFrequency = BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch; + _sut.validated = YES; _sut.lastAuthenticatedVersion = _sut.executableUUID; - - [_sut startManager]; - - [verifyCount(delegateMock, never()) authenticator:_sut willShowAuthenticationController:(id)anything()]; -} - -- (void) testThatFailedValidationCallsTheCompletionBlock { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; - - __block BOOL validated = YES; - __block NSError *error = nil; - tValidationCompletion completion = ^(BOOL validated_, NSError *error_) { - validated = validated_; - error = error_; - }; - [_sut validateInstallationWithCompletion:completion]; - [_sut validationFailedWithError:[NSError errorWithDomain:kBITAuthenticatorErrorDomain code:0 userInfo:nil] - completion:completion]; - - assertThatBool(validated, equalToBool(NO)); - assertThat(error, notNilValue()); -} - -- (void) testThatFailedRequiredValidationShowsAuthenticationViewController { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOnAppActive; - - [_sut validationFailedWithError:[NSError errorWithDomain:kBITAuthenticatorErrorDomain code:0 userInfo:nil] - completion:_sut.defaultValidationCompletionBlock]; - [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; -} - -- (void) testThatFailedOptionalValidationDoesNotShowAuthenticationViewController { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOptional; - - [_sut validationFailedWithError:nil - completion:_sut.defaultValidationCompletionBlock]; - [verifyCount(delegateMock, never()) authenticator:_sut willShowAuthenticationController:(id)anything()]; -} - -- (void) testThatSuccessValidationCallsTheCompletionBlock { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; - - __block BOOL validated = NO; - __block NSError *error = nil; - tValidationCompletion completion = ^(BOOL validated_, NSError *error_) { - validated = validated_; - error = error_; - }; - [_sut validateInstallationWithCompletion:completion]; - [_sut validationSucceededWithCompletion:completion]; - - assertThatBool(validated, equalToBool(YES)); - assertThat(error, nilValue()); -} - -- (void) testThatAuthTokenIsResettingWhenVendorIdentifierChanged { - MyDeviceWithIdentifierForVendor *device = [MyDeviceWithIdentifierForVendor new]; - _sut.currentDevice = (id)device; - [_sut didAuthenticateWithToken:@"SuperToken"]; - NSString *ident = [_sut installationIdentification]; - assertThat(ident, equalTo(@"SuperToken")); - device.identifierForVendor = [NSUUID UUID]; - ident = [_sut installationIdentification]; - assertThat(ident, isNot(equalTo(@"SuperToken"))); + assertThatBool(_sut.needsValidation, equalToBool(NO)); } #pragma mark - Test installationIdentificationValidated Flag -- (void) testThatFlagIsResetOnFailedValidation { - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; - assertThatBool(_sut.installationIdentificationValidated, equalToBool(NO)); -} - -- (void) testThatFlagIsSetOnFailedValidation { - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; - [_sut validationSucceededWithCompletion:nil]; - assertThatBool(_sut.installationIdentificationValidated, equalToBool(YES)); -} - - (void) testThatApplicationBackgroundingResetsValidatedFlagInValidationTypeOnAppActive { - _sut.validationType = BITAuthenticatorValidationTypeOnAppActive; - //trigger flag set to YES - [_sut validationSucceededWithCompletion:nil]; + _sut.restrictionEnforcementFrequency = BITAuthenticatorAppRestrictionEnforcementOnAppActive; + _sut.validated = YES; [_sut applicationWillResignActive:nil]; - assertThatBool(_sut.installationIdentificationValidated, equalToBool(NO)); + assertThatBool(_sut.isValidated, equalToBool(NO)); } - (void) testThatApplicationBackgroundingKeepValidatedFlag { - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; - //trigger flag set to YES - [_sut validationSucceededWithCompletion:nil]; + _sut.restrictApplicationUsage = BITAuthenticatorAppRestrictionEnforcementOnFirstLaunch; + _sut.validated = YES; [_sut applicationWillResignActive:nil]; - assertThatBool(_sut.installationIdentificationValidated, equalToBool(YES)); -} - -- (void) testThatInitialAuthSetsValidatedFlag { - _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; - [_sut didAuthenticateWithToken:@"MyToken"]; - assertThatBool(_sut.installationIdentificationValidated, equalToBool(YES)); + assertThatBool(_sut.isValidated, equalToBool(YES)); } @end