diff --git a/Classes/BITAuthenticator.h b/Classes/BITAuthenticator.h index bad92467d6..a0770ad056 100644 --- a/Classes/BITAuthenticator.h +++ b/Classes/BITAuthenticator.h @@ -46,6 +46,15 @@ typedef void(^tValidationCompletion)(BOOL validated, NSError *error); @property (nonatomic, weak) id delegate; +/** + The authentication token from HockeyApp. + + Set the token to the `Secret ID` which HockeyApp provides for every app. + + When running the app from the App Store, this setting is ignored. + */ +@property (nonatomic, copy) NSString *authenticationSecret; + #pragma mark - Identification /** * Provides an identification for the current app installation @@ -53,7 +62,7 @@ typedef void(^tValidationCompletion)(BOOL validated, NSError *error); * 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 this the current vendorIdentifier provided by UIKit. + * it is disabled, it returns the vendorIdentifier provided by UIKit. * * @return a string identifying this app installation */ @@ -77,8 +86,8 @@ typedef void(^tValidationCompletion)(BOOL validated, NSError *error); /** * Validate the app installation * - * Depending on @see loginOption, this is reset after the app becomes active and tries to revalidate - * the 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. * diff --git a/Classes/BITAuthenticator.m b/Classes/BITAuthenticator.m index d5f8d54b89..9ec0e3c8e0 100644 --- a/Classes/BITAuthenticator.m +++ b/Classes/BITAuthenticator.m @@ -40,15 +40,17 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe switch (self.validationType) { case BITAuthenticatorValidationTypeOnAppActive: - [self validateInstallationWithCompletion:nil]; + [self validateInstallationWithCompletion:[self defaultValidationCompletionBlock]]; break; case BITAuthenticatorValidationTypeOnFirstLaunch: if(![self.lastAuthenticatedVersion isEqualToString:self.executableUUID]) { - [self validateInstallationWithCompletion:nil]; + [self validateInstallationWithCompletion:[self defaultValidationCompletionBlock]]; } break; - case BITAuthenticatorValidationTypeNever: case BITAuthenticatorValidationTypeOptional: + //TODO: what to do in optional case? + break; + case BITAuthenticatorValidationTypeNever: break; } } @@ -99,9 +101,10 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe } }]; } else { - NSString *validationEndpoint = @"validate"; + NSString *validationPath = [NSString stringWithFormat:@"api/3/apps/%@/identity/validate", self.encodedAppIdentifier]; __weak typeof (self) weakSelf = self; - [self getPath:validationEndpoint + [self getPath:validationPath + parameters:[self validationParameters] completion:^(BITHTTPOperation *operation, id response, NSError *error) { typeof (self) strongSelf = weakSelf; if(nil == response) { @@ -112,20 +115,34 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain code:BITAuthenticatorNetworkError userInfo:userInfo]; - [strongSelf validationFailedWithError:error]; + [strongSelf validationFailedWithError:error completion:completion]; } else { NSError *validationParseError = nil; BOOL isValidated = [strongSelf.class isValidationResponseValid:response error:&validationParseError]; if(isValidated) { - [strongSelf validationSucceeded]; + [strongSelf validationSucceededWithCompletion:completion]; } else { - [strongSelf validationFailedWithError:validationParseError]; + [strongSelf validationFailedWithError:validationParseError completion:completion]; } } }]; } } +- (NSDictionary*) validationParameters { + NSParameterAssert(self.authenticationToken); + NSDictionary *params = nil; + switch (self.authenticationType) { + case BITAuthenticatorAuthTypeEmail: + params = @{@"iuid" : self.authenticationToken}; + break; + case BITAuthenticatorAuthTypeEmailAndPassword: + params = @{@"auid" : self.authenticationToken}; + break; + } + return params; +} + + (BOOL) isValidationResponseValid:(id) response error:(NSError **) error { NSParameterAssert(response); @@ -150,8 +167,31 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe return NO; } - //TODO: add proper validation - return [jsonObject[@"isValid"] boolValue]; + NSString *status = jsonObject[@"status"]; + if([status isEqualToString:@"not authorized"]) { + if(error) { + *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain + code:BITAuthenticatorNotAuthorized + userInfo:nil]; + } + return NO; + } else if([status isEqualToString:@"not found"]) { + if(error) { + *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain + code:BITAuthenticatorNotAuthorized + userInfo:nil]; + } + return NO; + } else if([status isEqualToString:@"validated"]) { + return YES; + } else { + if(error) { + *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain + code:BITAuthenticatorAPIServerReturnedInvalidRespone + userInfo:nil]; + } + return NO; + } } - (void)authenticateWithCompletion:(tAuthenticationCompletion)completion { @@ -160,27 +200,39 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe return; } - BITAuthenticationViewController *viewController = [[BITAuthenticationViewController alloc] initWithStyle:UITableViewStyleGrouped]; - viewController.delegate = self; - viewController.authenticator = self; + BOOL requiresPassword; switch (self.authenticationType) { case BITAuthenticatorAuthTypeEmailAndPassword: - viewController.requirePassword = YES; + requiresPassword = YES; break; case BITAuthenticatorAuthTypeEmail: - viewController.requirePassword = NO; + requiresPassword = NO; break; } - if(viewController) { - [self.delegate authenticator:self willShowAuthenticationController:viewController]; - _authenticationController = viewController; - _authenticationCompletionBlock = completion; - UIViewController *rootViewController = [self.findVisibleWindow rootViewController]; - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; - [rootViewController presentModalViewController:navController - animated:YES]; + BITAuthenticationViewController *viewController = [[BITAuthenticationViewController alloc] initWithApplicationIdentifier:self.encodedAppIdentifier + requirePassword:requiresPassword + delegate:self]; + viewController.authenticator = self; + switch (self.validationType) { + case BITAuthenticatorValidationTypeNever: + case BITAuthenticatorValidationTypeOptional: + viewController.showsCancelButton = YES; + break; + case BITAuthenticatorValidationTypeOnAppActive: + case BITAuthenticatorValidationTypeOnFirstLaunch: + viewController.showsCancelButton = NO; + break; } + + [self.delegate authenticator:self willShowAuthenticationController:viewController]; + + _authenticationController = viewController; + _authenticationCompletionBlock = completion; + UIViewController *rootViewController = [self.findVisibleWindow rootViewController]; + UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController]; + [rootViewController presentModalViewController:navController + animated:YES]; } #pragma mark - AuthenticationViewControllerDelegate @@ -202,8 +254,10 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe - (void) authenticationViewController:(UIViewController*) viewController authenticatedWithToken:(NSString*) token { + [viewController dismissModalViewControllerAnimated:YES]; _authenticationController = nil; self.authenticationToken = token; + self.lastAuthenticatedVersion = [self executableUUID]; if(self.authenticationCompletionBlock) { self.authenticationCompletionBlock(self.authenticationToken, nil); self.authenticationCompletionBlock = nil; @@ -213,29 +267,17 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe } #pragma mark - Validation Pseudo-Delegate -- (void)validationFailedWithError:(NSError *)validationError { - if(self.validationCompletion) { - self.validationCompletion(NO, validationError); - self.validationCompletion = nil; +- (void)validationFailedWithError:(NSError *)validationError completion:(tValidationCompletion) completion{ + if(completion) { + completion(NO, validationError); } else { [self.delegate authenticator:self failedToValidateInstallationWithError:validationError]; } - - switch (self.validationType) { - case BITAuthenticatorValidationTypeNever: - case BITAuthenticatorValidationTypeOptional: - break; - case BITAuthenticatorValidationTypeOnAppActive: - case BITAuthenticatorValidationTypeOnFirstLaunch: - //TODO tell delegate and block the application - break; - } } -- (void)validationSucceeded { - if(self.validationCompletion) { - self.validationCompletion(YES, nil); - self.validationCompletion = nil; +- (void)validationSucceededWithCompletion:(tValidationCompletion) completion { + if(completion) { + completion(YES, nil); } else { [self.delegate authenticatorDidValidateInstallation:self]; } @@ -302,10 +344,24 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe #pragma mark - Application Lifecycle - (void)applicationDidBecomeActive:(NSNotification *)note { if(BITAuthenticatorValidationTypeOnAppActive == self.validationType) { - [self validateInstallationWithCompletion:nil]; + [self validateInstallationWithCompletion:[self defaultValidationCompletionBlock]]; } } +- (tValidationCompletion) defaultValidationCompletionBlock { + return ^(BOOL validated, NSError *error) { + switch (self.validationType) { + case BITAuthenticatorValidationTypeNever: + case BITAuthenticatorValidationTypeOptional: + break; + case BITAuthenticatorValidationTypeOnAppActive: + case BITAuthenticatorValidationTypeOnFirstLaunch: + [self authenticateWithCompletion:nil]; + break; + } + }; +}; + #pragma mark - Networking - (NSMutableURLRequest *) requestWithMethod:(NSString*) method path:(NSString *) path diff --git a/Classes/BITAuthenticator_Private.h b/Classes/BITAuthenticator_Private.h index 15f7cc8302..86e31a6930 100644 --- a/Classes/BITAuthenticator_Private.h +++ b/Classes/BITAuthenticator_Private.h @@ -30,7 +30,6 @@ @property (nonatomic, copy) NSString *lastAuthenticatedVersion; @property (nonatomic, copy) tAuthenticationCompletion authenticationCompletionBlock; -@property (nonatomic, copy) tValidationCompletion validationCompletion; /** * removes all previously stored authentication tokens, UDIDs, etc @@ -44,8 +43,8 @@ - (void) applicationDidBecomeActive:(NSNotification*) note; #pragma mark - Validation callbacks -- (void) validationSucceeded; -- (void) validationFailedWithError:(NSError *) validationError; +- (void) validationSucceededWithCompletion:(tValidationCompletion) completion; +- (void) validationFailedWithError:(NSError *) validationError completion:(tValidationCompletion) completion; #pragma mark - Networking helpers (TODO: move to base-class / networking component) diff --git a/Support/HockeySDKTests/BITAuthenticatorTests.m b/Support/HockeySDKTests/BITAuthenticatorTests.m index f1a2499a95..b6dbcd6c9f 100644 --- a/Support/HockeySDKTests/BITAuthenticatorTests.m +++ b/Support/HockeySDKTests/BITAuthenticatorTests.m @@ -41,6 +41,7 @@ } - (void)tearDown { + [_sut cancelOperationsWithPath:nil method:nil]; [_sut cleanupInternalStorage]; _sut = nil; @@ -201,16 +202,6 @@ [verify(delegateMock) authenticator:_sut willShowAuthenticationController:(id)anything()]; } -- (void) testThatValidationFailsWithoutAnURL { - id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); - _sut.delegate = delegateMock; - _sut.authenticationToken = @"Test"; - - [_sut validateInstallationWithCompletion:nil]; - - [verify(delegateMock) authenticator:_sut failedToValidateInstallationWithError:(id)anything()]; -} - #pragma mark - Lifetime checks - (void) testThatValidationDoesntTriggerIfDisabled { id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); @@ -270,7 +261,7 @@ _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; [_sut validateInstallationWithCompletion:nil]; - [_sut validationFailedWithError:nil]; + [_sut validationFailedWithError:nil completion:nil]; [verifyCount(delegateMock, times(1)) authenticator:_sut failedToValidateInstallationWithError:(id)anything()]; [verifyCount(delegateMock, never()) authenticatorDidValidateInstallation:_sut]; @@ -282,7 +273,7 @@ _sut.validationType = BITAuthenticatorValidationTypeOnFirstLaunch; [_sut validateInstallationWithCompletion:nil]; - [_sut validationSucceeded]; + [_sut validationSucceededWithCompletion:nil]; [verifyCount(delegateMock, never()) authenticator:_sut failedToValidateInstallationWithError:(id)anything()]; [verifyCount(delegateMock, times(1)) authenticatorDidValidateInstallation:_sut]; @@ -291,37 +282,75 @@ #pragma mark - Networking base tests - (void) testThatURLRequestHasBaseURLSet { _sut.serverURL = @"http://myserver.com"; - NSMutableURLRequest *request = [_sut requestWithMethod:@"GET" path:nil]; + NSMutableURLRequest *request = [_sut requestWithMethod:@"GET" path:nil parameters:nil]; assertThat(request.URL, equalTo([NSURL URLWithString:@"http://myserver.com/"])); } - (void) testThatURLRequestHasPathAppended { _sut.serverURL = @"http://myserver.com"; - NSMutableURLRequest *request = [_sut requestWithMethod:@"GET" path:@"projects"]; + NSMutableURLRequest *request = [_sut requestWithMethod:@"GET" path:@"projects" parameters:nil]; assertThat(request.URL, equalTo([NSURL URLWithString:@"http://myserver.com/projects"])); } - (void) testThatURLRequestHasMethodSet { - NSMutableURLRequest *request = [_sut requestWithMethod:@"POST" path:nil]; + NSMutableURLRequest *request = [_sut requestWithMethod:@"POST" path:nil parameters:nil]; assertThat(request.HTTPMethod, equalTo(@"POST")); } - (void) testThatOperationHasURLRequestSet { _sut.serverURL = @"http://myserver.com"; - NSURLRequest *r = [_sut requestWithMethod:@"PUT" path:@"x"]; + NSURLRequest *r = [_sut requestWithMethod:@"PUT" path:@"x" parameters:nil]; BITHTTPOperation *op = [_sut operationWithURLRequest:r completion:nil]; assertThat(op.URLRequest, equalTo(r)); } +- (void) testThatURLRequestHasParametersInGetAppended { + NSDictionary *parameters = @{ + @"email" : @"peter@pan.de", + @"push" : @"pop", + }; + NSMutableURLRequest *request = [_sut requestWithMethod:@"GET" + path:@"something" + parameters:parameters]; + NSURL *url = request.URL; + NSString *params = [url query]; + NSArray *paramPairs = [params componentsSeparatedByString:@"&"]; + assertThat(paramPairs, hasCountOf(2)); + + NSMutableDictionary *dict = [NSMutableDictionary new]; + for(NSString *paramPair in paramPairs) { + NSArray *a = [paramPair componentsSeparatedByString:@"="]; + assertThat(a, hasCountOf(2)); + dict[a[0]] = a[1]; + } + assertThat(dict, equalTo(parameters)); +} + +- (void) testThatURLRequestHasParametersInPostInTheBody { + //pending +} + #pragma mark - Convenience methods - (void) testThatGetPathCreatesAndEnquesAnOperation { assertThatUnsignedInt(_sut.operationQueue.operationCount, equalToUnsignedInt(0)); [given([_sut operationWithURLRequest:(id)anything() - completion:(id)anything()]) willReturn:[NSOperation new]]; + completion:nil]) willReturn:[NSOperation new]]; [_sut getPath:@"endpoint" + parameters:nil + completion:nil]; + assertThatUnsignedInt(_sut.operationQueue.operationCount, equalToUnsignedInt(1)); +} + +- (void) testThatPostPathCreatesAndEnquesAnOperation { + assertThatUnsignedInt(_sut.operationQueue.operationCount, equalToUnsignedInt(0)); + [given([_sut operationWithURLRequest:nil + completion:nil]) willReturn:[NSOperation new]]; + + [_sut postPath:@"endpoint" + parameters:nil completion:nil]; assertThatUnsignedInt(_sut.operationQueue.operationCount, equalToUnsignedInt(1)); } @@ -343,9 +372,9 @@ - (void) testThatOperationCancellingMatchesAllOperationsWithNilMethod { [_sut.operationQueue setSuspended:YES]; - NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:nil]; - NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:nil]; - NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil]; + NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:nil parameters:nil]; + NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:nil parameters:nil]; + NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil parameters:nil]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestGet completion:nil]]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestPut @@ -359,9 +388,9 @@ - (void) testThatOperationCancellingMatchesAllOperationsWithNilPath { [_sut.operationQueue setSuspended:YES]; - NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:@"test"]; - NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:@"Another/acas"]; - NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil]; + NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:@"test" parameters:nil]; + NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:@"Another/acas" parameters:nil]; + NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil parameters:nil]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestGet completion:nil]]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestPut @@ -375,9 +404,11 @@ - (void) testThatOperationCancellingMatchesAllOperationsWithSetPath { - NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:@"test"]; - NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:@"Another/acas"]; - NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil]; + NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:@"test" parameters:nil]; + NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:@"Another/acas" parameters:nil]; + NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil parameters:nil]; + [_sut.operationQueue setSuspended:YES]; + [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestGet completion:nil]]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestPut @@ -385,14 +416,14 @@ [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestPost completion:nil]]; assertThatUnsignedInt(_sut.operationQueue.operationCount, equalToUnsignedInt(3)); - [_sut cancelOperationsWithPath:@"Another/acas" method:nil]; - assertThatUnsignedInt(_sut.operationQueue.operationCount, equalToUnsignedInt(2)); + NSUInteger numCancelled = [_sut cancelOperationsWithPath:@"Another/acas" method:nil]; + assertThatUnsignedInt(numCancelled, equalToUnsignedInt(1)); } - (void) testThatOperationCancellingMatchesAllOperationsWithSetMethod { - NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:@"test"]; - NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:@"Another/acas"]; - NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil]; + NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:@"test" parameters:nil]; + NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:@"Another/acas" parameters:nil]; + NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil parameters:nil]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestGet completion:nil]]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestPut @@ -405,9 +436,9 @@ } - (void) testThatOperationCancellingMatchesAllOperationsWithSetMethodAndPath { - NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:@"test"]; - NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:@"Another/acas"]; - NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil]; + NSURLRequest *requestGet = [_sut requestWithMethod:@"GET" path:@"test" parameters:nil]; + NSURLRequest *requestPut = [_sut requestWithMethod:@"PUT" path:@"Another/acas" parameters:nil]; + NSURLRequest *requestPost = [_sut requestWithMethod:@"POST" path:nil parameters:nil]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestGet completion:nil]]; [_sut enqeueHTTPOperation:[_sut operationWithURLRequest:requestPut