From 1ca5a5695273be4d13ba9d7dec74d20da66cbf1e Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Latour Date: Fri, 18 Apr 2014 12:50:09 -0300 Subject: [PATCH] Allow multiple user accounts for authentication --- GCDWebServer/Core/GCDWebServer.h | 5 ++- GCDWebServer/Core/GCDWebServer.m | 33 +++++++++-------- GCDWebServer/Core/GCDWebServerConnection.m | 41 +++++++++++++--------- GCDWebServer/Core/GCDWebServerPrivate.h | 4 +-- Mac/main.m | 16 ++++----- 5 files changed, 53 insertions(+), 46 deletions(-) diff --git a/GCDWebServer/Core/GCDWebServer.h b/GCDWebServer/Core/GCDWebServer.h index bc9d811..e9a5196 100644 --- a/GCDWebServer/Core/GCDWebServer.h +++ b/GCDWebServer/Core/GCDWebServer.h @@ -48,8 +48,7 @@ extern NSString* const GCDWebServerOption_MaxPendingConnections; // NSNumber / extern NSString* const GCDWebServerOption_ServerName; // NSString (default is server class name) extern NSString* const GCDWebServerOption_AuthenticationMethod; // One of "GCDWebServerAuthenticationMethod_..." (default is nil i.e. no authentication) extern NSString* const GCDWebServerOption_AuthenticationRealm; // NSString (default is server name) -extern NSString* const GCDWebServerOption_AuthenticationUser; // NSString -extern NSString* const GCDWebServerOption_AuthenticationPassword; // NSString +extern NSString* const GCDWebServerOption_AuthenticationAccounts; // NSDictionary of username / password (default is nil i.e. no accounts) extern NSString* const GCDWebServerOption_ConnectionClass; // Subclass of GCDWebServerConnection (default is GCDWebServerConnection class) extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET; // NSNumber / BOOL (default is YES) extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; // NSNumber / double (default is 1.0 seconds - set to <=0.0 to disable coaslescing of -webServerDidConnect: / -webServerDidDisconnect:) @@ -57,7 +56,7 @@ extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; // extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; // NSNumber / BOOL (default is YES) #endif -extern NSString* const GCDWebServerAuthenticationMethod_Basic; +extern NSString* const GCDWebServerAuthenticationMethod_Basic; // Not recommended as password is sent in clear extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess; @class GCDWebServer; diff --git a/GCDWebServer/Core/GCDWebServer.m b/GCDWebServer/Core/GCDWebServer.m index 695855e..30a5494 100644 --- a/GCDWebServer/Core/GCDWebServer.m +++ b/GCDWebServer/Core/GCDWebServer.m @@ -55,8 +55,8 @@ NSDictionary* _options; NSString* _serverName; NSString* _authenticationRealm; - NSString* _authenticationBasicAccount; - NSString* _authenticationDigestAccount; + NSMutableDictionary* _authenticationBasicAccounts; + NSMutableDictionary* _authenticationDigestAccounts; Class _connectionClass; BOOL _mapHEADToGET; CFTimeInterval _disconnectDelay; @@ -86,8 +86,7 @@ NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnectio NSString* const GCDWebServerOption_ServerName = @"ServerName"; NSString* const GCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod"; NSString* const GCDWebServerOption_AuthenticationRealm = @"AuthenticationRealm"; -NSString* const GCDWebServerOption_AuthenticationUser = @"AuthenticationUser"; -NSString* const GCDWebServerOption_AuthenticationPassword = @"AuthenticationPassword"; +NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAccounts"; NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass"; NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET"; NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval"; @@ -157,7 +156,7 @@ static void _SignalHandler(int signal) { @implementation GCDWebServer @synthesize delegate=_delegate, handlers=_handlers, port=_port, serverName=_serverName, authenticationRealm=_authenticationRealm, - authenticationBasicAccount=_authenticationBasicAccount, authenticationDigestAccount=_authenticationDigestAccount, + authenticationBasicAccounts=_authenticationBasicAccounts, authenticationDigestAccounts=_authenticationDigestAccounts, shouldAutomaticallyMapHEADToGET=_mapHEADToGET; #ifndef __GCDWEBSERVER_LOGGING_HEADER__ @@ -383,14 +382,18 @@ static inline NSString* _EncodeBase64(NSString* string) { NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil); if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) { _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; - NSString* user = _GetOption(_options, GCDWebServerOption_AuthenticationUser, @""); - NSString* password = _GetOption(_options, GCDWebServerOption_AuthenticationPassword, @""); - _authenticationBasicAccount = ARC_RETAIN(_EncodeBase64([NSString stringWithFormat:@"%@:%@", user, password])); + _authenticationBasicAccounts = [[NSMutableDictionary alloc] init]; + NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); + [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { + [_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username]; + }]; } else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) { _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; - NSString* user = _GetOption(_options, GCDWebServerOption_AuthenticationUser, @""); - NSString* password = _GetOption(_options, GCDWebServerOption_AuthenticationPassword, @""); - _authenticationDigestAccount = ARC_RETAIN(GCDWebServerComputeMD5Digest(@"%@:%@:%@", user, _authenticationRealm, password)); + _authenticationDigestAccounts = [[NSMutableDictionary alloc] init]; + NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); + [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { + [_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username]; + }]; } _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]); _mapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue]; @@ -508,10 +511,10 @@ static inline NSString* _EncodeBase64(NSString* string) { _serverName = nil; ARC_RELEASE(_authenticationRealm); _authenticationRealm = nil; - ARC_RELEASE(_authenticationBasicAccount); - _authenticationBasicAccount = nil; - ARC_RELEASE(_authenticationDigestAccount); - _authenticationDigestAccount = nil; + ARC_RELEASE(_authenticationBasicAccounts); + _authenticationBasicAccounts = nil; + ARC_RELEASE(_authenticationDigestAccounts); + _authenticationDigestAccounts = nil; LOG_INFO(@"%@ stopped", [self class]); if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) { diff --git a/GCDWebServer/Core/GCDWebServerConnection.m b/GCDWebServer/Core/GCDWebServerConnection.m index 4023a28..9f06274 100644 --- a/GCDWebServer/Core/GCDWebServerConnection.m +++ b/GCDWebServer/Core/GCDWebServerConnection.m @@ -717,36 +717,43 @@ static NSString* _StringFromAddressData(NSData* data) { - (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request { LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_bytesRead); GCDWebServerResponse* response = nil; - if (_server.authenticationBasicAccount) { - BOOL authenticated = NO; + if (_server.authenticationBasicAccounts) { + __block BOOL authenticated = NO; NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"]; if ([authorizationHeader hasPrefix:@"Basic "]) { NSString* basicAccount = [authorizationHeader substringFromIndex:6]; - if ([basicAccount isEqualToString:_server.authenticationBasicAccount]) { - authenticated = YES; - } + [_server.authenticationBasicAccounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* digest, BOOL* stop) { + if ([basicAccount isEqualToString:digest]) { + authenticated = YES; + *stop = YES; + } + }]; } if (!authenticated) { response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized]; [response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"]; } - } else if (_server.authenticationDigestAccount) { + } else if (_server.authenticationDigestAccounts) { BOOL authenticated = NO; BOOL isStaled = NO; NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"]; if ([authorizationHeader hasPrefix:@"Digest "]) { - NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce"); - if ([nonce isEqualToString:_digestAuthenticationNonce]) { // TODO: Also check "realm" and "username" provided by client - NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri"); - NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response"); - NSString* ha1 = _server.authenticationDigestAccount; - NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required - NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2); - if ([actualResponse isEqualToString:expectedResponse]) { - authenticated = YES; + NSString* realm = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm"); + if ([realm isEqualToString:_server.authenticationRealm]) { + NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce"); + if ([nonce isEqualToString:_digestAuthenticationNonce]) { + NSString* username = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username"); + NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri"); + NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response"); + NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username]; + NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required + NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2); + if ([actualResponse isEqualToString:expectedResponse]) { + authenticated = YES; + } + } else if (nonce.length) { + isStaled = YES; } - } else if (nonce.length) { - isStaled = YES; } } if (!authenticated) { diff --git a/GCDWebServer/Core/GCDWebServerPrivate.h b/GCDWebServer/Core/GCDWebServerPrivate.h index 6ed246d..d29f2eb 100644 --- a/GCDWebServer/Core/GCDWebServerPrivate.h +++ b/GCDWebServer/Core/GCDWebServerPrivate.h @@ -131,8 +131,8 @@ extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_F @property(nonatomic, readonly) NSArray* handlers; @property(nonatomic, readonly) NSString* serverName; @property(nonatomic, readonly) NSString* authenticationRealm; -@property(nonatomic, readonly) NSString* authenticationBasicAccount; -@property(nonatomic, readonly) NSString* authenticationDigestAccount; +@property(nonatomic, readonly) NSDictionary* authenticationBasicAccounts; +@property(nonatomic, readonly) NSDictionary* authenticationDigestAccounts; @property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET; - (void)willStartConnection:(GCDWebServerConnection*)connection; - (void)didEndConnection:(GCDWebServerConnection*)connection; diff --git a/Mac/main.m b/Mac/main.m index ff817e8..9b7f993 100644 --- a/Mac/main.m +++ b/Mac/main.m @@ -300,16 +300,14 @@ int main(int argc, const char* argv[]) { NSMutableDictionary* options = [NSMutableDictionary dictionary]; [options setObject:@8080 forKey:GCDWebServerOption_Port]; [options setObject:@"" forKey:GCDWebServerOption_BonjourName]; - if ([authenticationMethod isEqualToString:@"Basic"]) { - [options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod]; + if (authenticationUser && authenticationPassword) { [options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm]; - [options setValue:authenticationUser forKey:GCDWebServerOption_AuthenticationUser]; - [options setValue:authenticationPassword forKey:GCDWebServerOption_AuthenticationPassword]; - } else if ([authenticationMethod isEqualToString:@"Digest"]) { - [options setObject:GCDWebServerAuthenticationMethod_DigestAccess forKey:GCDWebServerOption_AuthenticationMethod]; - [options setValue:authenticationRealm forKey:GCDWebServerOption_AuthenticationRealm]; - [options setValue:authenticationUser forKey:GCDWebServerOption_AuthenticationUser]; - [options setValue:authenticationPassword forKey:GCDWebServerOption_AuthenticationPassword]; + [options setObject:@{authenticationUser: authenticationPassword} forKey:GCDWebServerOption_AuthenticationAccounts]; + if ([authenticationMethod isEqualToString:@"Basic"]) { + [options setObject:GCDWebServerAuthenticationMethod_Basic forKey:GCDWebServerOption_AuthenticationMethod]; + } else if ([authenticationMethod isEqualToString:@"Digest"]) { + [options setObject:GCDWebServerAuthenticationMethod_DigestAccess forKey:GCDWebServerOption_AuthenticationMethod]; + } } if ([webServer runWithOptions:options]) { result = 0;