diff --git a/amqtt/contrib/auth_db/plugin.py b/amqtt/contrib/auth_db/plugin.py index d4fbe6b..51b665d 100644 --- a/amqtt/contrib/auth_db/plugin.py +++ b/amqtt/contrib/auth_db/plugin.py @@ -20,7 +20,7 @@ def default_hash_scheme() -> list[str]: return ["argon2", "bcrypt", "pbkdf2_sha256", "scrypt"] -class UserAuthDBPlugin(BaseAuthPlugin, BaseTopicPlugin): +class UserAuthDBPlugin(BaseAuthPlugin): def __init__(self, context: BrokerContext) -> None: super().__init__(context) diff --git a/amqtt/contrib/http.py b/amqtt/contrib/http.py index 87185b1..22d03ff 100644 --- a/amqtt/contrib/http.py +++ b/amqtt/contrib/http.py @@ -15,7 +15,7 @@ from aiohttp import ClientResponse, ClientSession, FormData from amqtt.broker import BrokerContext from amqtt.contexts import Action -from amqtt.plugins.base import BaseAuthPlugin, BaseTopicPlugin, BasePlugin +from amqtt.plugins.base import BaseAuthPlugin, BasePlugin, BaseTopicPlugin from amqtt.session import Session logger = logging.getLogger(__name__) @@ -72,7 +72,7 @@ class HttpConfig: """duration, in seconds, to wait for the HTTP server to respond""" -class HttpPlugin(BasePlugin): +class AuthHttpPlugin(BasePlugin[BrokerContext]): def __init__(self, context: BrokerContext) -> None: super().__init__(context) @@ -138,7 +138,7 @@ class HttpPlugin(BasePlugin): return f"{'https' if self.config.with_tls else 'http'}://{self.config.host}:{self.config.port}{uri}" -class HttpAuthPlugin(HttpPlugin, BaseAuthPlugin): +class UserAuthHttpPlugin(AuthHttpPlugin, BaseAuthPlugin): async def authenticate(self, *, session: Session) -> bool | None: d = {"username": session.username, "password": session.password, "client_id": session.client_id} @@ -152,7 +152,7 @@ class HttpAuthPlugin(HttpPlugin, BaseAuthPlugin): """URI of the auth check.""" -class HttpTopicPlugin(HttpPlugin, BaseTopicPlugin): +class TopicAuthHttpPlugin(AuthHttpPlugin, BaseTopicPlugin): async def topic_filtering(self, *, session: Session | None = None, @@ -175,5 +175,6 @@ class HttpTopicPlugin(HttpPlugin, BaseTopicPlugin): @dataclass class Config(HttpConfig): """Configuration for the HTTP Topic Plugin.""" + topic_uri: str = "/acl" """URI of the topic check.""" diff --git a/docs/plugins/auth_db.md b/docs/plugins/auth_db.md index ac996e3..b4620c1 100644 --- a/docs/plugins/auth_db.md +++ b/docs/plugins/auth_db.md @@ -1,7 +1,7 @@ # Relational Database for Authentication and Authorization -- `amqtt.contrib.auth_db.AuthUserDBPlugin` (authentication) verify a client's ability to connect to broker -- `amqtt.contrib.auth_db.AuthTopicDBPlugin` (authorization) determine a client's access to topics +- `amqtt.contrib.auth_db.UserAuthDBPlugin` (authentication) verify a client's ability to connect to broker +- `amqtt.contrib.auth_db.TopicAuthDBPlugin` (authorization) determine a client's access to topics Relational database access is supported using SQLAlchemy so MySQL, MariaDB, Postgres and SQLite support is available. diff --git a/docs/plugins/http.md b/docs/plugins/http.md index 6dc26d2..9ef639c 100644 --- a/docs/plugins/http.md +++ b/docs/plugins/http.md @@ -1,9 +1,16 @@ # Authentication & Authorization via external HTTP server -`amqtt.contrib.http.HttpAuthPlugin` - If clients accessing the broker are managed by another application, it can implement API endpoints -that respond with information about client authenticated and topic-level authorization. +that respond with information about client authentication and/or topic-level authorization. + +- `amqtt.contrib.http.UserAuthHttpPlugin` (client authentication) +- `amqtt.contrib.http.TopicAuthHttpPlugin` (topic authorization) + +Configuration of these plugins is identical (except for the uri name) so that they can be used independently, if desired. + +## User Auth + +See the [Request and Response Modes](#request-response-modes) section below for details on `params_mode` and `response_mode`. !!! info "browser-based mqtt over websockets" @@ -35,9 +42,18 @@ that respond with information about client authenticated and topic-level authori try { const clientMqtt = await mqtt.connect(url, options); -See the [Request and Response Modes](#request-response-modes) section below for details on `params_mode` and `response_mode`. +::: amqtt.contrib.http.UserAuthHttpPlugin.Config + options: + show_source: false + heading_level: 4 + extra: + class_style: "simple" -::: amqtt.contrib.http.HttpAuthPlugin.Config +## Topic ACL + +See the [Request and Response Modes](#request-response-modes) section below for details on `params_mode` and `response_mode`. + +::: amqtt.contrib.http.TopicAuthHttpPlugin.Config options: show_source: false heading_level: 4 @@ -57,10 +73,6 @@ format will depend on `params_mode` configuration attribute (`json` or `form`).: - password *(str)* - client_id *(str)* -*For superuser validation, the request will contain:* - - - username *(str)* - *For acl check, the request will contain:* - username *(str)* @@ -75,8 +87,8 @@ All endpoints should respond with the following, dependent on `response_mode` co - status code: 2xx (granted) or 4xx(denied) or 5xx (noop) !!! note "5xx response" - **noop** (no operation): plugin will not participate in the filtering operation and will defer to another - topic filtering plugin to determine access. if there is no other topic filtering plugin, access will be denied. + **noop** (no operation): plugin will not participate in the operation and will defer to another + plugin to determine access. if there is no other auth/filtering plugin, access will be denied. *In `json` mode:* @@ -87,8 +99,8 @@ All endpoints should respond with the following, dependent on `response_mode` co or { 'error': 'optional error message' } (noop) !!! note "excluded 'ok' key" - **noop** (no operation): plugin will not participate in the filtering operation and will defer to another - topic filtering plugin to determine access. if there is no other topic filtering plugin, access will be denied. + **noop** (no operation): plugin will not participate in the operation and will defer to another + plugin to determine access. if there is no other auth/filtering plugin, access will be denied. *In `text` mode:* diff --git a/tests/contrib/test_http.py b/tests/contrib/test_http.py index fc5bc2f..182ccf2 100644 --- a/tests/contrib/test_http.py +++ b/tests/contrib/test_http.py @@ -8,7 +8,7 @@ from aiohttp.web import Response from amqtt.broker import BrokerContext, Broker from amqtt.contexts import Action -from amqtt.contrib.http import HttpAuthPlugin, ParamsMode, ResponseMode, RequestMethod, HttpTopicPlugin +from amqtt.contrib.http import UserAuthHttpPlugin, TopicAuthHttpPlugin, ParamsMode, ResponseMode, RequestMethod from amqtt.session import Session logger = logging.getLogger(__name__) @@ -135,7 +135,7 @@ async def test_request_auth_response(empty_broker, http_server, kind, url, username, matcher, is_allowed): context = BrokerContext(broker=empty_broker) - context.config = HttpAuthPlugin.Config( + context.config = UserAuthHttpPlugin.Config( host="127.0.0.1", port=8080, user_uri=url, @@ -143,7 +143,7 @@ async def test_request_auth_response(empty_broker, http_server, kind, url, params_mode=params_mode, response_mode=response_mode, ) - http_acl = HttpAuthPlugin(context) + http_acl = UserAuthHttpPlugin(context) logger.warning(f'kind is {kind}') session = Session() @@ -162,7 +162,7 @@ async def test_request_topic_response(empty_broker, http_server, kind, url, request_method, params_mode, response_mode, username, matcher, is_allowed): context = BrokerContext(broker=empty_broker) - context.config = HttpTopicPlugin.Config( + context.config = TopicAuthHttpPlugin.Config( host="127.0.0.1", port=8080, topic_uri=url, @@ -170,7 +170,7 @@ async def test_request_topic_response(empty_broker, http_server, kind, url, params_mode=params_mode, response_mode=response_mode, ) - http_acl = HttpTopicPlugin(context) + http_acl = TopicAuthHttpPlugin(context) s = Session() s.username = username