Ben Copsey

Merge branch 'auth-tweaks'

@@ -962,23 +962,22 @@ static NSOperationQueue *sharedQueue = nil; @@ -962,23 +962,22 @@ static NSOperationQueue *sharedQueue = nil;
962 return; 962 return;
963 } 963 }
964 964
965 - // First, see if we have any credentials we can use in the session store  
966 NSDictionary *credentials = nil; 965 NSDictionary *credentials = nil;
967 - if ([self useSessionPersistence]) {  
968 - credentials = [self findSessionAuthenticationCredentials];  
969 - }  
970 -  
971 966
972 - // Are any credentials set on this request that might be used for basic authentication? 967 + // Do we already have an auth header?
973 - if ([self username] && [self password] && ![self domain]) { 968 + if (![[self requestHeaders] objectForKey:@"Authorization"]) {
974 969
975 - // If we know this request should use Basic auth, we'll add an Authorization header with basic credentials 970 + // If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header
976 - if ([[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) { 971 + if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {
977 [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]]; 972 [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]];
978 - }  
979 - }  
980 973
981 - if (credentials && ![[self requestHeaders] objectForKey:@"Authorization"]) { 974 + } else {
  975 +
  976 + // See if we have any cached credentials we can use in the session store
  977 + if ([self useSessionPersistence]) {
  978 + credentials = [self findSessionAuthenticationCredentials];
  979 +
  980 + if (credentials) {
982 981
983 // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them 982 // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them
984 // (credentials for Digest and NTLM will always be stored like this) 983 // (credentials for Digest and NTLM will always be stored like this)
@@ -996,6 +995,11 @@ static NSOperationQueue *sharedQueue = nil; @@ -996,6 +995,11 @@ static NSOperationQueue *sharedQueue = nil;
996 [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]]; 995 [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
997 } 996 }
998 } 997 }
  998 + }
  999 + }
  1000 + }
  1001 +
  1002 + // Apply proxy authentication credentials
