diff --git a/Classes/BITAuthenticator.h b/Classes/BITAuthenticator.h index 9c677f6378..fee1f1d7f7 100644 --- a/Classes/BITAuthenticator.h +++ b/Classes/BITAuthenticator.h @@ -75,6 +75,18 @@ typedef NS_ENUM(NSUInteger, BITAuthenticatorIdentificationType) { * for further documentation on this. */ BITAuthenticatorIdentificationTypeDevice, + /** + * Ask for the HockeyApp account email. + *

+ * This will present a user interface requesting the user to start a Safari based + * flow to login to HockeyApp (if not already logged in) and to share the hockeyapp + * account's email. + *

+ * If restrictApplicationUsage is enabled, the provided user account has to match a + * registered HockeyApp user who is a member or tester of the app. + * For identification purpose any HockeyApp user is allowed. + */ + BITAuthenticatorIdentificationTypeWebAuth, }; /** diff --git a/Classes/BITAuthenticator.m b/Classes/BITAuthenticator.m index c491490db9..6a6a48096a 100644 --- a/Classes/BITAuthenticator.m +++ b/Classes/BITAuthenticator.m @@ -180,6 +180,11 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut viewController.showsLoginViaWebButton = YES; viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerWebUDIDLoginDescription"); break; + case BITAuthenticatorIdentificationTypeWebAuth: + viewController = [[BITAuthenticationViewController alloc] initWithDelegate:self]; + viewController.requirePassword = NO; + viewController.showsLoginViaWebButton = YES; + viewController.tableViewTitle = BITHockeyLocalizedString(@"HockeyAuthenticationViewControllerWebAuthLoginDescription"); break; case BITAuthenticatorIdentificationTypeHockeyAppEmail: if(nil == self.authenticationSecret) { @@ -230,6 +235,7 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut //no break case BITAuthenticatorIdentificationTypeDevice: case BITAuthenticatorIdentificationTypeHockeyAppUser: + case BITAuthenticatorIdentificationTypeWebAuth: if(nil == self.installationIdentifier) { error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain code:BITAuthenticatorNotIdentified @@ -483,7 +489,25 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut } - (NSURL *)deviceAuthenticationURL { - return [self.webpageURL URLByAppendingPathComponent:[NSString stringWithFormat:@"apps/%@/authorize", self.encodedAppIdentifier]]; + NSString *whatParameter = nil; + switch (self.identificationType) { + case BITAuthenticatorIdentificationTypeWebAuth: + whatParameter = @"email"; + break; + case BITAuthenticatorIdentificationTypeDevice: + whatParameter = @"udid"; + break; + case BITAuthenticatorIdentificationTypeAnonymous: + case BITAuthenticatorIdentificationTypeHockeyAppEmail: + case BITAuthenticatorIdentificationTypeHockeyAppUser: + NSAssert(NO,@"Should not happen. Those identification types don't need an authentication URL"); + return nil; + break; + } + NSURL *url = [self.webpageURL URLByAppendingPathComponent:[NSString stringWithFormat:@"apps/%@/authorize", self.encodedAppIdentifier]]; + NSParameterAssert(whatParameter && url.absoluteString); + url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?what=%@", url.absoluteString, whatParameter]]; + return url; } - (void)authenticationViewControllerDidTapWebButton:(UIViewController *)viewController { @@ -496,19 +520,46 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut - (BOOL) handleOpenURL:(NSURL *) url sourceApplication:(NSString *) sourceApplication annotation:(id) annotation { - BOOL isValidURL = NO; - NSString *udid = [self UDIDFromOpenURL:url annotation:annotation isValidURL:&isValidURL]; - if(NO == isValidURL) { - //do nothing, was not for us + //check if this URL was meant for us, if not return NO so the user can + //handle it + NSString *const kAuthorizationHost = @"authorize"; + NSString *urlScheme = _urlScheme ? : [NSString stringWithFormat:@"ha%@", self.appIdentifier]; + if(!([[url scheme] isEqualToString:urlScheme] && [[url host] isEqualToString:kAuthorizationHost])) { return NO; } + + NSString *installationIdentifier = nil; + NSString *localizedErrorDescription = nil; + switch (self.identificationType) { + case BITAuthenticatorIdentificationTypeWebAuth: { + NSString *email = nil; + [self.class email:&email andIUID:&installationIdentifier fromOpenURL:url]; + if(email) { + [self addStringValueToKeychain:email forKey:kBITAuthenticatorUserEmailKey]; + } else { + BITHockeyLog(@"No email found in URL: %@", url); + } + localizedErrorDescription = @"Failed to retrieve parameters from URL."; + break; + } + case BITAuthenticatorIdentificationTypeDevice: { + installationIdentifier = [self.class UDIDFromOpenURL:url annotation:annotation]; + localizedErrorDescription = @"Failed to retrieve UDID from URL."; + break; + } + case BITAuthenticatorIdentificationTypeHockeyAppEmail: + case BITAuthenticatorIdentificationTypeAnonymous: + case BITAuthenticatorIdentificationTypeHockeyAppUser: + NSAssert(NO, @"Should only be called for Device and WebAuth identificationType"); + return NO; + } - if(udid){ + if(installationIdentifier){ if(NO == self.restrictApplicationUsage) { [_authenticationController dismissViewControllerAnimated:YES completion:nil]; _authenticationController = nil; } - [self storeInstallationIdentifier:udid withType:BITAuthenticatorIdentificationTypeDevice]; + [self storeInstallationIdentifier:installationIdentifier withType:self.identificationType]; self.identified = YES; if(self.identificationCompletion) { self.identificationCompletion(YES, nil); @@ -516,12 +567,12 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut } } else { //reset token - [self storeInstallationIdentifier:nil withType:BITAuthenticatorIdentificationTypeDevice]; + [self storeInstallationIdentifier:nil withType:self.identificationType]; self.identified = NO; if(self.identificationCompletion) { NSError *error = [NSError errorWithDomain:kBITAuthenticatorErrorDomain code:BITAuthenticatorErrorUnknown - userInfo:@{NSLocalizedDescriptionKey : @"Failed to retrieve UDID from URL"}]; + userInfo:@{NSLocalizedDescriptionKey : localizedErrorDescription}]; self.identificationCompletion(NO, error); self.identificationCompletion = nil; } @@ -529,30 +580,33 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut return YES; } -- (NSString *) UDIDFromOpenURL:(NSURL *) url annotation:(id) annotation isValidURL:(BOOL*) isValid{ - NSString *const kAuthorizationHost = @"authorize"; - NSString *urlScheme = _urlScheme ? : [NSString stringWithFormat:@"ha%@", self.appIdentifier]; - if([[url scheme] isEqualToString:urlScheme] && [[url host] isEqualToString:kAuthorizationHost]) { - if(isValid) { - *isValid = YES; ++ (NSString *) UDIDFromOpenURL:(NSURL *) url annotation:(id) annotation { + NSString *query = [url query]; + NSString *udid = nil; + //there should actually only one + static NSString * const UDIDQuerySpecifier = @"udid"; + for(NSString *queryComponents in [query componentsSeparatedByString:@"&"]) { + NSArray *parameterComponents = [queryComponents componentsSeparatedByString:@"="]; + if(2 == parameterComponents.count && [parameterComponents[0] isEqualToString:UDIDQuerySpecifier]) { + udid = parameterComponents[1]; + break; } - NSString *query = [url query]; - NSString *udid = nil; - //there should actually only one - static NSString * const UDIDQuerySpecifier = @"udid"; - for(NSString *queryComponents in [query componentsSeparatedByString:@"&"]) { - NSArray *parameterComponents = [queryComponents componentsSeparatedByString:@"="]; - if(2 == parameterComponents.count && [parameterComponents[0] isEqualToString:UDIDQuerySpecifier]) { - udid = parameterComponents[1]; - break; - } + } + return udid; +} + ++ (void) email:(NSString**) email andIUID:(NSString**) iuid fromOpenURL:(NSURL *) url { + NSString *query = [url query]; + //there should actually only one + static NSString * const EmailQuerySpecifier = @"email"; + static NSString * const IUIDQuerySpecifier = @"iuid"; + for(NSString *queryComponents in [query componentsSeparatedByString:@"&"]) { + NSArray *parameterComponents = [queryComponents componentsSeparatedByString:@"="]; + if(email && 2 == parameterComponents.count && [parameterComponents[0] isEqualToString:EmailQuerySpecifier]) { + *email = parameterComponents[1]; + } else if(iuid && 2 == parameterComponents.count && [parameterComponents[0] isEqualToString:IUIDQuerySpecifier]) { + *iuid = parameterComponents[1]; } - return udid; - } else { - if(isValid) { - *isValid = NO; - } - return nil; } } @@ -642,6 +696,7 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut - (NSString *)installationIdentifierParameterString { switch(self.identificationType) { case BITAuthenticatorIdentificationTypeHockeyAppEmail: + case BITAuthenticatorIdentificationTypeWebAuth: return @"iuid"; case BITAuthenticatorIdentificationTypeHockeyAppUser: return @"auid"; case BITAuthenticatorIdentificationTypeDevice: return @"udid"; @@ -652,6 +707,7 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut + (NSString *)stringForIdentificationType:(BITAuthenticatorIdentificationType) identificationType { switch(identificationType) { case BITAuthenticatorIdentificationTypeHockeyAppEmail: return @"iuid"; + case BITAuthenticatorIdentificationTypeWebAuth: return @"webAuth"; case BITAuthenticatorIdentificationTypeHockeyAppUser: return @"auid"; case BITAuthenticatorIdentificationTypeDevice: return @"udid"; case BITAuthenticatorIdentificationTypeAnonymous: return @"uuid"; @@ -670,6 +726,7 @@ static NSString* const kBITAuthenticatorAuthTokenTypeKey = @"BITAuthenticatorAut switch (self.identificationType) { case BITAuthenticatorIdentificationTypeHockeyAppEmail: case BITAuthenticatorIdentificationTypeHockeyAppUser: + case BITAuthenticatorIdentificationTypeWebAuth: return [self stringValueFromKeychainForKey:kBITAuthenticatorUserEmailKey]; case BITAuthenticatorIdentificationTypeAnonymous: case BITAuthenticatorIdentificationTypeDevice: diff --git a/Support/HockeySDKTests/BITAuthenticatorTests.m b/Support/HockeySDKTests/BITAuthenticatorTests.m index e2ea3441b3..af68b0c490 100644 --- a/Support/HockeySDKTests/BITAuthenticatorTests.m +++ b/Support/HockeySDKTests/BITAuthenticatorTests.m @@ -189,6 +189,16 @@ static void *kInstallationIdentification = &kInstallationIdentification; [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; } +#pragma mark - Web auth identification type +- (void) testWebAuthIdentificationShowsViewController { + _sut.identificationType = BITAuthenticatorIdentificationTypeWebAuth; + id delegateMock = mockProtocol(@protocol(BITAuthenticatorDelegate)); + _sut.delegate = delegateMock; + + [_sut identifyWithCompletion:nil]; + + [verifyCount(delegateMock, times(1)) authenticator:_sut willShowAuthenticationController:(id)anything()]; +} #pragma mark - Email identification type - (void) testEmailIdentificationFailsWithMissingSecret {