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
- (instancetype) initWithApplicationIdentifier:(NSString*) encodedApplicationIdentifier
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;
- (instancetype) initWithDelegate:(id<BITAuthenticationViewControllerDelegate>) delegate;
/**
* can be set to YES to also require the users password
@ -39,11 +27,6 @@
* defaults to YES
*/
@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;
@ -51,7 +34,27 @@
@protocol BITAuthenticationViewControllerDelegate<NSObject>
/**
* called then the user cancelled
*
* @param viewController the delegating 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

View File

@ -23,14 +23,10 @@
@implementation BITAuthenticationViewController
- (instancetype) initWithApplicationIdentifier:(NSString*) encodedApplicationIdentifier
requirePassword:(BOOL) requiresPassword
delegate:(id<BITAuthenticationViewControllerDelegate>) delegate {
- (instancetype) initWithDelegate:(id<BITAuthenticationViewControllerDelegate>)delegate {
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
self.title = BITHockeyLocalizedString(@"HockeyAuthenticatorViewControllerTitle");
_encodedApplicationIdentifier = [encodedApplicationIdentifier copy];
_requirePassword = requiresPassword;
_delegate = delegate;
_showsCancelButton = YES;
}
@ -222,71 +218,25 @@
- (void)saveAction:(id)sender {
[self showLoginUI:YES];
NSParameterAssert(self.encodedApplicationIdentifier);
NSString *authenticationPath = [self authenticationPath];
NSDictionary *params = [self parametersForAuthentication];
__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) {
//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];
}
__weak typeof(self) weakSelf = self;
[self.delegate authenticationViewController:self
handleAuthenticationWithEmail:self.email
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;
[strongSelf showLoginUI:NO];
}
}];
}
- (void) showLoginUI:(BOOL) enableLoginUI {
@ -294,48 +244,4 @@
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

View File

@ -82,6 +82,7 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
}
}
#pragma mark - Validation
- (void) validateInstallationWithCompletion:(tValidationCompletion) completion {
if(nil == self.authenticationToken) {
[self authenticateWithCompletion:^(NSString *authenticationToken, NSError *error) {
@ -189,6 +190,8 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
}
}
#pragma mark - Authentication
- (void)authenticateWithCompletion:(tAuthenticationCompletion)completion {
if(_authenticationController) {
BITHockeyLog(@"Already authenticating. Ignoring request");
@ -205,10 +208,8 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
break;
}
BITAuthenticationViewController *viewController = [[BITAuthenticationViewController alloc] initWithApplicationIdentifier:self.encodedAppIdentifier
requirePassword:requiresPassword
delegate:self];
viewController.authenticator = self;
BITAuthenticationViewController *viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self];
viewController.requirePassword = requiresPassword;
switch (self.validationType) {
case BITAuthenticatorValidationTypeNever:
case BITAuthenticatorValidationTypeOptional:
@ -232,6 +233,16 @@ static NSString* const kBITAuthenticatorLastAuthenticatedVersionKey = @"BITAuthe
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
- (void) authenticationViewControllerDidCancel:(UIViewController*) viewController {
[viewController dismissModalViewControllerAnimated:YES];
@ -247,15 +258,120 @@ 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;
- (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));
NSString *authenticationPath = [self authenticationPath];
NSDictionary *params = [self parametersForAuthenticationEmail:email password:password];
__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;
#pragma mark - Internal Auth callbacks
- (void) didAuthenticateWithToken:(NSString*) token;
#pragma mark - Validation
/**
* Validate the app installation

View File

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