999 if ([self useSessionPersistence]) { 1003 if ([self useSessionPersistence]) {
1000 credentials = [self findSessionProxyAuthenticationCredentials]; 1004 credentials = [self findSessionProxyAuthenticationCredentials];
1001 if (credentials) { 1005 if (credentials) {
@@ -2394,11 +2398,11 @@ static NSOperationQueue *sharedQueue = nil; @@ -2394,11 +2398,11 @@ static NSOperationQueue *sharedQueue = nil;
2394 NSString *user = nil; 2398 NSString *user = nil;
2395 NSString *pass = nil; 2399 NSString *pass = nil;
2396 2400
2397 - 2401 + ASIHTTPRequest *theRequest = [self mainRequest];
2398 // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request 2402 // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
2399 - if ([self mainRequest] && [[self mainRequest] proxyUsername] && [[self mainRequest] proxyPassword]) { 2403 + if ([theRequest proxyUsername] && [theRequest proxyPassword]) {
2400 - user = [[self mainRequest] proxyUsername]; 2404 + user = [theRequest proxyUsername];
2401 - pass = [[self mainRequest] proxyPassword]; 2405 + pass = [theRequest proxyPassword];
2402 2406
2403 // Let's try to use the ones set in this object 2407 // Let's try to use the ones set in this object
2404 } else if ([self proxyUsername] && [self proxyPassword]) { 2408 } else if ([self proxyUsername] && [self proxyPassword]) {
@@ -2406,6 +2410,13 @@ static NSOperationQueue *sharedQueue = nil; @@ -2406,6 +2410,13 @@ static NSOperationQueue *sharedQueue = nil;
2406 pass = [self proxyPassword]; 2410 pass = [self proxyPassword];
2407 } 2411 }
2408 2412
  2413 + // When we connect to a website using NTLM via a proxy, we will use the main credentials
  2414 + if ((!user || !pass) && [self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM) {
  2415 + user = [self username];
  2416 + pass = [self password];
  2417 + }
  2418 +
  2419 +
2409 2420
2410 // Ok, that didn't work, let's try the keychain 2421 // Ok, that didn't work, let's try the keychain
2411 // For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistence 2422 // For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistence
@@ -2423,13 +2434,21 @@ static NSOperationQueue *sharedQueue = nil; @@ -2423,13 +2434,21 @@ static NSOperationQueue *sharedQueue = nil;
2423 2434
2424 NSString *ntlmDomain = [self proxyDomain]; 2435 NSString *ntlmDomain = [self proxyDomain];
2425 2436
2426 - // If we have no domain yet, let's try to extract it from the username 2437 + // If we have no domain yet
2427 if (!ntlmDomain || [ntlmDomain length] == 0) { 2438 if (!ntlmDomain || [ntlmDomain length] == 0) {
2428 - ntlmDomain = @""; 2439 +
  2440 + // Let's try to extract it from the username
2429 NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"]; 2441 NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"];
2430 if ([ntlmComponents count] == 2) { 2442 if ([ntlmComponents count] == 2) {
2431 ntlmDomain = [ntlmComponents objectAtIndex:0]; 2443 ntlmDomain = [ntlmComponents objectAtIndex:0];
2432 user = [ntlmComponents objectAtIndex:1]; 2444 user = [ntlmComponents objectAtIndex:1];
  2445 +
  2446 + // 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
  2447 + } else {
  2448 + ntlmDomain = [self domain];
  2449 + }
  2450 + if (!ntlmDomain) {
  2451 + ntlmDomain = @"";
2433 } 2452 }
2434 } 2453 }
2435 [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; 2454 [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
@@ -2815,6 +2834,7 @@ static NSOperationQueue *sharedQueue = nil; @@ -2815,6 +2834,7 @@ static NSOperationQueue *sharedQueue = nil;
2815 return; 2834 return;
2816 } 2835 }
2817 2836
  2837 + // Do we actually need to authenticate with a proxy?
2818 if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) { 2838 if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) {
2819 [self attemptToApplyProxyCredentialsAndResume]; 2839 [self attemptToApplyProxyCredentialsAndResume];
2820 return; 2840 return;
@@ -4005,31 +4025,59 @@ static NSOperationQueue *sharedQueue = nil; @@ -4005,31 +4025,59 @@ static NSOperationQueue *sharedQueue = nil;
4005 { 4025 {
4006 [sessionCredentialsLock lock]; 4026 [sessionCredentialsLock lock];
4007 NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore]; 4027 NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
4008 - // Find an exact match (same url) 4028 + NSURL *requestURL = [self url];
  4029 +
  4030 + BOOL haveFoundExactMatch;
  4031 + NSDictionary *closeMatch = nil;
  4032 +
  4033 + // Loop through all the cached credentials we have, looking for the best match for this request
4009 for (NSDictionary *theCredentials in sessionCredentialsList) { 4034 for (NSDictionary *theCredentials in sessionCredentialsList) {
4010 - if ([(NSURL*)[theCredentials objectForKey:@"URL"] isEqual:[self url]]) { 4035 +
4011 - // /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 4036 + haveFoundExactMatch = NO;
4012 - if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { 4037 + NSURL *cachedCredentialsURL = [theCredentials objectForKey:@"URL"];
4013 - [sessionCredentialsLock unlock]; 4038 +
4014 - return theCredentials; 4039 + // Find an exact match (same url)
  4040 + if ([cachedCredentialsURL isEqual:[self url]]) {
  4041 + haveFoundExactMatch = YES;
  4042 +
  4043 + // This is not an exact match for the url, and we already have a close match we can use
  4044 + } else if (closeMatch) {
  4045 + continue;
  4046 +
  4047 + // Find a close match (same host, scheme and port)
  4048 + } else if ([[cachedCredentialsURL host] isEqualToString:[requestURL host]] && ([cachedCredentialsURL port] == [requestURL port] || ([requestURL port] && [[cachedCredentialsURL port] isEqualToNumber:[requestURL port]])) && [[cachedCredentialsURL scheme] isEqualToString:[requestURL scheme]]) {
  4049 + } else {
  4050 + continue;
4015 } 4051 }
  4052 +
  4053 + // 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
  4054 + if ([self authenticationRealm] && ([theCredentials objectForKey:@"AuthenticationRealm"] && ![[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {
  4055 + continue;
  4056 + }
  4057 +
  4058 + // If we have a username and password set on the request, check that they are the same as the cached ones
  4059 + if ([self username] && [self password]) {
  4060 + NSDictionary *usernameAndPassword = [theCredentials objectForKey:@"Credentials"];
  4061 + NSString *storedUsername = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername];
  4062 + NSString *storedPassword = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername];
  4063 + if (![storedUsername isEqualToString:[self username]] || ![storedPassword isEqualToString:[self password]]) {
  4064 + continue;
4016 } 4065 }
4017 } 4066 }
4018 - // Find a rough match (same host, port, scheme)  
4019 - NSURL *requestURL = [self url];  
4020 - for (NSDictionary *theCredentials in sessionCredentialsList) {  
4021 - NSURL *theURL = [theCredentials objectForKey:@"URL"];  
4022 4067
4023 - // Port can be nil! 4068 + // If we have an exact match for the url, use those credentials
4024 - if ([[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]]) { 4069 + if (haveFoundExactMatch) {
4025 - if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {  
4026 [sessionCredentialsLock unlock]; 4070 [sessionCredentialsLock unlock];
4027 return theCredentials; 4071 return theCredentials;
4028 } 4072 }
4029 - } 4073 +
  4074 + // 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
  4075 + closeMatch = theCredentials;
4030 } 4076 }
4031 [sessionCredentialsLock unlock]; 4077 [sessionCredentialsLock unlock];
4032 - return nil; 4078 +
  4079 + // Return credentials that matched on host, port and scheme, or nil if we didn't find any
  4080 + return closeMatch;
4033 } 4081 }
4034 4082
4035 #pragma mark keychain storage 4083 #pragma mark keychain storage
@@ -1087,6 +1087,16 @@ @@ -1087,6 +1087,16 @@
1087 err = [request error]; 1087 err = [request error];
1088 GHAssertNil(err,@"Failed to reuse credentials"); 1088 GHAssertNil(err,@"Failed to reuse credentials");
1089 1089
  1090 + // Ensure new credentials are used in place of those in the session
  1091 + request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/basic-authentication-new-credentials"]] autorelease];
  1092 + [request setUsername:@"secret_username_2"];
  1093 + [request setPassword:@"secret_password_2"];
  1094 + [request setUseSessionPersistence:YES];
  1095 + [request setUseKeychainPersistence:NO];
  1096 + [request startSynchronous];
  1097 + err = [request error];
  1098 + GHAssertNil(err,@"Failed to reuse credentials");
  1099 +
1090 [ASIHTTPRequest clearSession]; 1100 [ASIHTTPRequest clearSession];
1091 1101
1092 // Ensure credentials stored in the session were wiped 1102 // Ensure credentials stored in the session were wiped
@@ -1103,7 +1113,7 @@ @@ -1103,7 +1113,7 @@
1103 err = [request error]; 1113 err = [request error];
1104 GHAssertNil(err,@"Failed to use stored credentials"); 1114 GHAssertNil(err,@"Failed to use stored credentials");
1105 1115
1106 - [ASIHTTPRequest removeCredentialsForHost:@"allseeing-i.com" port:0 protocol:@"http" realm:@"SECRET_STUFF"]; 1116 + [ASIHTTPRequest removeCredentialsForHost:@"asi" port:0 protocol:@"http" realm:@"SECRET_STUFF"];
1107 1117
1108 // Ensure credentials stored in the keychain were wiped 1118 // Ensure credentials stored in the keychain were wiped
1109 request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; 1119 request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
@@ -1424,7 +1434,7 @@ @@ -1424,7 +1434,7 @@
1424 { 1434 {
1425 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_to_new_domain"]]; 1435 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_to_new_domain"]];
1426 [request startSynchronous]; 1436 [request startSynchronous];
1427 - BOOL success = [[[request url] absoluteString] isEqualToString:@"http://www.apple.com/"]; 1437 + BOOL success = [[[request url] absoluteString] isEqualToString:@"http://www.apple.com"];
1428 GHAssertTrue(success,@"Failed to redirect to a different domain"); 1438 GHAssertTrue(success,@"Failed to redirect to a different domain");
1429 } 1439 }
1430 1440