refactoring: move auth-logic to the authenticator

via a delegation-completionBlock-combo
This commit is contained in:
Stephan Diederich 2013-09-06 19:23:58 +02:00
parent 6c29a72a98
commit 567b0def60
5 changed files with 175 additions and 147 deletions

View File

@ -13,19 +13,7 @@
@interface BITAuthenticationViewController : UITableViewController @interface BITAuthenticationViewController : UITableViewController
- (instancetype) initWithApplicationIdentifier:(NSString*) encodedApplicationIdentifier - (instancetype) initWithDelegate:(id<BITAuthenticationViewControllerDelegate>) delegate;
requirePassword:(BOOL) requiresPassword
delegate:(id<BITAuthenticationViewControllerDelegate>) delegate;
/**
* must be set
*/
@property (nonatomic, strong) BITHockeyAppClient *hockeyAppClient;
/**
* The application's id to identifiy it in the backend
*/
@property (nonatomic, copy) NSString *encodedApplicationIdentifier;
/** /**
* can be set to YES to also require the users password * can be set to YES to also require the users password
@ -39,11 +27,6 @@
* defaults to YES * defaults to YES
*/ */
@property (nonatomic, assign) BOOL showsCancelButton; @property (nonatomic, assign) BOOL showsCancelButton;
/**
* TODO: instead of passing the whole authenticator, we actually only need
* something to create and enqueue BITHTTPOperations
*/
@property (nonatomic, weak) BITAuthenticator *authenticator;
@property (nonatomic, weak) id<BITAuthenticationViewControllerDelegate> delegate; @property (nonatomic, weak) id<BITAuthenticationViewControllerDelegate> delegate;
@ -51,7 +34,27 @@
@protocol BITAuthenticationViewControllerDelegate<NSObject> @protocol BITAuthenticationViewControllerDelegate<NSObject>
/**
* called then the user cancelled
*
* @param viewController the delegating viewcontroller
*/
- (void) authenticationViewControllerDidCancel:(UIViewController*) viewController; - (void) authenticationViewControllerDidCancel:(UIViewController*) viewController;
- (void) authenticationViewController:(UIViewController*) viewController authenticatedWithToken:(NSString*) token;
/**
* called when the user wants to login
*
* @param viewController the delegating viewcontroller
* @param email the content of the email-field
* @param password the content of the password-field (if existent)
* @param completion Must be called by the delegate once the auth-task completed
* This viewcontroller shows an activity-indicator in between and blocks
* the UI. if succeeded is NO, it shows an alertView presenting the error
* given by the completion block
*/
- (void) authenticationViewController:(UIViewController*) viewController
handleAuthenticationWithEmail:(NSString*) email
password:(NSString*) password
completion:(void(^)(BOOL succeeded, NSError *error)) completion;
@end @end

View File

