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 {
// Authentication currently being used for prompting and resuming
CFHTTPAuthenticationRef requestAuthentication;
NSMutableDictionary *requestCredentials;
int authenticationRetryCount;
NSString *authenticationMethod;
// HTTP status code, eg: 200 = OK, 404 = Not found etc
int responseStatusCode;
... ... @@ -204,9 +206,12 @@ typedef enum _ASINetworkErrorType {
#pragma mark request logic
// Start loading the request
// Main request loop is in here
- (void)loadRequest;
// Start the read stream. Called by loadRequest, and again to restart the request when authentication is needed
- (void)startRequest;
// Cancel loading and clean up
- (void)cancelLoad;
... ...
... ... @@ -61,6 +61,7 @@ static NSError *ASIUnableToCreateRequestError;
self = [super init];
[self setRequestMethod:@"GET"];
lastBytesSent = 0;
showAccurateProgress = YES;
shouldResetProgressIndicators = YES;
updatedProgress = NO;
... ... @@ -68,6 +69,8 @@ static NSError *ASIUnableToCreateRequestError;
[self setPassword:nil];
[self setUsername:nil];
[self setRequestHeaders:nil];
authenticationRetryCount = 0;
authenticationMethod = nil;
authenticationRealm = nil;
outputStream = nil;
requestAuthentication = NULL;
... ... @@ -120,6 +123,7 @@ static NSError *ASIUnableToCreateRequestError;
[responseHeaders release];
[requestMethod release];
[cancelledLock release];
[authenticationMethod release];
[super dealloc];
}
... ... @@ -291,11 +295,8 @@ static NSError *ASIUnableToCreateRequestError;
}
// Start the request
- (void)loadRequest
- (void)startRequest
{
[cancelledLock lock];
if ([self isCancelled]) {
... ... @@ -363,7 +364,14 @@ static NSError *ASIUnableToCreateRequestError;
}
[self resetUploadProgress:amount];
}
}
// Start the request
- (void)loadRequest
{
[self startRequest];
// Record when the request started, so we can timeout if nothing happens
... ... @@ -831,6 +839,7 @@ static NSError *ASIUnableToCreateRequestError;
- (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials
{
authenticationRetryCount++;
if (newCredentials && requestAuthentication && request) {
// Apply whatever credentials we've built up to the old request
... ... @@ -846,10 +855,10 @@ static NSError *ASIUnableToCreateRequestError;
[ASIHTTPRequest setSessionCredentials:newCredentials];
}
[self setRequestCredentials:newCredentials];
return TRUE;
return YES;
}
}
return FALSE;
return NO;
}
- (NSMutableDictionary *)findCredentials
... ... @@ -858,6 +867,9 @@ static NSError *ASIUnableToCreateRequestError;
// Is an account domain needed? (used currently for NTLM only)
if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
if (!domain) {
[self setDomain:@""];
}
[newCredentials setObject:domain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
}
... ... @@ -925,8 +937,10 @@ static NSError *ASIUnableToCreateRequestError;
CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader);
requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
CFRelease(responseHeader);
authenticationMethod = (NSString *)CFHTTPAuthenticationCopyMethod(requestAuthentication);
}
if (!requestAuthentication) {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]];
return;
... ... @@ -963,8 +977,14 @@ static NSError *ASIUnableToCreateRequestError;
[self cancelLoad];
if (requestCredentials) {
if ([self applyCredentials:requestCredentials]) {
[self loadRequest];
NSLog(@"%hi",authenticationRetryCount);
if (((authenticationMethod != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || authenticationRetryCount < 2) && [self applyCredentials:requestCredentials]) {
[self startRequest];
// We've failed NTLM authentication twice, we should assume our credentials are wrong
} else if (authenticationMethod == (NSString *)kCFHTTPAuthenticationSchemeNTLM && authenticationRetryCount == 2) {
[self failWithError:ASIAuthenticationError];
} else {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
}
... ... @@ -978,7 +998,7 @@ static NSError *ASIUnableToCreateRequestError;
if (newCredentials) {
if ([self applyCredentials:newCredentials]) {
[self loadRequest];
[self startRequest];
} else {
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
}
... ...
... ... @@ -23,7 +23,9 @@
- (void)testCookies;
- (void)testBasicAuthentication;
- (void)testDigestAuthentication;
- (void)testNTLMAuthentication;
- (void)testCharacterEncoding;
- (void)testCompressedResponse;
- (void)testCompressedResponseDownloadToFile;
@end
... ...
... ... @@ -439,6 +439,48 @@
GHAssertTrue(success,@"Failed to clear credentials");
}
- (void)testNTLMAuthentication
{
/*
If you want to run this test, set your hostname, username, password and domain below.
*/
NSString *theURL = @"";
NSString *username = @"";
NSString *password = @"";
NSString *domain = @"";
if ([theURL isEqualToString:@""] || [username isEqualToString:@""] || [password isEqualToString:@""]) {
GHAssertFalse(true,@"Skipping NTLM test because no server details were supplied");
}
[ASIHTTPRequest clearSession];
NSURL *url = [[[NSURL alloc] initWithString:theURL] autorelease];
ASIHTTPRequest *request;
BOOL success;
NSError *err;
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setUseKeychainPersistance:NO];
[request setUseSessionPersistance:NO];
[request start];
success = [[request error] code] == ASIAuthenticationErrorType;
GHAssertTrue(success,@"Failed to generate permission denied error with no credentials");
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setUseSessionPersistance:YES];
[request setUseKeychainPersistance:NO];
[request setUsername:username];
[request setPassword:password];
[request setDomain:domain];
[request start];
err = [request error];
GHAssertNil(err,@"Got an error when correct credentials were supplied");
NSLog([request responseString]);
}
- (void)testCompressedResponse
{
// allseeing-i.com does not gzip png images
... ...
... ... @@ -24,4 +24,5 @@
- (void)testProgressWithAuthentication;
- (void)setProgress:(float)newProgress;
@end
... ...
... ... @@ -12,6 +12,7 @@
@implementation ASINetworkQueueTests
static CFStringRef ASIHTTPRequestTestsRunMode = CFSTR("ASIHTTPRequestTestsRunMode");
- (void)testProgress
... ...