get_channel(settings, dry_run=False) -> Channel | None with: - enabled=False → None (no warning, no exception) - enabled=True + missing jid/password/to/host → redacted warning log, None - enabled=True + complete config → SyncXmppChannel instance - Factory never raises exceptions (D2 non-blocking degradation) - redact_secrets with extra_secrets=[password, jid, to] on warning logs Re-exports Channel, XmppChannel, SyncXmppChannel from channels package. 19 unit tests covering disabled, misconfigured, complete, dry-run, and secret-safe warning log scenarios. Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid> Co-authored-by: opencode/coder <coder@agents.invalid>
82 lines
3.3 KiB
Python
82 lines
3.3 KiB
Python
"""Fabrique de création des canaux de sortie du pipeline ``pronote-sync``.
|
|
|
|
Ce module expose la fonction :func:`get_channel` qui instancie le canal de
|
|
sortie XMPP à partir de sa configuration, ainsi que les types publics du
|
|
paquet ``pronote_sync.channels`` :
|
|
:class:`~pronote_sync.channels.protocol.Channel`,
|
|
:class:`~pronote_sync.channels.xmpp.XmppChannel` et
|
|
:class:`~pronote_sync.channels.xmpp.SyncXmppChannel`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from pronote_sync.channels.protocol import Channel
|
|
from pronote_sync.channels.xmpp import SyncXmppChannel, XmppChannel
|
|
from pronote_sync.config.settings import XmppSettings
|
|
from pronote_sync.utils.redaction import redact_secrets
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
__all__ = ["Channel", "XmppChannel", "SyncXmppChannel", "get_channel"]
|
|
|
|
|
|
def get_channel(settings: XmppSettings, dry_run: bool = False) -> Channel | None:
|
|
"""Instancie le canal de sortie XMPP selon la configuration (D2).
|
|
|
|
Si le canal est désactivé (``enabled`` à ``False``), la fabrique
|
|
retourne ``None`` sans avertissement ni exception. Si le canal est
|
|
activé mais que l'un des champs requis (``jid``, ``password``, ``to``,
|
|
``host``) est vide ou absent, un avertissement est journalisé puis
|
|
``None`` est retourné. Dans tous les autres cas, une instance de
|
|
:class:`~pronote_sync.channels.xmpp.SyncXmppChannel` est construite et
|
|
retournée.
|
|
|
|
L'avertissement est expurgé des valeurs sensibles (``jid``, mot de
|
|
passe, destinataire) via :func:`pronote_sync.utils.redaction.redact_secrets`
|
|
(SEC-XMPP-02) : le message journalisé ne contient jamais ces valeurs en
|
|
clair. La fabrique ne lève jamais d'exception (dégradation non bloquante).
|
|
|
|
:param settings: Paramètres de configuration du canal XMPP.
|
|
:param dry_run: Si ``True``, le canal est créé en mode simulation
|
|
(aucun envoi réseau lors de l'appel à ``send``).
|
|
:return: Canal de sortie prêt à l'emploi, ou ``None`` si le canal est
|
|
désactivé ou mal configuré.
|
|
:rtype: Channel | None
|
|
"""
|
|
if not settings.enabled:
|
|
return None
|
|
|
|
# SEC-XMPP-02 : valeurs sensibles à masquer dans le journal (les valeurs
|
|
# ``None`` sont ignorées).
|
|
extra_secrets = [
|
|
secret for secret in (settings.password, settings.jid, settings.to) if secret is not None
|
|
]
|
|
|
|
# SEC-XMPP-02 : rejeter aussi les chaînes vides ou composées uniquement
|
|
# d'espaces : ``bool(SecretStr)`` et ``bool(str)`` ne testent que la
|
|
# présence de l'objet, pas la valeur contenue.
|
|
missing_fields = [
|
|
name
|
|
for name, present in (
|
|
("jid", settings.jid is not None and bool(settings.jid.strip())),
|
|
(
|
|
"password",
|
|
settings.password is not None
|
|
and bool(settings.password.get_secret_value().strip()),
|
|
),
|
|
("to", settings.to is not None and bool(settings.to.strip())),
|
|
("host", bool(settings.host.strip())),
|
|
)
|
|
if not present
|
|
]
|
|
if missing_fields:
|
|
logger.warning(
|
|
"XMPP : configuration incomplète (champs manquants : %s), canal désactivé.",
|
|
redact_secrets(", ".join(missing_fields), extra_secrets=extra_secrets),
|
|
)
|
|
return None
|
|
|
|
return SyncXmppChannel(settings, dry_run=dry_run)
|