Allow multiple user accounts for authentication

This commit is contained in:
Pierre-Olivier Latour 2014-04-18 12:50:09 -03:00
parent 80cff01154
commit 1ca5a56952
5 changed files with 53 additions and 46 deletions

View File

@ -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;

View File

@ -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:)]) {

View File

@ -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) {

View File

@ -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;

View File

@ -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;