Skip to content

Commit

Permalink
Merge branch 'auth-tweaks' into integration
Browse files Browse the repository at this point in the history
  • Loading branch information
pokeb committed May 8, 2011
2 parents a20c118 + 5262af3 commit b9ec18f
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 58 deletions.
160 changes: 104 additions & 56 deletions Classes/ASIHTTPRequest.m
Expand Up @@ -961,41 +961,45 @@ - (void)applyAuthorizationHeader
if (![self shouldPresentCredentialsBeforeChallenge]) {
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]) {

// If we know this request should use Basic auth, we'll add an Authorization header with basic credentials
if ([[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {

// Do we already have an auth header?
if (![[self requestHeaders] objectForKey:@"Authorization"]) {

// 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"]) {

// 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)
if ([credentials objectForKey:@"Authentication"]) {

// If we've already talked to this server and have valid credentials, let's apply them to the request
if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
[[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
}

// If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication
// When this happens, we'll need to create the Authorization header ourselves

} else {
NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"];
[self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];

// 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)
if ([credentials objectForKey:@"Authentication"]) {

// If we've already talked to this server and have valid credentials, let's apply them to the request
if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
[[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
}

// If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication
// When this happens, we'll need to create the Authorization header ourselves
} else {
NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"];
[self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
}
}
}
}
}

// Apply proxy authentication credentials
if ([self useSessionPersistence]) {
credentials = [self findSessionProxyAuthenticationCredentials];
if (credentials) {
Expand Down Expand Up @@ -2394,17 +2398,24 @@ - (NSMutableDictionary *)findProxyCredentials
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
// Let's try to use the ones set in this object
} else if ([self proxyUsername] && [self proxyPassword]) {
user = [self proxyUsername];
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
Expand All @@ -2423,13 +2434,21 @@ - (NSMutableDictionary *)findProxyCredentials

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];
Expand Down Expand Up @@ -2815,6 +2834,7 @@ - (void)attemptToApplyCredentialsAndResume
return;
}

// Do we actually need to authenticate with a proxy?
if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) {
[self attemptToApplyProxyCredentialsAndResume];
return;
Expand Down Expand Up @@ -4005,31 +4025,59 @@ - (NSDictionary *)findSessionAuthenticationCredentials
{
[sessionCredentialsLock lock];
NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
// Find an exact match (same url)
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;
}
}
}
// Find a rough match (same host, port, scheme)
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) {
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]])) {
[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;
}
}

// 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
Expand Down
14 changes: 12 additions & 2 deletions Classes/Tests/ASIHTTPRequestTests.m
Expand Up @@ -1087,6 +1087,16 @@ - (void)testBasicAuthentication
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://allseeing-i.com/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
Expand All @@ -1103,7 +1113,7 @@ - (void)testBasicAuthentication
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];
Expand Down Expand Up @@ -1424,7 +1434,7 @@ - (void)testRedirectToNewDomain
{
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_to_new_domain"]];
[request startSynchronous];
BOOL success = [[[request url] absoluteString] isEqualToString:@"http://www.apple.com/"];
BOOL success = [[[request url] absoluteString] isEqualToString:@"http://www.apple.com"];
GHAssertTrue(success,@"Failed to redirect to a different domain");
}

Expand Down

0 comments on commit b9ec18f

Please sign in to comment.