refine validation logic

This commit is contained in:
Stephan Diederich 2013-08-15 22:30:39 +02:00
parent 4647907ea0
commit ff555cb87d
4 changed files with 177 additions and 82 deletions

View File

@ -46,6 +46,15 @@ typedef void(^tValidationCompletion)(BOOL validated, NSError *error);
@property (nonatomic, weak) id<BITAuthenticatorDelegate> 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.
*

View File

@ -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

View File

@ -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)

View File

@ -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