Restructure authentication retries to make NTLM work
Moved stream setup into startRequest to help with above
Showing
6 changed files
with
85 additions
and
14 deletions
| @@ -113,6 +113,8 @@ typedef enum _ASINetworkErrorType { | @@ -113,6 +113,8 @@ typedef enum _ASINetworkErrorType { | ||
| 113 | // Authentication currently being used for prompting and resuming | 113 | // Authentication currently being used for prompting and resuming |
| 114 | CFHTTPAuthenticationRef requestAuthentication; | 114 | CFHTTPAuthenticationRef requestAuthentication; |
| 115 | NSMutableDictionary *requestCredentials; | 115 | NSMutableDictionary *requestCredentials; |
| 116 | + int authenticationRetryCount; | ||
| 117 | + NSString *authenticationMethod; | ||
| 116 | 118 | ||
| 117 | // HTTP status code, eg: 200 = OK, 404 = Not found etc | 119 | // HTTP status code, eg: 200 = OK, 404 = Not found etc |
| 118 | int responseStatusCode; | 120 | int responseStatusCode; |
| @@ -204,9 +206,12 @@ typedef enum _ASINetworkErrorType { | @@ -204,9 +206,12 @@ typedef enum _ASINetworkErrorType { | ||
| 204 | 206 | ||
| 205 | #pragma mark request logic | 207 | #pragma mark request logic |
| 206 | 208 | ||
| 207 | -// Start loading the request | 209 | +// Main request loop is in here |
| 208 | - (void)loadRequest; | 210 | - (void)loadRequest; |
| 209 | 211 | ||
| 212 | +// Start the read stream. Called by loadRequest, and again to restart the request when authentication is needed | ||
| 213 | +- (void)startRequest; | ||
| 214 | + | ||
| 210 | // Cancel loading and clean up | 215 | // Cancel loading and clean up |
| 211 | - (void)cancelLoad; | 216 | - (void)cancelLoad; |
| 212 | 217 |
| @@ -61,6 +61,7 @@ static NSError *ASIUnableToCreateRequestError; | @@ -61,6 +61,7 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 61 | self = [super init]; | 61 | self = [super init]; |
| 62 | [self setRequestMethod:@"GET"]; | 62 | [self setRequestMethod:@"GET"]; |
| 63 | lastBytesSent = 0; | 63 | lastBytesSent = 0; |
| 64 | + | ||
| 64 | showAccurateProgress = YES; | 65 | showAccurateProgress = YES; |
| 65 | shouldResetProgressIndicators = YES; | 66 | shouldResetProgressIndicators = YES; |
| 66 | updatedProgress = NO; | 67 | updatedProgress = NO; |
| @@ -68,6 +69,8 @@ static NSError *ASIUnableToCreateRequestError; | @@ -68,6 +69,8 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 68 | [self setPassword:nil]; | 69 | [self setPassword:nil]; |
| 69 | [self setUsername:nil]; | 70 | [self setUsername:nil]; |
| 70 | [self setRequestHeaders:nil]; | 71 | [self setRequestHeaders:nil]; |
| 72 | + authenticationRetryCount = 0; | ||
| 73 | + authenticationMethod = nil; | ||
| 71 | authenticationRealm = nil; | 74 | authenticationRealm = nil; |
| 72 | outputStream = nil; | 75 | outputStream = nil; |
| 73 | requestAuthentication = NULL; | 76 | requestAuthentication = NULL; |
| @@ -120,6 +123,7 @@ static NSError *ASIUnableToCreateRequestError; | @@ -120,6 +123,7 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 120 | [responseHeaders release]; | 123 | [responseHeaders release]; |
| 121 | [requestMethod release]; | 124 | [requestMethod release]; |
| 122 | [cancelledLock release]; | 125 | [cancelledLock release]; |
| 126 | + [authenticationMethod release]; | ||
| 123 | [super dealloc]; | 127 | [super dealloc]; |
| 124 | } | 128 | } |
| 125 | 129 | ||
| @@ -291,11 +295,8 @@ static NSError *ASIUnableToCreateRequestError; | @@ -291,11 +295,8 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 291 | 295 | ||
| 292 | } | 296 | } |
| 293 | 297 | ||
| 294 | - | 298 | +- (void)startRequest |
| 295 | -// Start the request | ||
| 296 | -- (void)loadRequest | ||
| 297 | { | 299 | { |
| 298 | - | ||
| 299 | [cancelledLock lock]; | 300 | [cancelledLock lock]; |
| 300 | 301 | ||
| 301 | if ([self isCancelled]) { | 302 | if ([self isCancelled]) { |
| @@ -362,8 +363,15 @@ static NSError *ASIUnableToCreateRequestError; | @@ -362,8 +363,15 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 362 | amount = postLength; | 363 | amount = postLength; |
| 363 | } | 364 | } |
| 364 | [self resetUploadProgress:amount]; | 365 | [self resetUploadProgress:amount]; |
| 365 | - } | 366 | + } |
| 366 | - | 367 | +} |
| 368 | + | ||
| 369 | +// Start the request | ||
| 370 | +- (void)loadRequest | ||
| 371 | +{ | ||
| 372 | + | ||
| 373 | + | ||
| 374 | + [self startRequest]; | ||
| 367 | 375 | ||
| 368 | 376 | ||
| 369 | // Record when the request started, so we can timeout if nothing happens | 377 | // Record when the request started, so we can timeout if nothing happens |
| @@ -831,7 +839,8 @@ static NSError *ASIUnableToCreateRequestError; | @@ -831,7 +839,8 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 831 | 839 | ||
| 832 | - (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials | 840 | - (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials |
| 833 | { | 841 | { |
| 834 | - | 842 | + authenticationRetryCount++; |
| 843 | + | ||
| 835 | if (newCredentials && requestAuthentication && request) { | 844 | if (newCredentials && requestAuthentication && request) { |
| 836 | // Apply whatever credentials we've built up to the old request | 845 | // Apply whatever credentials we've built up to the old request |
| 837 | if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) { | 846 | if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) { |
| @@ -846,10 +855,10 @@ static NSError *ASIUnableToCreateRequestError; | @@ -846,10 +855,10 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 846 | [ASIHTTPRequest setSessionCredentials:newCredentials]; | 855 | [ASIHTTPRequest setSessionCredentials:newCredentials]; |
| 847 | } | 856 | } |
| 848 | [self setRequestCredentials:newCredentials]; | 857 | [self setRequestCredentials:newCredentials]; |
| 849 | - return TRUE; | 858 | + return YES; |
| 850 | } | 859 | } |
| 851 | } | 860 | } |
| 852 | - return FALSE; | 861 | + return NO; |
| 853 | } | 862 | } |
| 854 | 863 | ||
| 855 | - (NSMutableDictionary *)findCredentials | 864 | - (NSMutableDictionary *)findCredentials |
| @@ -858,6 +867,9 @@ static NSError *ASIUnableToCreateRequestError; | @@ -858,6 +867,9 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 858 | 867 | ||
| 859 | // Is an account domain needed? (used currently for NTLM only) | 868 | // Is an account domain needed? (used currently for NTLM only) |
| 860 | if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { | 869 | if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) { |
| 870 | + if (!domain) { | ||
| 871 | + [self setDomain:@""]; | ||
| 872 | + } | ||
| 861 | [newCredentials setObject:domain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; | 873 | [newCredentials setObject:domain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; |
| 862 | } | 874 | } |
| 863 | 875 | ||
| @@ -925,7 +937,9 @@ static NSError *ASIUnableToCreateRequestError; | @@ -925,7 +937,9 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 925 | CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader); | 937 | CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader); |
| 926 | requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); | 938 | requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); |
| 927 | CFRelease(responseHeader); | 939 | CFRelease(responseHeader); |
| 928 | - } | 940 | + authenticationMethod = (NSString *)CFHTTPAuthenticationCopyMethod(requestAuthentication); |
| 941 | + } | ||
| 942 | + | ||
| 929 | 943 | ||
| 930 | if (!requestAuthentication) { | 944 | if (!requestAuthentication) { |
| 931 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]]; | 945 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]]; |
| @@ -963,8 +977,14 @@ static NSError *ASIUnableToCreateRequestError; | @@ -963,8 +977,14 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 963 | [self cancelLoad]; | 977 | [self cancelLoad]; |
| 964 | 978 | ||
| 965 | if (requestCredentials) { | 979 | if (requestCredentials) { |
| 966 | - if ([self applyCredentials:requestCredentials]) { | 980 | + NSLog(@"%hi",authenticationRetryCount); |
| 967 | - [self loadRequest]; | 981 | + if (((authenticationMethod != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || authenticationRetryCount < 2) && [self applyCredentials:requestCredentials]) { |
| 982 | + [self startRequest]; | ||
| 983 | + | ||
| 984 | + // We've failed NTLM authentication twice, we should assume our credentials are wrong | ||
| 985 | + } else if (authenticationMethod == (NSString *)kCFHTTPAuthenticationSchemeNTLM && authenticationRetryCount == 2) { | ||
| 986 | + [self failWithError:ASIAuthenticationError]; | ||
| 987 | + | ||
| 968 | } else { | 988 | } else { |
| 969 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; | 989 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; |
| 970 | } | 990 | } |
| @@ -978,7 +998,7 @@ static NSError *ASIUnableToCreateRequestError; | @@ -978,7 +998,7 @@ static NSError *ASIUnableToCreateRequestError; | ||
| 978 | if (newCredentials) { | 998 | if (newCredentials) { |
| 979 | 999 | ||
| 980 | if ([self applyCredentials:newCredentials]) { | 1000 | if ([self applyCredentials:newCredentials]) { |
| 981 | - [self loadRequest]; | 1001 | + [self startRequest]; |
| 982 | } else { | 1002 | } else { |
| 983 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; | 1003 | [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]]; |
| 984 | } | 1004 | } |
| @@ -23,7 +23,9 @@ | @@ -23,7 +23,9 @@ | ||
| 23 | - (void)testCookies; | 23 | - (void)testCookies; |
| 24 | - (void)testBasicAuthentication; | 24 | - (void)testBasicAuthentication; |
| 25 | - (void)testDigestAuthentication; | 25 | - (void)testDigestAuthentication; |
| 26 | +- (void)testNTLMAuthentication; | ||
| 26 | - (void)testCharacterEncoding; | 27 | - (void)testCharacterEncoding; |
| 27 | - (void)testCompressedResponse; | 28 | - (void)testCompressedResponse; |
| 28 | - (void)testCompressedResponseDownloadToFile; | 29 | - (void)testCompressedResponseDownloadToFile; |
| 30 | + | ||
| 29 | @end | 31 | @end |
| @@ -439,6 +439,48 @@ | @@ -439,6 +439,48 @@ | ||
| 439 | GHAssertTrue(success,@"Failed to clear credentials"); | 439 | GHAssertTrue(success,@"Failed to clear credentials"); |
| 440 | } | 440 | } |
| 441 | 441 | ||
| 442 | + | ||
| 443 | +- (void)testNTLMAuthentication | ||
| 444 | +{ | ||
| 445 | + /* | ||
| 446 | + If you want to run this test, set your hostname, username, password and domain below. | ||
| 447 | + */ | ||
| 448 | + NSString *theURL = @""; | ||
| 449 | + NSString *username = @""; | ||
| 450 | + NSString *password = @""; | ||
| 451 | + NSString *domain = @""; | ||
| 452 | + | ||
| 453 | + if ([theURL isEqualToString:@""] || [username isEqualToString:@""] || [password isEqualToString:@""]) { | ||
| 454 | + GHAssertFalse(true,@"Skipping NTLM test because no server details were supplied"); | ||
| 455 | + } | ||
| 456 | + | ||
| 457 | + [ASIHTTPRequest clearSession]; | ||
| 458 | + | ||
| 459 | + NSURL *url = [[[NSURL alloc] initWithString:theURL] autorelease]; | ||
| 460 | + ASIHTTPRequest *request; | ||
| 461 | + BOOL success; | ||
| 462 | + NSError *err; | ||
| 463 | + | ||
| 464 | + request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; | ||
| 465 | + [request setUseKeychainPersistance:NO]; | ||
| 466 | + [request setUseSessionPersistance:NO]; | ||
| 467 | + [request start]; | ||
| 468 | + success = [[request error] code] == ASIAuthenticationErrorType; | ||
| 469 | + GHAssertTrue(success,@"Failed to generate permission denied error with no credentials"); | ||
| 470 | + | ||
| 471 | + | ||
| 472 | + request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; | ||
| 473 | + [request setUseSessionPersistance:YES]; | ||
| 474 | + [request setUseKeychainPersistance:NO]; | ||
| 475 | + [request setUsername:username]; | ||
| 476 | + [request setPassword:password]; | ||
| 477 | + [request setDomain:domain]; | ||
| 478 | + [request start]; | ||
| 479 | + err = [request error]; | ||
| 480 | + GHAssertNil(err,@"Got an error when correct credentials were supplied"); | ||
| 481 | + NSLog([request responseString]); | ||
| 482 | +} | ||
| 483 | + | ||
| 442 | - (void)testCompressedResponse | 484 | - (void)testCompressedResponse |
| 443 | { | 485 | { |
| 444 | // allseeing-i.com does not gzip png images | 486 | // allseeing-i.com does not gzip png images |
| @@ -12,6 +12,7 @@ | @@ -12,6 +12,7 @@ | ||
| 12 | 12 | ||
| 13 | @implementation ASINetworkQueueTests | 13 | @implementation ASINetworkQueueTests |
| 14 | 14 | ||
| 15 | + | ||
| 15 | static CFStringRef ASIHTTPRequestTestsRunMode = CFSTR("ASIHTTPRequestTestsRunMode"); | 16 | static CFStringRef ASIHTTPRequestTestsRunMode = CFSTR("ASIHTTPRequestTestsRunMode"); |
| 16 | 17 | ||
| 17 | - (void)testProgress | 18 | - (void)testProgress |
-
Please register or login to post a comment