소스 검색

Allow multiple user accounts for authentication

Pierre-Olivier Latour 11 년 전
부모
커밋
1ca5a56952
5개의 변경된 파일53개의 추가작업 그리고 46개의 파일을 삭제
  1. 2 3
      GCDWebServer/Core/GCDWebServer.h
  2. 18 15
      GCDWebServer/Core/GCDWebServer.m
  3. 24 17
      GCDWebServer/Core/GCDWebServerConnection.m
  4. 2 2
      GCDWebServer/Core/GCDWebServerPrivate.h
  5. 7 9
      Mac/main.m

+ 2 - 3
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;

+ 18 - 15
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:)]) {

+ 24 - 17
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) {

+ 2 - 2
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;

+ 7 - 9
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;