Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Yet more replication auth fixes

- 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...
commit 277cf46e2ca2045f98d5d3d75a4bd180af78586a 1 parent 92e1a7a
@snej snej authored
View
43 Demo-Mac/TouchServ.m
@@ -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];
@@ -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);
@@ -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);
@@ -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) {
@@ -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!",
View
16 Source/ChangeTracker/TDConnectionChangeTracker.m
@@ -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;
@@ -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;
View
24 Source/TDMultipartDownloader.m
@@ -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];
}
View
49 Source/TDRemoteRequest.m
@@ -163,11 +163,8 @@ - (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;
@@ -175,7 +172,7 @@ - (bool) retryWithCredential {
[_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;
}
@@ -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);
@@ -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)))
@@ -229,6 +261,7 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self respondWithResult: self error: nil];
}
+
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
2  vendor/MYUtilities
@@ -1 +1 @@
-Subproject commit e8e65f61f44af31182f09a97fb630bce940f4c4a
+Subproject commit afe6991b970fb04fced8b368fbeb2e49aff274af
Please sign in to comment.
Something went wrong with that request. Please try again.