Ben Copsey

Cached credentials will not be used if different ones are set on the request

Tweak to finding NTLM proxy credentials
... ... @@ -940,23 +940,22 @@ static NSOperationQueue *sharedQueue = nil;
return;
}
// First, see if we have any credentials we can use in the session store
NSDictionary *credentials = nil;
if ([self useSessionPersistence]) {
credentials = [self findSessionAuthenticationCredentials];
}
// Are any credentials set on this request that might be used for basic authentication?
if ([self username] && [self password] && ![self domain]) {
// Do we already have an auth header?
if (![[self requestHeaders] objectForKey:@"Authorization"]) {
// If we know this request should use Basic auth, we'll add an Authorization header with basic credentials
if ([[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {
// If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header
if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {
[self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]];
}
}
if (credentials && ![[self requestHeaders] objectForKey:@"Authorization"]) {
} else {
// See if we have any cached credentials we can use in the session store
if ([self useSessionPersistence]) {
credentials = [self findSessionAuthenticationCredentials];
if (credentials) {
// When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them
// (credentials for Digest and NTLM will always be stored like this)
... ... @@ -974,6 +973,11 @@ static NSOperationQueue *sharedQueue = nil;
[self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
}
}
}
}
}
// Apply proxy authentication credentials
if ([self useSessionPersistence]) {
credentials = [self findSessionProxyAuthenticationCredentials];
if (credentials) {
... ... @@ -2368,11 +2372,11 @@ static NSOperationQueue *sharedQueue = nil;
NSString *user = nil;
NSString *pass = nil;
ASIHTTPRequest *theRequest = [self mainRequest];
// If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
if ([self mainRequest] && [[self mainRequest] proxyUsername] && [[self mainRequest] proxyPassword]) {
user = [[self mainRequest] proxyUsername];
pass = [[self mainRequest] proxyPassword];
if ([theRequest proxyUsername] && [theRequest proxyPassword]) {
user = [theRequest proxyUsername];
pass = [theRequest proxyPassword];
// Let's try to use the ones set in this object
} else if ([self proxyUsername] && [self proxyPassword]) {
... ... @@ -2380,6 +2384,13 @@ static NSOperationQueue *sharedQueue = nil;
pass = [self proxyPassword];
}
// When we connect to a website using NTLM via a proxy, we will use the main credentials
if ((!user || !pass) && [self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM) {
user = [self username];
pass = [self password];
}
// Ok, that didn't work, let's try the keychain
// For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistence
... ... @@ -2397,13 +2408,21 @@ static NSOperationQueue *sharedQueue = nil;
NSString *ntlmDomain = [self proxyDomain];
// If we have no domain yet, let's try to extract it from the username
// If we have no domain yet
if (!ntlmDomain || [ntlmDomain length] == 0) {
ntlmDomain = @"";
// Let's try to extract it from the username
NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"];
if ([ntlmComponents count] == 2) {
ntlmDomain = [ntlmComponents objectAtIndex:0];
user = [ntlmComponents objectAtIndex:1];
// If we are connecting to a website using NTLM, but we are connecting via a proxy, the string we need may be in the domain property
} else {
ntlmDomain = [self domain];
}
if (!ntlmDomain) {
ntlmDomain = @"";
}
}
[newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
... ... @@ -2789,6 +2808,7 @@ static NSOperationQueue *sharedQueue = nil;
return;
}
// Do we actually need to authenticate with a proxy?
if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) {
[self attemptToApplyProxyCredentialsAndResume];
return;
... ... @@ -3971,31 +3991,59 @@ static NSOperationQueue *sharedQueue = nil;
{
[sessionCredentialsLock lock];
NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
// Find an exact match (same url)
NSURL *requestURL = [self url];
BOOL haveFoundExactMatch;
NSDictionary *closeMatch = nil;
// Loop through all the cached credentials we have, looking for the best match for this request
for (NSDictionary *theCredentials in sessionCredentialsList) {
if ([(NSURL*)[theCredentials objectForKey:@"URL"] isEqual:[self url]]) {
// /Just a sanity check to ensure we never choose credentials from a different realm. Can't really do more than that, as either this request or the stored credentials may not have a realm when the other does
if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {
[sessionCredentialsLock unlock];
return theCredentials;
haveFoundExactMatch = NO;
NSURL *cachedCredentialsURL = [theCredentials objectForKey:@"URL"];
// Find an exact match (same url)
if ([cachedCredentialsURL isEqual:[self url]]) {
haveFoundExactMatch = YES;
// This is not an exact match for the url, and we already have a close match we can use
} else if (closeMatch) {
continue;
// Find a close match (same host, scheme and port)
} else if ([[cachedCredentialsURL host] isEqualToString:[requestURL host]] && ([cachedCredentialsURL port] == [requestURL port] || ([requestURL port] && [[cachedCredentialsURL port] isEqualToNumber:[requestURL port]])) && [[cachedCredentialsURL scheme] isEqualToString:[requestURL scheme]]) {
} else {
continue;
}
// Just a sanity check to ensure we never choose credentials from a different realm. Can't really do more than that, as either this request or the stored credentials may not have a realm when the other does
if ([self authenticationRealm] && ([theCredentials objectForKey:@"AuthenticationRealm"] && ![[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {
continue;
}
// If we have a username and password set on the request, check that they are the same as the cached ones
if ([self username] && [self password]) {
NSDictionary *usernameAndPassword = [theCredentials objectForKey:@"Credentials"];
NSString *storedUsername = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername];
NSString *storedPassword = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername];
if (![storedUsername isEqualToString:[self username]] || ![storedPassword isEqualToString:[self password]]) {
continue;
}
}
// Find a rough match (same host, port, scheme)
NSURL *requestURL = [self url];
for (NSDictionary *theCredentials in sessionCredentialsList) {
NSURL *theURL = [theCredentials objectForKey:@"URL"];
// Port can be nil!
if ([[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]]) {
if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {
// If we have an exact match for the url, use those credentials
if (haveFoundExactMatch) {
[sessionCredentialsLock unlock];
return theCredentials;
}
}
// We have no exact match, let's remember that we have a good match for this server, and we'll use it at the end if we don't find an exact match
closeMatch = theCredentials;
}
[sessionCredentialsLock unlock];
return nil;
// Return credentials that matched on host, port and scheme, or nil if we didn't find any
return closeMatch;
}
#pragma mark keychain storage
... ...
... ... @@ -1036,10 +1036,10 @@
- (void)testBasicAuthentication
{
[ASIHTTPRequest removeCredentialsForHost:@"allseeing-i.com" port:0 protocol:@"http" realm:@"SECRET_STUFF"];
[ASIHTTPRequest removeCredentialsForHost:@"asi" port:0 protocol:@"http" realm:@"SECRET_STUFF"];
[ASIHTTPRequest clearSession];
NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/basic-authentication"];
NSURL *url = [NSURL URLWithString:@"http://asi/ASIHTTPRequest/tests/basic-authentication"];
ASIHTTPRequest *request;
BOOL success;
NSError *err;
... ... @@ -1087,6 +1087,16 @@
err = [request error];
GHAssertNil(err,@"Failed to reuse credentials");
// Ensure new credentials are used in place of those in the session
request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://asi/ASIHTTPRequest/tests/basic-authentication-new-credentials"]] autorelease];
[request setUsername:@"secret_username_2"];
[request setPassword:@"secret_password_2"];
[request setUseSessionPersistence:YES];
[request setUseKeychainPersistence:NO];
[request startSynchronous];
err = [request error];
GHAssertNil(err,@"Failed to reuse credentials");
[ASIHTTPRequest clearSession];
// Ensure credentials stored in the session were wiped
... ... @@ -1103,7 +1113,7 @@
err = [request error];
GHAssertNil(err,@"Failed to use stored credentials");
[ASIHTTPRequest removeCredentialsForHost:@"allseeing-i.com" port:0 protocol:@"http" realm:@"SECRET_STUFF"];
[ASIHTTPRequest removeCredentialsForHost:@"asi" port:0 protocol:@"http" realm:@"SECRET_STUFF"];
// Ensure credentials stored in the keychain were wiped
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
... ...