@ -23,14 +23,10 @@
@implementation BITAuthenticationViewController @implementation BITAuthenticationViewController
- (instancetype) initWithApplicationIdentifier:(NSString*) encodedApplicationIdentifier - (instancetype) initWithDelegate:(id<BITAuthenticationViewControllerDelegate>)delegate {
requirePassword:(BOOL) requiresPassword
delegate:(id<BITAuthenticationViewControllerDelegate>) delegate {
self = [super initWithStyle:UITableViewStyleGrouped]; self = [super initWithStyle:UITableViewStyleGrouped];
if (self) { if (self) {
self.title = BITHockeyLocalizedString(@"HockeyAuthenticatorViewControllerTitle"); self.title = BITHockeyLocalizedString(@"HockeyAuthenticatorViewControllerTitle");
_encodedApplicationIdentifier = [encodedApplicationIdentifier copy];
_requirePassword = requiresPassword;
_delegate = delegate; _delegate = delegate;
_showsCancelButton = YES; _showsCancelButton = YES;
} }
@ -222,120 +218,30 @@
- (void)saveAction:(id)sender { - (void)saveAction:(id)sender {
[self showLoginUI:YES]; [self showLoginUI:YES];
NSParameterAssert(self.encodedApplicationIdentifier);
NSString *authenticationPath = [self authenticationPath];
NSDictionary *params = [self parametersForAuthentication];
__weak typeof(self) weakSelf = self; __weak typeof(self) weakSelf = self;
[self.hockeyAppClient postPath:authenticationPath [self.delegate authenticationViewController:self
parameters:params handleAuthenticationWithEmail:self.email
completion:^(BITHTTPOperation *operation, id response, NSError *error) { password:self.password
completion:^(BOOL succeeded, NSError *error) {
if(succeeded) {
//controller shoud dismiss us shortly..
} else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
message:error.localizedDescription
delegate:nil
cancelButtonTitle:BITHockeyLocalizedString(@"OK")
otherButtonTitles:nil];
[alertView show];
typeof(self) strongSelf = weakSelf; typeof(self) strongSelf = weakSelf;
if(nil == response) { [strongSelf showLoginUI:NO];
//TODO think about alertview messages
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:@"Failed to authenticate"
delegate:nil
cancelButtonTitle:BITHockeyLocalizedString(@"OK")
otherButtonTitles:nil];
[alert show];
} else if(401 == operation.response.statusCode) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:@"Not authorized"
delegate:nil
cancelButtonTitle:BITHockeyLocalizedString(@"OK")
otherButtonTitles:nil];
[alert show];
} else {
NSError *authParseError = nil;
NSString *authToken = [strongSelf.class authenticationTokenFromReponse:response
error:&authParseError];
if(nil == authToken) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:@"Failed to authenticate"
delegate:nil
cancelButtonTitle:BITHockeyLocalizedString(@"OK")
otherButtonTitles:nil];
[alert show];
} else {
[strongSelf.delegate authenticationViewController:strongSelf authenticatedWithToken:authToken];
} }
}
[self showLoginUI:NO];
}]; }];
} }
- (NSDictionary *) parametersForAuthentication {
if(self.requirePassword) {
return @{ @"user" : [NSString stringWithFormat:@"%@:%@", self.email, self.password] };
} else {
NSString *authCode = BITHockeyMD5([NSString stringWithFormat:@"%@%@",
self.authenticator.authenticationSecret ? : @"",
self.email ? : @""]);
return @{
@"email" : self.email,
@"authcode" : authCode.lowercaseString,
};
}
}
- (NSString *) authenticationPath {
if(self.requirePassword) {
return [NSString stringWithFormat:@"api/3/apps/%@/identity/authorize", self.encodedApplicationIdentifier];
} else {
return [NSString stringWithFormat:@"api/3/apps/%@/identity/check", self.encodedApplicationIdentifier];
}
}
- (void) showLoginUI:(BOOL) enableLoginUI { - (void) showLoginUI:(BOOL) enableLoginUI {
self.navigationItem.rightBarButtonItem.enabled = !enableLoginUI; self.navigationItem.rightBarButtonItem.enabled = !enableLoginUI;
self.tableView.userInteractionEnabled = !enableLoginUI; self.tableView.userInteractionEnabled = !enableLoginUI;
} }
+ (NSString *) authenticationTokenFromReponse:(id) response error:(NSError **) error {
NSParameterAssert(response);
NSError *jsonParseError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParseError];
if(nil == jsonObject) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidRespone
userInfo:(jsonParseError ? @{NSUnderlyingErrorKey : jsonParseError} : nil)];
}
return nil;
}
if(![jsonObject isKindOfClass:[NSDictionary class]]) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidRespone
userInfo:nil];
}
return nil;
}
NSString *status = jsonObject[@"status"];
if(nil == status) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidRespone
userInfo:nil];
}
return nil;
} else if([status isEqualToString:@"identified"]) {
return jsonObject[@"iuid"];
} else if([status isEqualToString:@"authorized"]) {
return jsonObject[@"auid"];
} else {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorNotAuthorized
userInfo:nil];
}
return nil;
}
}
@end @end

View File

