Ben Copsey

Restructure authentication retries to make NTLM work

Moved stream setup into startRequest to help with above
@@ -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
@@ -24,4 +24,5 @@ @@ -24,4 +24,5 @@
24 - (void)testProgressWithAuthentication; 24 - (void)testProgressWithAuthentication;
25 25
26 - (void)setProgress:(float)newProgress; 26 - (void)setProgress:(float)newProgress;
  27 +
27 @end 28 @end
@@ -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