Skip to content
This repository has been archived by the owner on Mar 9, 2022. It is now read-only.

Commit

Permalink
Yet more replication auth fixes
Browse files Browse the repository at this point in the history
- Use credentials embedded in the URL
- Make sure Authorization: header gets preserved across a redirect (NSURLConnection drops it)
- Weed out ServerTrust auth 'challenges' so they don't confuse my code
- Added optional username/password args to TouchServ's replication command

Fixes problems reported by Joel at 36codes.com
  • Loading branch information
snej committed Jul 20, 2012
1 parent 92e1a7a commit 277cf46
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 32 deletions.
43 changes: 36 additions & 7 deletions Demo-Mac/TouchServ.m
Expand Up @@ -46,7 +46,10 @@
}


static bool doReplicate( TDServer* server, const char* replArg, BOOL pull, BOOL createTarget) {
static bool doReplicate( TDServer* server, const char* replArg,
BOOL pull, BOOL createTarget,
const char *user, const char *password)
{
NSURL* remote = [NSMakeCollectable(CFURLCreateWithBytes(NULL, (const UInt8*)replArg,
strlen(replArg),
kCFStringEncodingUTF8, NULL)) autorelease];
Expand All @@ -59,6 +62,28 @@ static bool doReplicate( TDServer* server, const char* replArg, BOOL pull, BOOL
fprintf(stderr, "Invalid database name '%s'\n", dbName.UTF8String);
return false;
}

if (user && password) {
NSString* userStr = [NSString stringWithCString: user encoding: NSUTF8StringEncoding];
NSString* passStr = [NSString stringWithCString: password encoding: NSUTF8StringEncoding];
Log(@"Setting credentials for user '%@'", userStr);
NSURLCredential* cred;
cred = [NSURLCredential credentialWithUser: userStr
password: passStr
persistence: NSURLCredentialPersistenceForSession];
int port = remote.port.intValue;
if (port == 0)
port = [remote.scheme isEqualToString: @"https"] ? 443 : 80;
NSURLProtectionSpace* space;
space = [[[NSURLProtectionSpace alloc] initWithHost: remote.host
port: port
protocol: remote.scheme
realm: nil
authenticationMethod: NSURLAuthenticationMethodDefault]
autorelease];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential: cred
forProtectionSpace: space];
}

if (pull)
Log(@"Pulling from <%@> --> %@ ...", remote, dbName);
Expand All @@ -74,8 +99,8 @@ static bool doReplicate( TDServer* server, const char* replArg, BOOL pull, BOOL
fprintf(stderr, "Couldn't delete existing database '%s'\n", dbName.UTF8String);
return;
}
db = [dbm databaseNamed: dbName];
}
db = [dbm databaseNamed: dbName];
}
if (!db) {
fprintf(stderr, "No such database '%s'\n", dbName.UTF8String);
Expand Down Expand Up @@ -118,7 +143,7 @@ int main (int argc, const char * argv[])
NSData* value = [@"value" dataUsingEncoding: NSUTF8StringEncoding];
listener.TXTRecordDictionary = [NSDictionary dictionaryWithObject: value forKey: @"Key"];

const char* replArg = NULL;
const char* replArg = NULL, *user = NULL, *password = NULL;
BOOL pull = NO, createTarget = NO;

for (int i = 1; i < argc; ++i) {
Expand All @@ -131,19 +156,23 @@ int main (int argc, const char * argv[])
forKey: @"touchdb"];
Log(@"Auth required: user='touchdb', password='%@'", password);
} else if (strcmp(argv[i], "--pull") == 0) {
replArg = argv[i+1];
replArg = argv[++i];
pull = YES;
} else if (strcmp(argv[i], "--push") == 0) {
replArg = argv[i+1];
replArg = argv[++i];
} else if (strcmp(argv[i], "--create-target") == 0) {
createTarget = YES;
} else if (strcmp(argv[i], "--user") == 0) {
user = argv[++i];
} else if (strcmp(argv[i], "--password") == 0) {
password = argv[++i];
}
}

[listener start];

