diff --git a/Classes/BITAuthenticationViewController.h b/Classes/BITAuthenticationViewController.h index 465cb82235..5464418cd9 100644 --- a/Classes/BITAuthenticationViewController.h +++ b/Classes/BITAuthenticationViewController.h @@ -13,19 +13,7 @@ @interface BITAuthenticationViewController : UITableViewController -- (instancetype) initWithApplicationIdentifier:(NSString*) encodedApplicationIdentifier - requirePassword:(BOOL) requiresPassword - delegate:(id) 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) 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 delegate; @@ -51,7 +34,27 @@ @protocol BITAuthenticationViewControllerDelegate +/** + * 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 diff --git a/Classes/BITAuthenticationViewController.m b/Classes/BITAuthenticationViewController.m index aeb266c94c..256852274e 100644 --- a/Classes/BITAuthenticationViewController.m +++ b/Classes/BITAuthenticationViewController.m @@ -23,14 +23,10 @@ @implementation BITAuthenticationViewController -- (instancetype) initWithApplicationIdentifier:(NSString*) encodedApplicationIdentifier - requirePassword:(BOOL) requiresPassword - delegate:(id) delegate { +- (instancetype) initWithDelegate:(id)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 diff --git a/Classes/BITAuthenticator.m b/Classes/BITAuthenticator.m index c4720e48c7..a96f82e6d7 100644 --- a/Classes/BITAuthenticator.m +++ b/Classes/BITAuthenticator.m @@ -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; } } diff --git a/Classes/BITAuthenticator_Private.h b/Classes/BITAuthenticator_Private.h index 20a15be7c6..af97b83d1a 100644 --- a/Classes/BITAuthenticator_Private.h +++ b/Classes/BITAuthenticator_Private.h @@ -62,6 +62,9 @@ */ - (void) authenticateWithCompletion:(tAuthenticationCompletion) completion; +#pragma mark - Internal Auth callbacks +- (void) didAuthenticateWithToken:(NSString*) token; + #pragma mark - Validation /** * Validate the app installation diff --git a/Support/HockeySDKTests/BITAuthenticatorTests.m b/Support/HockeySDKTests/BITAuthenticatorTests.m index 6614789ae1..71a5320447 100644 --- a/Support/HockeySDKTests/BITAuthenticatorTests.m +++ b/Support/HockeySDKTests/BITAuthenticatorTests.m @@ -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)); }