@ -82,6 +82,7 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
} }
} }
#pragma mark - Validation
- (void) validateInstallationWithCompletion:(tValidationCompletion) completion { - (void) validateInstallationWithCompletion:(tValidationCompletion) completion {
if(nil == self.authenticationToken) { if(nil == self.authenticationToken) {
[self authenticateWithCompletion:^(NSString *authenticationToken, NSError *error) { [self authenticateWithCompletion:^(NSString *authenticationToken, NSError *error) {
@ -189,6 +190,8 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
} }
} }
#pragma mark - Authentication
- (void)authenticateWithCompletion:(tAuthenticationCompletion)completion { - (void)authenticateWithCompletion:(tAuthenticationCompletion)completion {
if(_authenticationController) { if(_authenticationController) {
BITHockeyLog(@"Already authenticating. Ignoring request"); BITHockeyLog(@"Already authenticating. Ignoring request");
@ -205,10 +208,8 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
break; break;
} }
BITAuthenticationViewController *viewController = [[BITAuthenticationViewController alloc] initWithApplicationIdentifier:self.encodedAppIdentifier BITAuthenticationViewController *viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
requirePassword:requiresPassword viewController.requirePassword = requiresPassword;
delegate:self];
viewController.authenticator = self;
switch (self.validationType) { switch (self.validationType) {
case BITAuthenticatorValidationTypeNever: case BITAuthenticatorValidationTypeNever:
case BITAuthenticatorValidationTypeOptional: case BITAuthenticatorValidationTypeOptional:
@ -232,6 +233,16 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
animated:YES]; animated:YES];
} }
- (void) didAuthenticateWithToken:(NSString*) token {
[_authenticationController dismissModalViewControllerAnimated:YES];
_authenticationController = nil;
self.authenticationToken = token;
self.lastAuthenticatedVersion = [self executableUUID];
if(self.authenticationCompletionBlock) {
self.authenticationCompletionBlock(self.authenticationToken, nil);
self.authenticationCompletionBlock = nil;
}
}
#pragma mark - AuthenticationViewControllerDelegate #pragma mark - AuthenticationViewControllerDelegate
- (void) authenticationViewControllerDidCancel:(UIViewController*) viewController { - (void) authenticationViewControllerDidCancel:(UIViewController*) viewController {
[viewController dismissModalViewControllerAnimated:YES]; [viewController dismissModalViewControllerAnimated:YES];
@ -248,14 +259,119 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
} }
- (void)authenticationViewController:(UIViewController *)viewController - (void)authenticationViewController:(UIViewController *)viewController
authenticatedWithToken:(NSString*) token { handleAuthenticationWithEmail:(NSString *)email
[viewController dismissModalViewControllerAnimated:YES]; password:(NSString *)password
_authenticationController = nil; completion:(void (^)(BOOL, NSError *))completion {
self.authenticationToken = token; NSParameterAssert(email && email.length);
self.lastAuthenticatedVersion = [self executableUUID]; NSParameterAssert(self.authenticationType == BITAuthenticatorAuthTypeEmail || (password && password.length));
if(self.authenticationCompletionBlock) { NSString *authenticationPath = [self authenticationPath];
self.authenticationCompletionBlock(self.authenticationToken, nil); NSDictionary *params = [self parametersForAuthenticationEmail:email password:password];
self.authenticationCompletionBlock = nil;
__weak typeof (self) weakSelf = self;
[self.hockeyAppClient postPath:authenticationPath
parameters:params
completion:^(BITHTTPOperation *operation, id response, NSError *error) {
typeof (self) strongSelf = weakSelf;
if(nil == response) {
NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidRespone
userInfo:@{
//TODO localize
NSLocalizedDescriptionKey : @"Failed to authenticate"
}];
completion(NO, error);
} else if(401 == operation.response.statusCode) {
NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorNotAuthorized
userInfo:@{
//TODO localize
NSLocalizedDescriptionKey : @"Not authorized"
}];
completion(NO, error);
} else {
NSError *authParseError = nil;
NSString *authToken = [strongSelf.class authenticationTokenFromReponse:response
error:&authParseError];
if(nil == authToken) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil
message:@"Failed to authenticate"
delegate:nil
cancelButtonTitle:BITHockeyLocalizedString(@"OK")
otherButtonTitles:nil];
[alert show];
} else {
[self didAuthenticateWithToken:authToken];
}
}
}];
}
- (NSDictionary *) parametersForAuthenticationEmail:(NSString*) email password:(NSString*) password {
if(BITAuthenticatorAuthTypeEmailAndPassword == self.authenticationType) {
return @{ @"user" : [NSString stringWithFormat:@"%@:%@", email, password] };
} else {
NSString *authCode = BITHockeyMD5([NSString stringWithFormat:@"%@%@",
self.authenticationSecret ? : @"",
email ? : @""]);
return @{
@"email" : email ? : @"",
@"authcode" : authCode.lowercaseString,
};
}
}
- (NSString *) authenticationPath {
if(BITAuthenticatorAuthTypeEmailAndPassword == self.authenticationType) {
return [NSString stringWithFormat:@"api/3/apps/%@/identity/authorize", self.encodedAppIdentifier];
} else {
return [NSString stringWithFormat:@"api/3/apps/%@/identity/check", self.encodedAppIdentifier];
}
}
+ (NSString *) authenticationTokenFromReponse:(id) response error:(NSError **) error {
NSParameterAssert(response);
NSError *jsonParseError = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParseError];
if(nil == jsonObject) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidRespone
userInfo:(jsonParseError ? @{NSUnderlyingErrorKey : jsonParseError} : nil)];
}
return nil;
}
if(![jsonObject isKindOfClass:[NSDictionary class]]) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidRespone
userInfo:nil];
}
return nil;
}
NSString *status = jsonObject[@"status"];
if(nil == status) {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorAPIServerReturnedInvalidRespone
userInfo:nil];
}
return nil;
} else if([status isEqualToString:@"identified"]) {
return jsonObject[@"iuid"];
} else if([status isEqualToString:@"authorized"]) {
return jsonObject[@"auid"];
} else {
if(error) {
*error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain
code:BITAuthenticatorNotAuthorized
userInfo:nil];
}
return nil;
} }
} }