if (replArg) {
if (!doReplicate(server, replArg, pull, createTarget))
if (!doReplicate(server, replArg, pull, createTarget, user, password))
return 1;
} else {
Log(@"TouchServ %@ is listening%@ on port %d ... relax!",
Expand Down
16 changes: 10 additions & 6 deletions Source/ChangeTracker/TDConnectionChangeTracker.m
Expand Up @@ -85,11 +85,8 @@ - (bool) retryWithCredential {
if (_authorizer || _challenged)
return false;
_challenged = YES;
NSURLProtectionSpace* space = [_databaseURL
my_protectionSpaceWithRealm: nil
authenticationMethod: NSURLAuthenticationMethodHTTPBasic];
NSURLCredential* cred = [[NSURLCredentialStorage sharedCredentialStorage]
defaultCredentialForProtectionSpace: space];
NSURLCredential* cred = [_databaseURL my_credentialForRealm: nil
authenticationMethod: NSURLAuthenticationMethodHTTPBasic];
if (!cred) {
LogTo(ChangeTracker, @"Got 401 but no stored credential found (with nil realm)");
return false;
Expand All @@ -107,8 +104,15 @@ - (bool) retryWithCredential {
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
_challenged = true;
id<NSURLAuthenticationChallengeSender> sender = challenge.sender;
NSString* authMethod = [[challenge protectionSpace] authenticationMethod];
if ($equal(authMethod, NSURLAuthenticationMethodServerTrust)) {
// TODO: Check trust of server cert
[sender performDefaultHandlingForAuthenticationChallenge: challenge];
return;
}

_challenged = true;
if (challenge.proposedCredential) {
[sender performDefaultHandlingForAuthenticationChallenge: challenge];
return;
Expand Down
24 changes: 14 additions & 10 deletions Source/TDMultipartDownloader.m
Expand Up @@ -67,17 +67,21 @@ - (NSDictionary*) document {


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[super connection: connection didReceiveResponse: response];
if (!_connection)
return;
TDStatus status = (TDStatus) ((NSHTTPURLResponse*)response).statusCode;
if (status < 300) {
// Check the content type to see whether it's a multipart response:
NSDictionary* headers = [(NSHTTPURLResponse*)response allHeaderFields];
NSString* contentType = [headers objectForKey: @"Content-Type"];
if ([contentType hasPrefix: @"text/plain"])
contentType = nil; // Workaround for CouchDB returning JSON docs with text/plain type
if (![_reader setContentType: contentType]) {
LogTo(RemoteRequest, @"%@ got invalid Content-Type '%@'", self, contentType);
[self cancelWithStatus: _reader.status];
return;
}
}

// Check the content type to see whether it's a multipart response:
NSDictionary* headers = [(NSHTTPURLResponse*)response allHeaderFields];
NSString* contentType = [headers objectForKey: @"Content-Type"];
if ([contentType hasPrefix: @"text/plain"])
contentType = nil; // Workaround for CouchDB returning JSON docs with text/plain type
if (![_reader setContentType: contentType])
[self cancelWithStatus: _reader.status];
[super connection: connection didReceiveResponse: response];
}


Expand Down
49 changes: 41 additions & 8 deletions Source/TDRemoteRequest.m
Expand Up @@ -163,19 +163,16 @@ - (bool) retryWithCredential {
if (_authorizer || _challenged)
return false;
_challenged = YES;
NSURLProtectionSpace* space = [_request.URL
my_protectionSpaceWithRealm: nil
authenticationMethod: NSURLAuthenticationMethodHTTPBasic];
NSURLCredential* cred = [[NSURLCredentialStorage sharedCredentialStorage]
defaultCredentialForProtectionSpace: space];
NSURLCredential* cred = [_request.URL my_credentialForRealm: nil
authenticationMethod: NSURLAuthenticationMethodHTTPBasic];
if (!cred) {
LogTo(RemoteRequest, @"Got 401 but no stored credential found (with nil realm)");
return false;
}

[_connection cancel];
self.authorizer = [[[TDBasicAuthorizer alloc] initWithCredential: cred] autorelease];
LogTo(RemoteRequest, @"Got 401 but retrying with %@", _authorizer);
LogTo(RemoteRequest, @"%@ retrying with %@", self, _authorizer);
[self startAfterDelay: 0.0];
return true;
}
Expand All @@ -187,11 +184,25 @@ - (bool) retryWithCredential {
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
LogTo(RemoteRequest, @"Got challenge: %@", challenge);
_challenged = true;
NSString* authMethod = [[challenge protectionSpace] authenticationMethod];
LogTo(RemoteRequest, @"Got challenge: %@ (%@)", challenge, authMethod);
if ($equal(authMethod, NSURLAuthenticationMethodHTTPBasic)) {
_challenged = true;
if (challenge.previousFailureCount == 0) {
NSURLCredential* cred = [_request.URL my_credentialForRealm: challenge.protectionSpace.realm
authenticationMethod: authMethod];
if (cred) {
[challenge.sender useCredential: cred forAuthenticationChallenge:challenge];
return;
}
}
} else if ($equal(authMethod, NSURLAuthenticationMethodServerTrust)) {
// TODO: Check trust of server cert
}
[challenge.sender performDefaultHandlingForAuthenticationChallenge: challenge];
}


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
_status = (int) ((NSHTTPURLResponse*)response).statusCode;
LogTo(RemoteRequest, @"%@: Got response, status %d", self, _status);
Expand All @@ -205,10 +216,31 @@ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLRespon
[self cancelWithStatus: _status];
}


- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)response
{
// The redirected request needs to be authorized again:
if (![request valueForHTTPHeaderField: @"Authorization"]) {
NSMutableURLRequest* nuRequest = [[request mutableCopy] autorelease];
NSString* auth;
if (_authorizer)
auth = [_authorizer authorizeURLRequest: nuRequest forRealm: nil];
else
auth = [_request valueForHTTPHeaderField: @"Authorization"];
[nuRequest setValue: auth forHTTPHeaderField: @"Authorization"];
request = nuRequest;
}
return request;
}


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
LogTo(RemoteRequest, @"%@: Got %lu bytes", self, (unsigned long)data.length);
}


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (WillLog()) {
if (!(_dontLog404 && error.code == kTDStatusNotFound && $equal(error.domain, TDHTTPErrorDomain)))
Expand All @@ -229,6 +261,7 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self respondWithResult: self error: nil];
}


- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
Expand Down
2 changes: 1 addition & 1 deletion vendor/MYUtilities
Submodule MYUtilities updated 1 files
+2 −0 MYURLUtils.m

0 comments on commit 277cf46

Please sign in to comment.