feat: authentification QR code / token pour Pronote
Ajoute le mode d'authentification PRONOTE_AUTH_MODE=qr_token comme alternative au mode password pour les instances Pronote utilisant HubEduConnect/EduConnect où l'authentification par mot de passe échoue (CAPTCHA, MFA, flux SAML). Nouveaux éléments : - PronoteSettings : auth_mode, qr_code_file, qr_pin (SecretStr) - PronoteAuthState : persistance du token rotatif dans .pronote_auth_state.json (écriture atomique, permissions 0600, symlink-safe via O_EXCL|O_NOFOLLOW) - PronoteClient._connect_qr_token() : token_login avec creds persistés, qrcode_login pour l'enrôlement initial, export_credentials persisté après chaque login réussi - PronoteAuthRotationError : levée en cas d'échec de rotation du token, propagée sans wrapping à travers PronoteFetcher et fetch_step jusqu'à PipelineRunner.run() qui notifie via XMPP (si canal disponible et dry_run inactif) - _is_pronotepy_configured() mode-aware : qr_token ne requiert que PRONOTE_URL - _collect_auth_secrets() : redaction des secrets explicites (token, PIN, jeton QR) dans tous les logs du chemin d'authentification Documentation : - .env.example : PRONOTE_AUTH_MODE, PRONOTE_QR_CODE_FILE, PRONOTE_QR_PIN - AGENTS.md : contrat d'authentification QR code / token - Wiki GuidePronote : section enrôlement, exécutions suivantes, ré-enrôlement Tests (686 passés, couverture 94.87%) : - 5 tests config QR, 9 tests auth_state, 10 tests client QR, 3 tests propagation, 4 tests intégration rotation end-to-end, 4 tests fallback mode-aware - Tests de non-fuite : sentinelles distinctes pour token, PIN, jeton QR Co-authored-by: coder/litellm/coder <coder@agents.invalid>
This commit is contained in:
@@ -42,6 +42,9 @@ class PronoteSettings(BaseSettings):
|
||||
agenda_source: Literal["auto", "ical", "pronotepy"] = "auto"
|
||||
homework_source: Literal["auto", "ical", "pronotepy"] = "auto"
|
||||
messages_source: Literal["pronotepy"] = "pronotepy"
|
||||
auth_mode: Literal["password", "qr_token"] = "password"
|
||||
qr_code_file: str | None = None
|
||||
qr_pin: SecretStr | None = None
|
||||
|
||||
@field_serializer("ical_url")
|
||||
def _serialize_ical_url(self, value: SecretStr | None) -> str | None:
|
||||
@@ -55,6 +58,18 @@ class PronoteSettings(BaseSettings):
|
||||
return None
|
||||
return "**********"
|
||||
|
||||
@field_serializer("qr_pin")
|
||||
def _serialize_qr_pin(self, value: SecretStr | None) -> str | None:
|
||||
"""Masque le code PIN QR lors de la sérialisation (repr, str, JSON).
|
||||
|
||||
:param value: Valeur du champ ``qr_pin``.
|
||||
:return: ``"**********"`` si la valeur est définie, ``None`` sinon.
|
||||
:rtype: str | None
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
return "**********"
|
||||
|
||||
|
||||
class CalDAVSettings(BaseSettings):
|
||||
"""Paramètres d'accès au serveur CalDAV de destination.
|
||||
@@ -266,8 +281,9 @@ class Settings(BaseSettings):
|
||||
"""Énumère tous les secrets configurés pour la rédaction.
|
||||
|
||||
Collecte les valeurs :class:`pydantic.SecretStr` non vides présentes
|
||||
dans les sous-configurations (Pronote, CalDAV, XMPP, IA). Les valeurs
|
||||
vides ou ``None`` sont filtrées ; les doublons sont supprimés.
|
||||
dans les sous-configurations (URL iCal, mots de passe, code PIN QR et
|
||||
clé API IA). Les valeurs vides ou ``None`` sont filtrées ; les
|
||||
doublons sont supprimés.
|
||||
|
||||
:return: Tuple de secrets à masquer dans les messages d'erreur.
|
||||
:rtype: tuple[SecretStr, ...]
|
||||
@@ -275,6 +291,7 @@ class Settings(BaseSettings):
|
||||
secrets = [
|
||||
self.pronote.ical_url,
|
||||
self.pronote.password,
|
||||
self.pronote.qr_pin,
|
||||
self.caldav.url,
|
||||
self.caldav.password,
|
||||
self.xmpp.password,
|
||||
|
||||
Reference in New Issue
Block a user