kopia lustrzana https://github.com/Yakifo/amqtt
splitting out contrib plugin documentation into separate pages
rodzic
c518b9b63f
commit
f6ccdbc51c
|
@ -129,7 +129,9 @@ class Broker:
|
|||
plugin_namespace: plugin namespace to use when loading plugin entry_points. defaults to `amqtt.broker.plugins`.
|
||||
|
||||
Raises:
|
||||
BrokerError, ParserError, PluginError
|
||||
BrokerError: problem with broker configuration
|
||||
PluginImportError: if importing a plugin from configuration
|
||||
PluginInitError: if initialization plugin fails
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
@ -86,7 +86,8 @@ class MQTTClient:
|
|||
config: dictionary of configuration options (see [client configuration](client_config.md)).
|
||||
|
||||
Raises:
|
||||
PluginError
|
||||
PluginImportError: if importing a plugin from configuration
|
||||
PluginInitError: if initialization plugin fails
|
||||
|
||||
"""
|
||||
|
||||
|
@ -145,7 +146,7 @@ class MQTTClient:
|
|||
[CONNACK](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033)'s return code
|
||||
|
||||
Raises:
|
||||
ClientError, ConnectError
|
||||
ConnectError: could not connect to broker
|
||||
|
||||
"""
|
||||
additional_headers = additional_headers if additional_headers is not None else {}
|
||||
|
@ -380,6 +381,7 @@ class MQTTClient:
|
|||
|
||||
Raises:
|
||||
asyncio.TimeoutError: if timeout occurs before a message is delivered
|
||||
ClientError: if client is not connected
|
||||
|
||||
"""
|
||||
if self._handler is None:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import logging
|
||||
from dataclasses import dataclass, field, fields, replace
|
||||
import logging
|
||||
|
||||
try:
|
||||
from enum import Enum, StrEnum
|
||||
|
@ -9,15 +9,14 @@ except ImportError:
|
|||
class StrEnum(str, Enum): #type: ignore[no-redef]
|
||||
pass
|
||||
|
||||
from pathlib import Path
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Literal
|
||||
|
||||
from dacite import Config as DaciteConfig, from_dict as dict_to_dataclass
|
||||
|
||||
from amqtt.mqtt.constants import QOS_0, QOS_2
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import asyncio
|
||||
|
||||
|
@ -155,9 +154,9 @@ class BrokerConfig(Dictable):
|
|||
listeners: dict[Literal["default"] | str, ListenerConfig] = field(default_factory=default_listeners) # noqa: PYI051
|
||||
"""Network of listeners used by the services. a 'default' named listener is required; if another listener
|
||||
does not set a value, the 'default' settings are applied. See
|
||||
[ListenerConfig](./#amqtt.contexts.ListenerConfig) for more information."""
|
||||
[ListenerConfig](#amqtt.contexts.ListenerConfig) for more information."""
|
||||
sys_interval: int | None = None
|
||||
"""*Deprecated field to configure the `BrokerSysPlugin`. See [`BrokerSysPlugin`](../packaged_plugins.md/#sys-topics)
|
||||
"""*Deprecated field to configure the `BrokerSysPlugin`. See [`BrokerSysPlugin`](#sys-topics)
|
||||
for recommended configuration.*"""
|
||||
timeout_disconnect_delay: int | None = 0
|
||||
"""Client disconnect timeout without a keep-alive."""
|
||||
|
@ -165,8 +164,8 @@ class BrokerConfig(Dictable):
|
|||
"""Seconds for an inactive session to be retained."""
|
||||
auth: dict[str, Any] | None = None
|
||||
"""*Deprecated field used to config EntryPoint-loaded plugins. See
|
||||
[`AnonymousAuthPlugin`](./#anonymous-auth-plugin) and
|
||||
[`FileAuthPlugin`](/packaged_plugins/#password-file-auth-plugin) for recommended configuration.*"""
|
||||
[`AnonymousAuthPlugin`](#anonymous-auth-plugin) and
|
||||
[`FileAuthPlugin`](#password-file-auth-plugin) for recommended configuration.*"""
|
||||
topic_check: dict[str, Any] | None = None
|
||||
"""*Deprecated field used to config EntryPoint-loaded plugins. See
|
||||
[`TopicTabooPlugin`](#taboo-topic-plugin) and
|
||||
|
@ -328,7 +327,7 @@ class ClientConfig(Dictable):
|
|||
"""Specify the topics and what flags should be set for messages published to them."""
|
||||
broker: ConnectionConfig | None = field(default_factory=ConnectionConfig)
|
||||
"""Configuration for connecting to the broker. See
|
||||
[ConnectionConfig](./#amqtt.contexts.ConnectionConfig) for more information."""
|
||||
[ConnectionConfig](#amqtt.contexts.ConnectionConfig) for more information."""
|
||||
plugins: dict[str, Any] | list[dict[str, Any]] | None = field(default_factory=default_client_plugins)
|
||||
"""The dictionary has a key of the dotted-module path of a class derived from `BasePlugin`; the value is
|
||||
a dictionary of configuration options for that plugin. See [Plugins](http://localhost:8000/custom_plugins/)
|
||||
|
@ -337,7 +336,7 @@ class ClientConfig(Dictable):
|
|||
"""If establishing a secure connection, should the hostname of the certificate be verified."""
|
||||
will: WillConfig | None = None
|
||||
"""Message, topic and flags that should be sent to if the client disconnects. See
|
||||
[WillConfig](./#amqtt.contexts.WillConfig)"""
|
||||
[WillConfig](#amqtt.contexts.WillConfig)"""
|
||||
|
||||
def __post__init__(self) -> None:
|
||||
"""Check config for errors and transform fields for easier use."""
|
||||
|
|
|
@ -56,14 +56,15 @@ class UserAuthDBPlugin(BaseAuthPlugin, BaseTopicPlugin):
|
|||
|
||||
connection: str
|
||||
"""SQLAlchemy connection string for the asyncio version of the database connector:
|
||||
- mysql+aiomysql://user:password@host:port/dbname
|
||||
- postgresql+asyncpg://user:password@host:port/dbname
|
||||
- sqlite+aiosqlite:///dbfilename.db
|
||||
|
||||
- `mysql+aiomysql://user:password@host:port/dbname`
|
||||
- `postgresql+asyncpg://user:password@host:port/dbname`
|
||||
- `sqlite+aiosqlite:///dbfilename.db`
|
||||
"""
|
||||
sync_schema: bool = False
|
||||
"""Use SQLAlchemy to create / update the database schema."""
|
||||
hash_schemes: list[str] = field(default_factory=default_hash_scheme)
|
||||
|
||||
"""list of hash schemes to use for passwords"""
|
||||
|
||||
class TopicAuthDBPlugin(BaseTopicPlugin):
|
||||
|
||||
|
@ -100,9 +101,10 @@ class TopicAuthDBPlugin(BaseTopicPlugin):
|
|||
|
||||
connection: str
|
||||
"""SQLAlchemy connection string for the asyncio version of the database connector:
|
||||
- mysql+aiomysql://user:password@host:port/dbname
|
||||
- postgresql+asyncpg://user:password@host:port/dbname
|
||||
- sqlite+aiosqlite:///dbfilename.db
|
||||
|
||||
- `mysql+aiomysql://user:password@host:port/dbname`
|
||||
- `postgresql+asyncpg://user:password@host:port/dbname`
|
||||
- `sqlite+aiosqlite:///dbfilename.db`
|
||||
"""
|
||||
sync_schema: bool = False
|
||||
"""Use SQLAlchemy to create / update the database schema."""
|
||||
|
|
|
@ -48,7 +48,7 @@ HTTP_4xx_MIN = 400
|
|||
HTTP_4xx_MAX = 499
|
||||
|
||||
|
||||
class HttpAuthTopicPlugin(BaseAuthPlugin, BaseTopicPlugin):
|
||||
class HttpAuthPlugin(BaseAuthPlugin, BaseTopicPlugin):
|
||||
|
||||
def __init__(self, context: BrokerContext) -> None:
|
||||
super().__init__(context)
|
||||
|
@ -167,7 +167,9 @@ class HttpAuthTopicPlugin(BaseAuthPlugin, BaseTopicPlugin):
|
|||
"""
|
||||
|
||||
host: str
|
||||
"""hostname of the server for the auth & acl check"""
|
||||
port: int
|
||||
"""port of the server for the auth & acl check"""
|
||||
user_uri: str
|
||||
topic_uri: str
|
||||
request_method: RequestMethod = RequestMethod.GET
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# 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
|
||||
|
||||
Relational database access is supported using SQLAlchemy so MySQL, MariaDB, Postgres and SQLite support is available.
|
||||
|
||||
For ease of use, the [`user_mgr` command-line utility](auth_db.md/#user_mgr) to add, remove, update and
|
||||
list clients. And the [`topic_mgr` command-line utility](auth_db.md/#topic_mgr) to add client access to
|
||||
subscribe, publish and receive messages on topics.
|
||||
|
||||
## Authentication Configuration
|
||||
|
||||
::: amqtt.contrib.auth_db.UserAuthDBPlugin.Config
|
||||
options:
|
||||
heading_level: 4
|
||||
extra:
|
||||
class_style: "simple"
|
||||
|
||||
## Authorization Configuration
|
||||
|
||||
::: amqtt.contrib.auth_db.TopicAuthDBPlugin.Config
|
||||
options:
|
||||
heading_level: 4
|
||||
extra:
|
||||
class_style: "simple"
|
||||
|
||||
## CLI
|
||||
|
||||
|
||||
::: mkdocs-typer2
|
||||
:module: amqtt.contrib.auth_db.user_mgr_cli
|
||||
:name: user_mgr
|
||||
|
||||
|
||||
::: mkdocs-typer2
|
||||
:module: amqtt.contrib.auth_db.topic_mgr_cli
|
||||
:name: topic_mgr
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# Contributed Plugins
|
||||
|
||||
These are fully supported plugins but require additional dependencies to be installed:
|
||||
|
||||
`$ pip install '.[contrib]'`
|
||||
|
||||
|
||||
- Relational Database Auth<br/>
|
||||
_includes manager script to add, remove and create db entries_
|
||||
- [DB Client Authentication](auth_db.md)<br/>
|
||||
Authenticate a client's connection to broker based on entries in a relational db (mysql, postgres, maria, sqlite).<br/>
|
||||
`amqtt.contrib.auth_db.AuthUserDBPlugin`
|
||||
- [DB Client Authorization](auth_db.md)<br/>
|
||||
Determine a client's access to topics.<br/>
|
||||
`amqtt.contrib.auth_db.AuthTopicDBPlugin`
|
||||
|
||||
- [HTTP Auth](http.md)<br/>
|
||||
Determine client authentication and authorization based on response from a separate HTTP server.<br/>
|
||||
`amqtt.contrib.http.HttpAuthTopicPlugin`
|
|
@ -77,7 +77,9 @@ and then run via `amqtt -c myBroker.yaml`.
|
|||
variables to configure its behavior.
|
||||
|
||||
::: amqtt.plugins.base.BasePlugin
|
||||
|
||||
options:
|
||||
show_source: false
|
||||
heading_level: 3
|
||||
|
||||
|
||||
## Events
|
||||
|
@ -85,16 +87,13 @@ and then run via `amqtt -c myBroker.yaml`.
|
|||
|
||||
All plugins are notified of events if the `BasePlugin` subclass implements one or more of these methods:
|
||||
|
||||
### Client and Broker
|
||||
|
||||
### Client
|
||||
|
||||
- `async def on_mqtt_packet_sent(self, *, packet: MQTTPacket[MQTTVariableHeader, MQTTPayload[MQTTVariableHeader], MQTTFixedHeader], session: Session | None = None) -> None`
|
||||
- `async def on_mqtt_packet_received(self, *, packet: MQTTPacket[MQTTVariableHeader, MQTTPayload[MQTTVariableHeader], MQTTFixedHeader], session: Session | None = None) -> None`
|
||||
|
||||
### Client Only
|
||||
|
||||
none
|
||||
|
||||
### Broker Only
|
||||
### Broker
|
||||
|
||||
- `async def on_broker_pre_start(self) -> None`
|
||||
- `async def on_broker_post_start(self) -> None`
|
||||
|
@ -111,6 +110,9 @@ none
|
|||
- `async def on_broker_client_unsubscribed(self, *, client_id: str, topic: str) -> None`
|
||||
|
||||
- `async def on_broker_message_received(self, *, client_id: str, message: ApplicationMessage) -> None`
|
||||
- `async def on_broker_message_broadcast(self, *, client_id: str, message: ApplicationMessage) -> None`
|
||||
- `async def on_mqtt_packet_sent(self, *, packet: MQTTPacket[MQTTVariableHeader, MQTTPayload[MQTTVariableHeader], MQTTFixedHeader], session: Session | None = None) -> None`
|
||||
- `async def on_mqtt_packet_received(self, *, packet: MQTTPacket[MQTTVariableHeader, MQTTPayload[MQTTVariableHeader], MQTTFixedHeader], session: Session | None = None) -> None`
|
||||
|
||||
|
||||
## Authentication Plugins
|
||||
|
@ -128,6 +130,9 @@ If there are multiple authentication plugins:
|
|||
- `None` gets ignored from the determination
|
||||
|
||||
::: amqtt.plugins.base.BaseAuthPlugin
|
||||
options:
|
||||
show_source: false
|
||||
heading_level: 3
|
||||
|
||||
## Topic Filter Plugins
|
||||
|
||||
|
@ -144,7 +149,9 @@ If there are multiple topic plugins:
|
|||
- `None` will be ignored
|
||||
|
||||
::: amqtt.plugins.base.BaseTopicPlugin
|
||||
|
||||
options:
|
||||
show_source: false
|
||||
heading_level: 3
|
||||
|
||||
!!! note
|
||||
A custom plugin class can subclass from both `BaseAuthPlugin` and `BaseTopicPlugin` as long it defines
|
|
@ -1,40 +1,3 @@
|
|||
# Contributed Plugins
|
||||
|
||||
Plugins that are not part of the core functionality of the aMQTT broker or client and require additional dependencies:
|
||||
|
||||
`$ pip install '.[contrib]'`
|
||||
|
||||
# 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.AuthTopicDBPlugine` (authorization) determine a client's access to topics
|
||||
|
||||
Relational database access is supported using SQLAlchemy so MySQL, MariaDB, Postgres and SQLite support is available.
|
||||
|
||||
For ease of use, the [`user_mgr` command-line utility](contrib_plugins.md/#user_mgr) to add, remove, update and
|
||||
list clients. And the [`topic_mgr` command-line utility](contrib_plugins.md/#user_topic) to add client access to
|
||||
subscribe, publish and receive messages on topics.
|
||||
|
||||
## Authentication Configuration
|
||||
|
||||
::: amqtt.contrib.auth_db.UserAuthDBPlugin.Config
|
||||
|
||||
## Authorization Configuration
|
||||
|
||||
::: amqtt.contrib.auth_db.TopicAuthDBPlugin.Config
|
||||
|
||||
## Command line for authentication
|
||||
|
||||
::: mkdocs-typer2
|
||||
:module: amqtt.contrib.auth_db.user_mgr_cli
|
||||
:name: user_mgr
|
||||
|
||||
## Command line for authorization
|
||||
|
||||
::: mkdocs-typer2
|
||||
:module: amqtt.contrib.auth_db.topic_mgr_cli
|
||||
:name: topic_mgr
|
||||
|
||||
# Authentication & Topic Access via external HTTP server
|
||||
|
||||
`amqtt.contrib.http.HttpAuthTopicPlugin`
|
||||
|
@ -43,6 +6,11 @@ If clients accessing the broker are managed by another application, implement AP
|
|||
that allows the broker to check if a client is authenticated and what topics that client
|
||||
is authorized to access.
|
||||
|
||||
::: amqtt.contrib.http.HttpAuthPlugin.Config
|
||||
options:
|
||||
show_source: false
|
||||
heading_level: 4
|
||||
|
||||
**Configuration**
|
||||
|
||||
- `host` *(str) hostname of the server for the auth & acl check
|
|
@ -23,7 +23,7 @@ and configured for the broker:
|
|||
--8<-- "pyproject.toml:included"
|
||||
```
|
||||
|
||||
But the same 4 plugins were activated in the previous default config:
|
||||
But the previous default config only caused 4 plugins to be active:
|
||||
|
||||
```yaml
|
||||
--8<-- "samples/legacy.yaml"
|
||||
|
@ -248,7 +248,7 @@ plugins:
|
|||
|
||||
`amqtt.plugins.logging_amqtt.PacketLoggerPlugin`
|
||||
|
||||
This plugin issues debug-level messages for [mqtt events](custom_plugins.md#client-and-broker): `on_mqtt_packet_sent`
|
||||
This plugin issues debug-level messages for [mqtt events](custom_plugins.md#events): `on_mqtt_packet_sent`
|
||||
and `on_mqtt_packet_received`.
|
||||
|
||||
```yaml
|
|
@ -32,4 +32,5 @@ used by the included broker script: [broker configuration](broker_config.md)
|
|||
|
||||
::: amqtt.broker.Broker
|
||||
|
||||
|
||||
[^1]: See [PyYAML](http://pyyaml.org/wiki/PyYAMLDocumentation) for loading YAML files as Python dict.
|
||||
|
|
|
@ -10,11 +10,13 @@ from _griffe.agents.visitor import Visitor
|
|||
from _griffe.models import Attribute
|
||||
|
||||
from amqtt.contexts import default_listeners, default_broker_plugins, default_client_plugins
|
||||
from amqtt.contrib.auth_db.plugin import default_hash_scheme
|
||||
|
||||
default_factory_map = {
|
||||
'default_listeners': default_listeners(),
|
||||
'default_broker_plugins': default_broker_plugins(),
|
||||
'default_client_plugins': default_client_plugins()
|
||||
'default_client_plugins': default_client_plugins(),
|
||||
'default_hash_scheme': default_hash_scheme()
|
||||
}
|
||||
|
||||
def get_qualified_name(node: ast.AST) -> str | None:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
template overrides for mkdocs-materials
|
|
@ -38,9 +38,12 @@ nav:
|
|||
- Client: references/client.md
|
||||
- Common: references/common.md
|
||||
- Plugins:
|
||||
- Packaged: packaged_plugins.md
|
||||
- Custom: custom_plugins.md
|
||||
- Contributed: contrib_plugins.md
|
||||
- Packaged: plugins/packaged_plugins.md
|
||||
- Custom: plugins/custom_plugins.md
|
||||
- Contributed:
|
||||
- plugins/contrib.md
|
||||
- Database Auth: plugins/auth_db.md
|
||||
- HTTP Auth: plugins/http.md
|
||||
- Configuration:
|
||||
- Broker: references/broker_config.md
|
||||
- Client: references/client_config.md
|
||||
|
@ -58,6 +61,7 @@ theme:
|
|||
name: material
|
||||
logo: assets/amqtt_bw.svg
|
||||
features:
|
||||
- toc.integrate
|
||||
- announce.dismiss
|
||||
- content.action.edit
|
||||
- content.action.view
|
||||
|
@ -118,6 +122,7 @@ markdown_extensions:
|
|||
- pymdownx.tilde
|
||||
- toc:
|
||||
permalink: "¤"
|
||||
toc_depth: 3
|
||||
|
||||
plugins:
|
||||
- search
|
||||
|
@ -135,7 +140,6 @@ plugins:
|
|||
options:
|
||||
# extra:
|
||||
# template_log_display: true
|
||||
annotation_path: "full"
|
||||
docstring_options:
|
||||
ignore_init_summary: true
|
||||
docstring_section_style: list
|
||||
|
|
|
@ -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 HttpAuthTopicPlugin, ParamsMode, ResponseMode, RequestMethod
|
||||
from amqtt.contrib.http import HttpAuthPlugin, ParamsMode, ResponseMode, RequestMethod
|
||||
from amqtt.session import Session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -135,7 +135,7 @@ async def test_request_acl_response(empty_broker, http_server, kind, url,
|
|||
username, matcher, is_allowed):
|
||||
|
||||
context = BrokerContext(broker=empty_broker)
|
||||
context.config = HttpAuthTopicPlugin.Config(
|
||||
context.config = HttpAuthPlugin.Config(
|
||||
host="127.0.0.1",
|
||||
port=8080,
|
||||
user_uri=url,
|
||||
|
@ -144,7 +144,7 @@ async def test_request_acl_response(empty_broker, http_server, kind, url,
|
|||
params_mode=params_mode,
|
||||
response_mode=response_mode,
|
||||
)
|
||||
http_acl = HttpAuthTopicPlugin(context)
|
||||
http_acl = HttpAuthPlugin(context)
|
||||
logger.warning(f'kind is {kind}')
|
||||
if kind == TestKind.ACL:
|
||||
s = Session()
|
||||
|
|
Ładowanie…
Reference in New Issue