diff --git a/hbmqtt/broker.py b/hbmqtt/broker.py index fdc8013..45f441c 100644 --- a/hbmqtt/broker.py +++ b/hbmqtt/broker.py @@ -636,25 +636,26 @@ class Broker: def authenticate(self, session: Session, listener): """ This method call the authenticate method on registered plugins to test user authentication. - User is considered authenticated if at least one plugin returns True. + User is considered authenticated if all plugins called returns True. + Plugins authenticate() method are supposed to return : + - True if user is authentication succeed + - False if user authentication fails + - None if authentication can't be achieved (then plugin result is then ignored) :param session: :param listener: :return: """ returns = yield from self.plugins_manager.map_plugin_coro("authenticate", session=session) - if not returns: - self.logger.debug("Authentication plugin results: %r" % returns) - return True - else: - for res in returns: - if res: - # Consider authentication succeed if at least one plugin returns True - return True - # If all plugins returned False, authentications is failed - return True - - # TODO : Handle client authentication here - return True + auth_result = True + for plugin in returns: + res = returns[plugin] + if res is False: + auth_result = False + self.logger.debug("Authentication failed due to '%s' plugin result: %s" % (plugin.name, res)) + else: + self.logger.debug("'%s' plugin result: %s" % (plugin.name, res)) + # If all plugins returned True, authentication is success + return auth_result def retain_message(self, source_session, topic_name, data, qos=None): if data is not None and data != b'': diff --git a/hbmqtt/plugins/authentication.py b/hbmqtt/plugins/authentication.py index 88a1247..67256cc 100644 --- a/hbmqtt/plugins/authentication.py +++ b/hbmqtt/plugins/authentication.py @@ -2,6 +2,7 @@ # # See the file license.txt for copying permission. import logging +import asyncio from passlib.apps import custom_app_context as pwd_context @@ -25,6 +26,7 @@ class AnonymousAuthPlugin(BaseAuthPlugin): def __init__(self, context): super().__init__(context) + @asyncio.coroutine def authenticate(self, *args, **kwargs): authenticated = super().authenticate(*args, **kwargs) if authenticated: @@ -72,14 +74,18 @@ class FileAuthPlugin(BaseAuthPlugin): else: self.context.logger.debug("Configuration parameter 'password_file' not found") + @asyncio.coroutine def authenticate(self, *args, **kwargs): authenticated = super().authenticate(*args, **kwargs) if authenticated: session = kwargs.get('session', None) - hash = self._users.get(session.username, None) - if not hash: - authenticated = False - self.context.logger.debug("User '%s' unknown" % session.username) + if session.username: + hash = self._users.get(session.username, None) + if not hash: + authenticated = False + self.context.logger.debug("No hash found for user '%s'" % session.username) + else: + authenticated = pwd_context.verify(session.password, hash) else: - authenticated = pwd_context.verify(session.password, hash) + return None return authenticated diff --git a/samples/broker_start.py b/samples/broker_start.py index 2f5e1ef..5e44e6f 100644 --- a/samples/broker_start.py +++ b/samples/broker_start.py @@ -1,5 +1,6 @@ import logging import asyncio +import os from hbmqtt.broker import Broker logger = logging.getLogger(__name__) @@ -19,6 +20,11 @@ config = { }, }, 'sys_interval': 0, + 'auth': { + 'allow-anonymous': False, + 'password-file': os.path.join(os.path.dirname(os.path.realpath(__file__)), "passwd") + + } } broker = Broker(config) diff --git a/samples/client_publish.py b/samples/client_publish.py index 06f4f5a..af18aff 100644 --- a/samples/client_publish.py +++ b/samples/client_publish.py @@ -45,7 +45,7 @@ def test_coro(): @asyncio.coroutine def test_coro2(): try: - future = yield from C.connect('mqtt://localhost:1883/') + future = yield from C.connect('mqtt://test:test@localhost:1883/') future.add_done_callback(disconnected) yield from asyncio.wait([asyncio.async(C.publish('a/b', b'TEST MESSAGE WITH QOS_1', qos=0x01))]) logger.info("messages published") diff --git a/samples/passwd b/samples/passwd index e69de29..9a3971e 100644 --- a/samples/passwd +++ b/samples/passwd @@ -0,0 +1,2 @@ +# Test user with 'test' password encrypted with sha-512 +test:$6$l4zQEHEcowc1Pnv4$HHrh8xnsZoLItQ8BmpFHM4r6q5UqK3DnXp2GaTm5zp5buQ7NheY3Xt9f6godVKbEtA.hOC7IEDwnok3pbAOip. \ No newline at end of file diff --git a/setup.py b/setup.py index 3ca12d3..0e7e868 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ setup( # 'event_logger_plugin = hbmqtt.plugins.logging:EventLoggerPlugin', 'packet_logger_plugin = hbmqtt.plugins.logging:PacketLoggerPlugin', 'auth_anonymous = hbmqtt.plugins.authentication:AnonymousAuthPlugin', + 'auth_file = hbmqtt.plugins.authentication:FileAuthPlugin', ], 'hbmqtt.client.plugins': [ 'packet_logger_plugin = hbmqtt.plugins.logging:PacketLoggerPlugin', diff --git a/tests/plugins/test_authentication.py b/tests/plugins/test_authentication.py index b548149..6ca44e8 100644 --- a/tests/plugins/test_authentication.py +++ b/tests/plugins/test_authentication.py @@ -5,6 +5,7 @@ import unittest import logging import os +import asyncio from hbmqtt.plugins.manager import BaseContext from hbmqtt.plugins.authentication import AnonymousAuthPlugin, FileAuthPlugin from hbmqtt.session import Session @@ -14,6 +15,9 @@ logging.basicConfig(level=logging.DEBUG, format=formatter) class TestAnonymousAuthPlugin(unittest.TestCase): + def setUp(self): + self.loop = asyncio.new_event_loop() + def test_allow_anonymous(self): context = BaseContext() context.logger = logging.getLogger(__name__) @@ -25,7 +29,8 @@ class TestAnonymousAuthPlugin(unittest.TestCase): s = Session() s.username = "" auth_plugin = AnonymousAuthPlugin(context) - self.assertTrue(auth_plugin.authenticate()) + ret = self.loop.run_until_complete(auth_plugin.authenticate(session=s)) + self.assertTrue(ret) def test_disallow_anonymous(self): context = BaseContext() @@ -38,7 +43,8 @@ class TestAnonymousAuthPlugin(unittest.TestCase): s = Session() s.username = "" auth_plugin = AnonymousAuthPlugin(context) - self.assertFalse(auth_plugin.authenticate(session=s)) + ret = self.loop.run_until_complete(auth_plugin.authenticate(session=s)) + self.assertFalse(ret) def test_allow_nonanonymous(self): context = BaseContext() @@ -51,10 +57,14 @@ class TestAnonymousAuthPlugin(unittest.TestCase): s = Session() s.username = "test" auth_plugin = AnonymousAuthPlugin(context) - self.assertTrue(auth_plugin.authenticate(session=s)) + ret = self.loop.run_until_complete(auth_plugin.authenticate(session=s)) + self.assertTrue(ret) class TestFileAuthPlugin(unittest.TestCase): + def setUp(self): + self.loop = asyncio.new_event_loop() + def test_allow(self): context = BaseContext() context.logger = logging.getLogger(__name__) @@ -67,7 +77,8 @@ class TestFileAuthPlugin(unittest.TestCase): s.username = "user" s.password = "test" auth_plugin = FileAuthPlugin(context) - self.assertTrue(auth_plugin.authenticate(session=s)) + ret = self.loop.run_until_complete(auth_plugin.authenticate(session=s)) + self.assertTrue(ret) def test_wrong_password(self): context = BaseContext() @@ -81,7 +92,8 @@ class TestFileAuthPlugin(unittest.TestCase): s.username = "user" s.password = "wrong password" auth_plugin = FileAuthPlugin(context) - self.assertFalse(auth_plugin.authenticate(session=s)) + ret = self.loop.run_until_complete(auth_plugin.authenticate(session=s)) + self.assertFalse(ret) def test_unknown_password(self): context = BaseContext() @@ -95,4 +107,5 @@ class TestFileAuthPlugin(unittest.TestCase): s.username = "some user" s.password = "some password" auth_plugin = FileAuthPlugin(context) - self.assertFalse(auth_plugin.authenticate(session=s)) + ret = self.loop.run_until_complete(auth_plugin.authenticate(session=s)) + self.assertFalse(ret)