瀏覽代碼

- The 'password' value in both the '[inet_http_server]' and
'[unix_http_server]' sections can now optionally be specified as a
SHA hexdigest instead of as cleartext. Values prefixed with
'{SHA}' will be considered SHA hex digests. To encrypt a password
to a form suitable for pasting into the configuration file using
Python, do, e.g.:

>>> import sha
>>> '{SHA}' + sha.new('thepassword').hexdigest()
'{SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d'

Chris McDonough 17 年之前
父節點
當前提交
369092e654
共有 3 個文件被更改,包括 91 次插入8 次删除
  1. 11 0
      CHANGES.txt
  2. 30 5
      src/supervisor/http.py
  3. 50 3
      src/supervisor/tests/test_http.py

+ 11 - 0
CHANGES.txt

@@ -45,6 +45,17 @@ Next Release
 
   - The 'pidproxy' script was made into a console script.
 
+  - The 'password' value in both the '[inet_http_server]' and
+    '[unix_http_server]' sections can now optionally be specified as a
+    SHA hexdigest instead of as cleartext.  Values prefixed with
+    '{SHA}' will be considered SHA hex digests.  To encrypt a password
+    to a form suitable for pasting into the configuration file using
+    Python, do, e.g.:
+
+       >>> import sha
+       >>> '{SHA}' + sha.new('thepassword').hexdigest()
+       '{SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d'
+
 3.0a3
 
   - Supervisorctl now reports a better error message when the main

+ 30 - 5
src/supervisor/http.py

@@ -27,6 +27,8 @@ from medusa import http_server
 from medusa import producers
 from medusa import filesys
 
+from medusa.auth_handler import auth_handler
+
 class NOT_DONE_YET:
     pass
 
@@ -817,11 +819,10 @@ def make_http_servers(options, supervisord):
             # wrap the xmlrpc handler and tailhandler in an authentication
             # handler
             users = {username:password}
-            from medusa.auth_handler import auth_handler
-            xmlrpchandler = auth_handler(users, xmlrpchandler)
-            tailhandler = auth_handler(users, tailhandler)
-            maintailhandler = auth_handler(users, maintailhandler)
-            uihandler = auth_handler(users, uihandler)
+            xmlrpchandler = supervisor_auth_handler(users, xmlrpchandler)
+            tailhandler = supervisor_auth_handler(users, tailhandler)
+            maintailhandler = supervisor_auth_handler(users, maintailhandler)
+            uihandler = supervisor_auth_handler(users, uihandler)
         else:
             options.logger.critical(
                 'Server %r running without any HTTP '
@@ -835,3 +836,27 @@ def make_http_servers(options, supervisord):
         servers.append((config, hs))
 
     return servers
+
+class encrypted_dictionary_authorizer:
+    def __init__ (self, dict):
+        self.dict = dict
+
+    def authorize(self, auth_info):
+        username, password = auth_info
+        if self.dict.has_key(username):
+            stored_password = self.dict[username]
+            if stored_password.startswith('{SHA}'):
+                import sha
+                password_hash = sha.new(password).hexdigest()
+                return stored_password[5:] == password_hash
+            else:
+                return stored_password == password
+        else:
+            return False
+
+class supervisor_auth_handler(auth_handler):
+    def __init__(self, dict, handler, realm='default'):
+        auth_handler.__init__(self, dict, handler, realm)
+        # override the authorizer with one that knows about SHA hashes too
+        self.authorizer = encrypted_dictionary_authorizer(dict)
+        

+ 50 - 3
src/supervisor/tests/test_http.py

@@ -10,7 +10,6 @@ from supervisor.tests.base import DummyRPCInterfaceFactory
 from supervisor.tests.base import DummyPConfig
 from supervisor.tests.base import DummyOptions
 from supervisor.tests.base import DummyRequest
-from supervisor.tests.base import DummyProcess
 
 from supervisor.http import NOT_DONE_YET
 
@@ -251,6 +250,53 @@ class DeferringHookedProducerTests(unittest.TestCase):
         self.assertEqual(producer.more(), '')
         self.assertEqual(L, [0])
 
+class EncryptedDictionaryAuthorizedTests(unittest.TestCase):
+    def _getTargetClass(self):
+        from supervisor.http import encrypted_dictionary_authorizer
+        return encrypted_dictionary_authorizer
+
+    def _makeOne(self, dict):
+        return self._getTargetClass()(dict)
+
+    def test_authorize_baduser(self):
+        authorizer = self._makeOne({})
+        self.assertEqual(authorizer.authorize(('foo', 'bar')), False)
+        
+    def test_authorize_gooduser_badpassword(self):
+        authorizer = self._makeOne({'foo':'password'})
+        self.assertEqual(authorizer.authorize(('foo', 'bar')), False)
+
+    def test_authorize_gooduser_goodpassword(self):
+        authorizer = self._makeOne({'foo':'password'})
+        self.assertEqual(authorizer.authorize(('foo', 'password')), True)
+    
+    def test_authorize_gooduser_badpassword_sha(self):
+        import sha
+        password = '{SHA}' + sha.new('password').hexdigest()
+        authorizer = self._makeOne({'foo':password})
+        self.assertEqual(authorizer.authorize(('foo', 'bar')), False)
+
+    def test_authorize_gooduser_goodpassword_sha(self):
+        import sha
+        password = '{SHA}' + sha.new('password').hexdigest()
+        authorizer = self._makeOne({'foo':password})
+        self.assertEqual(authorizer.authorize(('foo', 'password')), True)
+
+class SupervisorAuthHandlerTests(unittest.TestCase):
+    def _getTargetClass(self):
+        from supervisor.http import supervisor_auth_handler
+        return supervisor_auth_handler
+
+    def _makeOne(self, dict, handler):
+        return self._getTargetClass()(dict, handler)
+
+    def test_ctor(self):
+        handler = self._makeOne({'a':1}, None)
+        from supervisor.http import encrypted_dictionary_authorizer
+        self.assertEqual(handler.authorizer.__class__,
+                         encrypted_dictionary_authorizer)
+    
+
 class TopLevelFunctionTests(unittest.TestCase):
     def _make_http_servers(self, sconfigs):
         options = DummyOptions()
@@ -306,10 +352,11 @@ class TopLevelFunctionTests(unittest.TestCase):
                 'section':'unix_http_server'}
         servers = self._make_http_servers([inet, unix])
         self.assertEqual(len(servers), 2)
-        from medusa.auth_handler import auth_handler
+        from supervisor.http import supervisor_auth_handler
         for config, server in servers:
             for handler in server.handlers:
-                self.failUnless(isinstance(handler, auth_handler), handler)
+                self.failUnless(isinstance(handler, supervisor_auth_handler),
+                                handler)
 
 class DummyProducer:
     def __init__(self, *data):