Cached credentials will not be used if different ones are set on the request
Tweak to finding NTLM proxy credentials
Showing
2 changed files
with
117 additions
and
59 deletions
@@ -939,41 +939,45 @@ static NSOperationQueue *sharedQueue = nil; | @@ -939,41 +939,45 @@ static NSOperationQueue *sharedQueue = nil; | ||
939 | if (![self shouldPresentCredentialsBeforeChallenge]) { | 939 | if (![self shouldPresentCredentialsBeforeChallenge]) { |
940 | return; | 940 | return; |
941 | } | 941 | } |
942 | - | 942 | + |
943 | - // First, see if we have any credentials we can use in the session store | ||
944 | NSDictionary *credentials = nil; | 943 | NSDictionary *credentials = nil; |
945 | - if ([self useSessionPersistence]) { | 944 | + |
946 | - credentials = [self findSessionAuthenticationCredentials]; | 945 | + // Do we already have an auth header? |
947 | - } | 946 | + if (![[self requestHeaders] objectForKey:@"Authorization"]) { |
948 | - | 947 | + |
949 | - | 948 | + // If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header |
950 | - // Are any credentials set on this request that might be used for basic authentication? | 949 | + if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) { |
951 | - if ([self username] && [self password] && ![self domain]) { | ||
952 | - | ||
953 | - // If we know this request should use Basic auth, we'll add an Authorization header with basic credentials | ||
954 | - if ([[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) { | ||
955 | [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]]; | 950 | [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]]; |
956 | - } | 951 | + |
957 | - } | ||
958 | - | ||
959 | - if (credentials && ![[self requestHeaders] objectForKey:@"Authorization"]) { | ||
960 | - | ||
961 | - // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them | ||
962 | - // (credentials for Digest and NTLM will always be stored like this) | ||
963 | - if ([credentials objectForKey:@"Authentication"]) { | ||
964 | - | ||
965 | - // If we've already talked to this server and have valid credentials, let's apply them to the request | ||
966 | - if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) { | ||
967 | - [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; | ||
968 | - } | ||
969 | - | ||
970 | - // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication | ||
971 | - // When this happens, we'll need to create the Authorization header ourselves | ||
972 | } else { | 952 | } else { |
973 | - NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"]; | 953 | + |
974 | - [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]]; | 954 | + // See if we have any cached credentials we can use in the session store |
955 | + if ([self useSessionPersistence]) { | ||
956 | + credentials = [self findSessionAuthenticationCredentials]; | ||
957 | + | ||
958 | + if (credentials) { | ||
959 | + | ||
960 | + // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them | ||
961 | + // (credentials for Digest and NTLM will always be stored like this) | ||
962 | + if ([credentials objectForKey:@"Authentication"]) { | ||
963 | + | ||
964 | + // If we've already talked to this server and have valid credentials, let's apply them to the request | ||
965 | + if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) { | ||
966 | + [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]]; | ||
967 | + } | ||
968 | + | ||
969 | + // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication | ||
970 | + // When this happens, we'll need to create the Authorization header ourselves | ||
971 | + } else { | ||
972 | + NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"]; | ||
973 | + [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]]; | ||
974 | + } | ||
975 | + } | ||
976 | + } | ||
975 | } | 977 | } |
976 | } | 978 | } |
979 | + | ||
980 | + // Apply proxy authentication credentials | ||
977 | if ([self useSessionPersistence]) { | 981 | if ([self useSessionPersistence]) { |
978 | credentials = [self findSessionProxyAuthenticationCredentials]; | 982 | credentials = [self findSessionProxyAuthenticationCredentials]; |
979 | if (credentials) { | 983 | if (credentials) { |
@@ -2368,17 +2372,24 @@ static NSOperationQueue *sharedQueue = nil; | @@ -2368,17 +2372,24 @@ static NSOperationQueue *sharedQueue = nil; | ||
2368 | NSString *user = nil; | 2372 | NSString *user = nil; |
2369 | NSString *pass = nil; | 2373 | NSString *pass = nil; |
2370 | 2374 | ||
2371 | - | 2375 | + ASIHTTPRequest *theRequest = [self mainRequest]; |
2372 | // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request | 2376 | // If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request |
2373 | - if ([self mainRequest] && [[self mainRequest] proxyUsername] && [[self mainRequest] proxyPassword]) { | 2377 | + if ([theRequest proxyUsername] && [theRequest proxyPassword]) { |
2374 | - user = [[self mainRequest] proxyUsername]; | 2378 | + user = [theRequest proxyUsername]; |
2375 | - pass = [[self mainRequest] proxyPassword]; | 2379 | + pass = [theRequest proxyPassword]; |
2376 | 2380 | ||
2377 | - // Let's try to use the ones set in this object | 2381 | + // Let's try to use the ones set in this object |
2378 | } else if ([self proxyUsername] && [self proxyPassword]) { | 2382 | } else if ([self proxyUsername] && [self proxyPassword]) { |
2379 | user = [self proxyUsername]; | 2383 | user = [self proxyUsername]; |
2380 | pass = [self proxyPassword]; | 2384 | pass = [self proxyPassword]; |
2381 | - } | 2385 | + } |
2386 | + | ||
2387 | + // When we connect to a website using NTLM via a proxy, we will use the main credentials | ||
2388 | + if ((!user || !pass) && [self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM) { | ||
2389 | + user = [self username]; | ||
2390 | + pass = [self password]; | ||
2391 | + } | ||
2392 | + | ||
2382 | 2393 | ||
2383 | 2394 | ||
2384 | // Ok, that didn't work, let's try the keychain | 2395 | // Ok, that didn't work, let's try the keychain |
@@ -2397,13 +2408,21 @@ static NSOperationQueue *sharedQueue = nil; | @@ -2397,13 +2408,21 @@ static NSOperationQueue *sharedQueue = nil; | ||
2397 | 2408 | ||
2398 | NSString *ntlmDomain = [self proxyDomain]; | 2409 | NSString *ntlmDomain = [self proxyDomain]; |
2399 | 2410 | ||
2400 | - // If we have no domain yet, let's try to extract it from the username | 2411 | + // If we have no domain yet |
2401 | if (!ntlmDomain || [ntlmDomain length] == 0) { | 2412 | if (!ntlmDomain || [ntlmDomain length] == 0) { |
2402 | - ntlmDomain = @""; | 2413 | + |
2414 | + // Let's try to extract it from the username | ||
2403 | NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"]; | 2415 | NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"]; |
2404 | if ([ntlmComponents count] == 2) { | 2416 | if ([ntlmComponents count] == 2) { |
2405 | ntlmDomain = [ntlmComponents objectAtIndex:0]; | 2417 | ntlmDomain = [ntlmComponents objectAtIndex:0]; |
2406 | user = [ntlmComponents objectAtIndex:1]; | 2418 | user = [ntlmComponents objectAtIndex:1]; |
2419 | + | ||
2420 | + // 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 | ||
2421 | + } else { | ||
2422 | + ntlmDomain = [self domain]; | ||
2423 | + } | ||
2424 | + if (!ntlmDomain) { | ||
2425 | + ntlmDomain = @""; | ||
2407 | } | 2426 | } |
2408 | } | 2427 | } |
2409 | [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; | 2428 | [newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain]; |
@@ -2789,6 +2808,7 @@ static NSOperationQueue *sharedQueue = nil; | @@ -2789,6 +2808,7 @@ static NSOperationQueue *sharedQueue = nil; | ||
2789 | return; | 2808 | return; |
2790 | } | 2809 | } |
2791 | 2810 | ||
2811 | + // Do we actually need to authenticate with a proxy? | ||
2792 | if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) { | 2812 | if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) { |
2793 | [self attemptToApplyProxyCredentialsAndResume]; | 2813 | [self attemptToApplyProxyCredentialsAndResume]; |
2794 | return; | 2814 | return; |
@@ -3971,31 +3991,59 @@ static NSOperationQueue *sharedQueue = nil; | @@ -3971,31 +3991,59 @@ static NSOperationQueue *sharedQueue = nil; | ||
3971 | { | 3991 | { |
3972 | [sessionCredentialsLock lock]; | 3992 | [sessionCredentialsLock lock]; |
3973 | NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore]; | 3993 | NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore]; |
3974 | - // Find an exact match (same url) | ||
3975 | - for (NSDictionary *theCredentials in sessionCredentialsList) { | ||
3976 | - if ([(NSURL*)[theCredentials objectForKey:@"URL"] isEqual:[self url]]) { | ||
3977 | - // /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 | ||
3978 | - if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { | ||
3979 | - [sessionCredentialsLock unlock]; | ||
3980 | - return theCredentials; | ||
3981 | - } | ||
3982 | - } | ||
3983 | - } | ||
3984 | - // Find a rough match (same host, port, scheme) | ||
3985 | NSURL *requestURL = [self url]; | 3994 | NSURL *requestURL = [self url]; |
3995 | + | ||
3996 | + BOOL haveFoundExactMatch; | ||
3997 | + NSDictionary *closeMatch = nil; | ||
3998 | + | ||
3999 | + // Loop through all the cached credentials we have, looking for the best match for this request | ||
3986 | for (NSDictionary *theCredentials in sessionCredentialsList) { | 4000 | for (NSDictionary *theCredentials in sessionCredentialsList) { |
3987 | - NSURL *theURL = [theCredentials objectForKey:@"URL"]; | ||
3988 | 4001 | ||
3989 | - // Port can be nil! | 4002 | + haveFoundExactMatch = NO; |
3990 | - if ([[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]]) { | 4003 | + NSURL *cachedCredentialsURL = [theCredentials objectForKey:@"URL"]; |
3991 | - if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { | 4004 | + |
3992 | - [sessionCredentialsLock unlock]; | 4005 | + // Find an exact match (same url) |
3993 | - return theCredentials; | 4006 | + if ([cachedCredentialsURL isEqual:[self url]]) { |
4007 | + haveFoundExactMatch = YES; | ||
4008 | + | ||
4009 | + // This is not an exact match for the url, and we already have a close match we can use | ||
4010 | + } else if (closeMatch) { | ||
4011 | + continue; | ||
4012 | + | ||
4013 | + // Find a close match (same host, scheme and port) | ||
4014 | + } else if ([[cachedCredentialsURL host] isEqualToString:[requestURL host]] && ([cachedCredentialsURL port] == [requestURL port] || ([requestURL port] && [[cachedCredentialsURL port] isEqualToNumber:[requestURL port]])) && [[cachedCredentialsURL scheme] isEqualToString:[requestURL scheme]]) { | ||
4015 | + } else { | ||
4016 | + continue; | ||
4017 | + } | ||
4018 | + | ||
4019 | + // 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 | ||
4020 | + if ([self authenticationRealm] && ([theCredentials objectForKey:@"AuthenticationRealm"] && ![[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) { | ||
4021 | + continue; | ||
4022 | + } | ||
4023 | + | ||
4024 | + // If we have a username and password set on the request, check that they are the same as the cached ones | ||
4025 | + if ([self username] && [self password]) { | ||
4026 | + NSDictionary *usernameAndPassword = [theCredentials objectForKey:@"Credentials"]; | ||
4027 | + NSString *storedUsername = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername]; | ||
4028 | + NSString *storedPassword = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername]; | ||
4029 | + if (![storedUsername isEqualToString:[self username]] || ![storedPassword isEqualToString:[self password]]) { | ||
4030 | + continue; | ||
3994 | } | 4031 | } |
3995 | } | 4032 | } |
4033 | + | ||
4034 | + // If we have an exact match for the url, use those credentials | ||
4035 | + if (haveFoundExactMatch) { | ||
4036 | + [sessionCredentialsLock unlock]; | ||
4037 | + return theCredentials; | ||
4038 | + } | ||
4039 | + | ||
4040 | + // 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 | ||
4041 | + closeMatch = theCredentials; | ||
3996 | } | 4042 | } |
3997 | [sessionCredentialsLock unlock]; | 4043 | [sessionCredentialsLock unlock]; |
3998 | - return nil; | 4044 | + |
4045 | + // Return credentials that matched on host, port and scheme, or nil if we didn't find any | ||
4046 | + return closeMatch; | ||
3999 | } | 4047 | } |
4000 | 4048 | ||
4001 | #pragma mark keychain storage | 4049 | #pragma mark keychain storage |
@@ -1036,10 +1036,10 @@ | @@ -1036,10 +1036,10 @@ | ||
1036 | 1036 | ||
1037 | - (void)testBasicAuthentication | 1037 | - (void)testBasicAuthentication |
1038 | { | 1038 | { |
1039 | - [ASIHTTPRequest removeCredentialsForHost:@"allseeing-i.com" port:0 protocol:@"http" realm:@"SECRET_STUFF"]; | 1039 | + [ASIHTTPRequest removeCredentialsForHost:@"asi" port:0 protocol:@"http" realm:@"SECRET_STUFF"]; |
1040 | [ASIHTTPRequest clearSession]; | 1040 | [ASIHTTPRequest clearSession]; |
1041 | 1041 | ||
1042 | - NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/basic-authentication"]; | 1042 | + NSURL *url = [NSURL URLWithString:@"http://asi/ASIHTTPRequest/tests/basic-authentication"]; |
1043 | ASIHTTPRequest *request; | 1043 | ASIHTTPRequest *request; |
1044 | BOOL success; | 1044 | BOOL success; |
1045 | NSError *err; | 1045 | NSError *err; |
@@ -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://asi/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]; |
-
Please register or login to post a comment