Ben Copsey

Merge branch 'auth-tweaks'

@@ -961,41 +961,45 @@ static NSOperationQueue *sharedQueue = nil; @@ -961,41 +961,45 @@ static NSOperationQueue *sharedQueue = nil;
961 if (![self shouldPresentCredentialsBeforeChallenge]) { 961 if (![self shouldPresentCredentialsBeforeChallenge]) {
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]) { 966 +
968 - credentials = [self findSessionAuthenticationCredentials]; 967 + // Do we already have an auth header?
969 - } 968 + if (![[self requestHeaders] objectForKey:@"Authorization"]) {
970 - 969 +
971 - 970 + // If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header
972 - // Are any credentials set on this request that might be used for basic authentication? 971 + if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {
973 - if ([self username] && [self password] && ![self domain]) {  
974 -  
975 - // If we know this request should use Basic auth, we'll add an Authorization header with basic credentials  
976 - if ([[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {  
977 [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]]; 972 [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]];
978 - } 973 +
979 - }  
980 -  
981 - if (credentials && ![[self requestHeaders] objectForKey:@"Authorization"]) {  
982 -  
983 - // 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)  
985 - if ([credentials objectForKey:@"Authentication"]) {  
986 -  
987 - // If we've already talked to this server and have valid credentials, let's apply them to the request  
988 - if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {  
989 - [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];  
990 - }  
991 -  
992 - // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication  
993 - // When this happens, we'll need to create the Authorization header ourselves  
994 } else { 974 } else {
995 - NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"]; 975 +
996 - [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]]; 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) {
  981 +
  982 + // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them
  983 + // (credentials for Digest and NTLM will always be stored like this)
  984 + if ([credentials objectForKey:@"Authentication"]) {
  985 +
  986 + // If we've already talked to this server and have valid credentials, let's apply them to the request
  987 + if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
  988 + [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
  989 + }
  990 +
  991 + // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication
  992 + // When this happens, we'll need to create the Authorization header ourselves
  993 + } else {
  994 + NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"];
  995 + [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
  996 + }
  997 + }
  998 + }
997 } 999 }
998 } 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,17 +2398,24 @@ static NSOperationQueue *sharedQueue = nil; @@ -2394,17 +2398,24 @@ 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]) {
2405 user = [self proxyUsername]; 2409 user = [self proxyUsername];
2406 pass = [self proxyPassword]; 2410 pass = [self proxyPassword];
2407 - } 2411 + }
  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 +
2408 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
@@ -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)  
4009 - for (NSDictionary *theCredentials in sessionCredentialsList) {  
4010 - if ([(NSURL*)[theCredentials objectForKey:@"URL"] isEqual:[self url]]) {  
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  
4012 - if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {  
4013 - [sessionCredentialsLock unlock];  
4014 - return theCredentials;  
4015 - }  
4016 - }  
4017 - }  
4018 - // Find a rough match (same host, port, scheme)  
4019 NSURL *requestURL = [self 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
4020 for (NSDictionary *theCredentials in sessionCredentialsList) { 4034 for (NSDictionary *theCredentials in sessionCredentialsList) {
4021 - NSURL *theURL = [theCredentials objectForKey:@"URL"];  
4022 4035
4023 - // Port can be nil! 4036 + haveFoundExactMatch = NO;
4024 - if ([[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]]) { 4037 + NSURL *cachedCredentialsURL = [theCredentials objectForKey:@"URL"];
4025 - if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { 4038 +
4026 - [sessionCredentialsLock unlock]; 4039 + // Find an exact match (same url)
4027 - return theCredentials; 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;
  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;
4028 } 4065 }
4029 } 4066 }
  4067 +
  4068 + // If we have an exact match for the url, use those credentials
  4069 + if (haveFoundExactMatch) {
  4070 + [sessionCredentialsLock unlock];
  4071 + return theCredentials;
  4072 + }
  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