View File

@ -62,6 +62,9 @@
*/ */
- (void) authenticateWithCompletion:(tAuthenticationCompletion) completion; - (void) authenticateWithCompletion:(tAuthenticationCompletion) completion;
#pragma mark - Internal Auth callbacks
- (void) didAuthenticateWithToken:(NSString*) token;
#pragma mark - Validation #pragma mark - Validation
/** /**
* Validate the app installation * Validate the app installation

View File

@ -150,7 +150,7 @@ static void *kInstallationIdentification = &kInstallationIdentification;
//this will prepare everything and show the viewcontroller //this will prepare everything and show the viewcontroller
[_sut authenticateWithCompletion:nil]; [_sut authenticateWithCompletion:nil];
//fake delegate call from the viewcontroller //fake delegate call from the viewcontroller
[_sut authenticationViewController:nil authenticatedWithToken:@"SuperToken"]; [_sut didAuthenticateWithToken:@"SuperToken"];
assertThat(_sut.authenticationToken, equalTo(@"SuperToken")); assertThat(_sut.authenticationToken, equalTo(@"SuperToken"));
} }
@ -163,7 +163,7 @@ static void *kInstallationIdentification = &kInstallationIdentification;
[_sut authenticateWithCompletion:^(NSString *authenticationToken, NSError *error) { [_sut authenticateWithCompletion:^(NSString *authenticationToken, NSError *error) {
if(authenticationToken) didAuthenticate = YES; if(authenticationToken) didAuthenticate = YES;
}]; }];
[_sut authenticationViewController:nil authenticatedWithToken:@"SuperToken"]; [_sut didAuthenticateWithToken:@"SuperToken"];
assertThatBool(didAuthenticate, equalToBool(YES)); assertThatBool(didAuthenticate, equalToBool(YES));
} }