From 03638986697e46738c924ce860299e2080ba40ed Mon Sep 17 00:00:00 2001 From: Antoine Van Elstraete Date: Tue, 8 Sep 2026 23:15:06 +0200 Subject: [PATCH 1/5] feat: authentification QR code / token pour Pronote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .env.example | 13 + .gitignore | 2 + AGENTS.md | 25 + pronote_sync/config/settings.py | 21 +- pronote_sync/errors.py | 17 + pronote_sync/pipeline/run.py | 45 +- pronote_sync/pipeline/steps/fetch.py | 6 +- pronote_sync/sources/pronote/auth_state.py | 195 +++++++ pronote_sync/sources/pronote/client.py | 270 +++++++++- pronote_sync/sources/pronote/fallback.py | 26 +- tests/integration/test_pipeline_runner.py | 393 +++++++++++++- tests/unit/test_config.py | 76 +++ tests/unit/test_fallback.py | 104 ++++ tests/unit/test_pronote_auth_state.py | 208 ++++++++ tests/unit/test_pronote_client.py | 586 +++++++++++++++++++++ tests/unit/test_rotation_propagation.py | 180 +++++++ 16 files changed, 2128 insertions(+), 39 deletions(-) create mode 100644 pronote_sync/sources/pronote/auth_state.py create mode 100644 tests/unit/test_pronote_auth_state.py create mode 100644 tests/unit/test_rotation_propagation.py diff --git a/.env.example b/.env.example index 12e19ed..6c35742 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,19 @@ PRONOTE_AGENDA_SOURCE=auto PRONOTE_HOMEWORK_SOURCE=auto PRONOTE_MESSAGES_SOURCE=pronotepy +# Mode d'authentification Pronote +# "password" (défaut) : authentification classique URL + identifiant + mot de passe +# "qr_token" : authentification par QR code puis token persistant +PRONOTE_AUTH_MODE=password + +# Fichier JSON du QR code Pronote (enrôlement initial, mode qr_token uniquement) +# À générer depuis l'application mobile Pronote. Le QR code expire ~10 minutes. +# PRONOTE_QR_CODE_FILE=/path/to/qr_code.json + +# PIN à 4 chiffres pour l'enrôlement QR code (mode qr_token uniquement) +# SENSIBLE : ne jamais committer cette valeur +# PRONOTE_QR_PIN=1234 + # --- CalDAV --- CALDAV_URL=https://caldav.example.com/calendars/user/pronote/ CALDAV_USERNAME=user@example.com diff --git a/.gitignore b/.gitignore index dd0d1cb..cab0981 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,8 @@ Thumbs.db # --- Project-specific state files --- .blog_rss_state.json .caldav_sync_state.json +# État d'authentification pronotepy (QR code / token rotation) +.pronote_auth_state.json *.state.json # --- Local scratch / WIP files --- diff --git a/AGENTS.md b/AGENTS.md index 757fd87..aae347a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,6 +146,31 @@ pronote-sync --dry-run - Réutiliser un téléchargement/parsing iCal pour l'agenda et les devoirs pendant un même run, sans cache global ni persistant. +### Contrat d'authentification QR code / token + +- Le mode d'authentification est sélectionné par `PRONOTE_AUTH_MODE` : + - `password` (défaut) : authentification classique via URL, identifiant, mot de passe et ENT. + - `qr_token` : authentification par QR code puis token persistant (pour les instances Pronote + utilisant HubEduConnect/EduConnect où l'authentification par mot de passe échoue). +- En mode `qr_token`, le premier login utilise `pronotepy.qrcode_login(qr_code, pin, uuid)` avec + les paramètres `PRONOTE_QR_CODE_FILE` (chemin du JSON QR) et `PRONOTE_QR_PIN` (PIN SecretStr). +- Après chaque login réussi, les credentials exportées par `pronotepy.export_credentials()` sont + persistées dans `.pronote_auth_state.json` (permissions `0600`, format JSON versionné, écriture + atomique). Le token rotate à chaque session — le fichier doit être mis à jour après chaque run. +- Les logins suivants utilisent `pronotepy.token_login(**credentials)` avec le token persisté. +- En cas d'échec de `token_login` (token expiré/invalide), une `PronoteAuthRotationError` est levée. + Cette erreur se propage sans wrapping à travers `PronoteFetcher` et `fetch_step` jusqu'à + `PipelineRunner.run()`, qui : + - journalise l'erreur (expurgée) ; + - envoie une notification XMPP actionnable si le canal est disponible et `dry_run` est inactif ; + - retourne un résultat dégradé `(None, errors)`. +- `PronoteAuthRotationError` est re-levée telle quelle (`except PronoteAuthRotationError: raise`) + dans toutes les couches d'enveloppement du chemin critique (fetch_agenda, fetch_homework, + fetch_step). Ne pas l'attraper avec `except Exception` sans la re-léver d'abord. +- Le fichier `.pronote_auth_state.json` ne doit jamais être committé (couvert par `.gitignore`). + Son contenu (token vivant) ne doit jamais apparaître dans les logs, les messages d'erreur ou + les notifications XMPP. + ### Contrat du provider `openai-compatible` - Le provider `openai-compatible` réutilise `OpenAISynthesisProvider` avec un `base_url` personnalisé ; aucun nouveau provider n'est créé. - `AI_BASE_URL` et `AI_MODEL` sont requis ; `AI_API_KEY` est requis (MVP). diff --git a/pronote_sync/config/settings.py b/pronote_sync/config/settings.py index aa39a64..cde7b24 100644 --- a/pronote_sync/config/settings.py +++ b/pronote_sync/config/settings.py @@ -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, diff --git a/pronote_sync/errors.py b/pronote_sync/errors.py index ee9e166..0fe1e74 100644 --- a/pronote_sync/errors.py +++ b/pronote_sync/errors.py @@ -21,6 +21,23 @@ class PronoteSyncError(Exception): self.message = message +class PronoteAuthRotationError(PronoteSyncError): + """Erreur de rotation du token d'authentification pronotepy (QR code / token). + + Levée quand le token persisté est invalide ou expiré et qu'un ré-enrôlement + manuel (suppression du fichier d'état + nouveau QR code) est nécessaire. + + :ivar message: Message décrivant l'action à effectuer, sans secret. + """ + + def __init__(self, message: str) -> None: + """Initialise l'erreur de rotation. + + :param message: Message actionnable sans secret (PIN, token, URL). + """ + super().__init__(message) + + class ErrorSeverity(StrEnum): """Niveau de gravité d'une erreur produite par le pipeline.""" diff --git a/pronote_sync/pipeline/run.py b/pronote_sync/pipeline/run.py index 6f4f228..86bba04 100644 --- a/pronote_sync/pipeline/run.py +++ b/pronote_sync/pipeline/run.py @@ -11,7 +11,12 @@ from typing import Protocol, runtime_checkable from pronote_sync.channels import get_channel from pronote_sync.channels.protocol import Channel from pronote_sync.config.settings import Settings -from pronote_sync.errors import PipelineCriticalError, PipelineError, PipelineWarning +from pronote_sync.errors import ( + PipelineCriticalError, + PipelineError, + PipelineWarning, + PronoteAuthRotationError, +) from pronote_sync.models.blog import ExternalInfo from pronote_sync.models.pronote import PronoteData from pronote_sync.models.sync import CalDAVSyncResult, CalDAVSyncStatus @@ -26,6 +31,7 @@ from pronote_sync.pipeline.steps.send import send_step from pronote_sync.pipeline.steps.synthesis import synthesis_step from pronote_sync.sources.blog.rss import BlogRSSClient from pronote_sync.sources.blog.state import BlogRSSState +from pronote_sync.sources.pronote.auth_state import PronoteAuthState from pronote_sync.sources.pronote.client import PronoteClient from pronote_sync.sources.pronote.fallback import PronoteFetcher, PronoteFetcherProtocol from pronote_sync.sources.theoretical import get_theoretical_provider @@ -133,7 +139,15 @@ class PipelineRunner: blog_state = BlogRSSState() if settings.blog.enabled else None return cls( settings=settings, - pronote_fetcher=PronoteFetcher(settings, PronoteClient(settings.pronote)), + pronote_fetcher=PronoteFetcher( + settings, + PronoteClient( + settings.pronote, + auth_state=( + PronoteAuthState() if settings.pronote.auth_mode == "qr_token" else None + ), + ), + ), agenda_comparator=comparator, synthesis_provider=get_synthesis_provider(settings.ai), channel=get_channel(settings.xmpp, dry_run=effective_dry_run), @@ -190,6 +204,11 @@ class PipelineRunner: des étapes facultatives sont converties en :class:`PipelineWarning` afin que les étapes suivantes, notamment XMPP, restent exécutées. + Une :class:`PronoteAuthRotationError` interrompt également l'exécution : + l'erreur est journalisée expurgée, une notification XMPP actionnable est + envoyée (sauf en dry-run ou sans canal), puis un résultat dégradé est + retourné. + :return: Données Pronote normalisées ou ``None``, puis erreurs et avertissements. :rtype: tuple[PronoteData | None, list[PipelineError]] """ @@ -271,6 +290,28 @@ class PipelineRunner: except Exception as exc: self._warn("send", self._redact(exc)) return data, [*self._errors, *self._warnings] + except PronoteAuthRotationError as exc: + error = PipelineCriticalError(self._redact(exc), step="pronote") + logger.error("Erreur critique du pipeline : %s", error.message) + if self._channel is not None and not self._dry_run: + message = XmppMessage( + target_date=now.date(), + synthesis=( + "⚠️ Rotation du token Pronote échouée. Le token d'authentification est " + "expiré ou invalide. Action requise : supprimez le fichier " + ".pronote_auth_state.json et relancez le pipeline avec un nouveau QR " + "code (PRONOTE_QR_CODE_FILE + PRONOTE_QR_PIN)." + ), + external_info=None, + ) + try: + if not send_step(self._channel, message): + self._warn("send", "Le canal XMPP a refusé l'envoi") + except Exception as send_exc: + # L'envoi de la notification est un dernier avertissement : son échec + # ne doit pas masquer l'erreur de rotation, déjà critique. + self._warn("send", self._redact(send_exc)) + self._errors.append(error) except PipelineCriticalError as exc: logger.error("Erreur critique du pipeline : %s", exc.message) self._errors.append(exc) diff --git a/pronote_sync/pipeline/steps/fetch.py b/pronote_sync/pipeline/steps/fetch.py index 842bd52..8c0f270 100644 --- a/pronote_sync/pipeline/steps/fetch.py +++ b/pronote_sync/pipeline/steps/fetch.py @@ -5,7 +5,7 @@ from __future__ import annotations from dataclasses import dataclass from datetime import date -from pronote_sync.errors import PipelineCriticalError, PipelineWarning +from pronote_sync.errors import PipelineCriticalError, PipelineWarning, PronoteAuthRotationError from pronote_sync.models.agenda import Lesson, SchoolEvent from pronote_sync.models.homework import Homework from pronote_sync.models.message import Message @@ -101,6 +101,8 @@ def fetch_step( :return: Données récupérées et avertissements non critiques. :rtype: tuple[FetchedPronoteData, list[PipelineWarning]] :raises PipelineCriticalError: Si l'agenda ou les devoirs ne sont pas disponibles. + :raises PronoteAuthRotationError: Si une rotation du token d'authentification + pronotepy est nécessaire : propagée telle quelle jusqu'au pipeline. """ try: lessons, school_events = fetcher.fetch_agenda() @@ -108,6 +110,8 @@ def fetch_step( homeworks = fetcher.fetch_homework(target_date) except PipelineCriticalError: raise + except PronoteAuthRotationError: + raise except Exception as exc: raise PipelineCriticalError( f"Récupération Pronote impossible : {redact_exception(exc)}", step="fetch" diff --git a/pronote_sync/sources/pronote/auth_state.py b/pronote_sync/sources/pronote/auth_state.py new file mode 100644 index 0000000..e70d9df --- /dev/null +++ b/pronote_sync/sources/pronote/auth_state.py @@ -0,0 +1,195 @@ +"""Persistance des credentials d'authentification par token pronotepy. + +Ce module fournit :class:`PronoteAuthState`, qui stocke et charge les credentials +d'authentification par QR code / token entre les exécutions du pipeline. Le token +pronotepy rotate à chaque session : le fichier d'état doit être mis à jour après +chaque login réussi via :meth:`PronoteAuthState.save`. + +Le fichier d'état est créé avec des permissions ``0600`` car il contient un token +d'authentification vivant. Son contenu n'est jamais journalisé. +""" + +from __future__ import annotations + +import json +import logging +import os +from pathlib import Path +from typing import Any + +from pronote_sync.errors import PronoteSyncError +from pronote_sync.utils.redaction import redact_exception, redact_secrets + +logger = logging.getLogger(__name__) + +_STATE_VERSION = 1 + + +class PronoteAuthState: + """Persiste les credentials d'authentification par token pronotepy entre + les exécutions du pipeline. + + Le fichier d'état contient un dict au format : + {"version": 1, "credentials": {"pronote_url": "...", "username": "...", "password": "", "uuid": "..."}} + + Les credentials sont le retour de pronotepy.Client.export_credentials(), utilisé tel quel + pour token_login(**credentials). Le token rotate à chaque session — le fichier doit être + mis à jour après chaque login réussi. + + :param state_file: Chemin du fichier d'état JSON (``str`` ou + :class:`~pathlib.Path`). ``".pronote_auth_state.json"`` par défaut. + """ + + def __init__(self, state_file: Path | str = ".pronote_auth_state.json") -> None: + """Initialise le gestionnaire d'état d'authentification Pronote. + + Le fichier d'état n'est pas créé à l'initialisation : il n'est écrit + qu'à la première sauvegarde réussie via :meth:`save`. + + :param state_file: Chemin du fichier d'état JSON (``str`` ou + :class:`~pathlib.Path`). ``".pronote_auth_state.json"`` par défaut. + """ + self._state_file = Path(state_file) + + def load(self) -> dict[str, str] | None: + """Charge les credentials d'authentification depuis le fichier d'état. + + Un fichier absent renvoie ``None`` (journalisé en debug). Un fichier + corrompu, une version absente ou non supportée, ou un champ + ``credentials`` invalide renvoient ``None`` avec un avertissement. + Le contenu des credentials n'est jamais journalisé. + + :return: Dict des credentials (``pronote_url``, ``username``, + ``password``, ``uuid``) prêt pour + ``pronotepy.Client.token_login(**credentials)``, ou ``None`` si + aucun état valide n'est disponible. + :rtype: dict[str, str] | None + """ + if not self._state_file.exists(): + logger.debug( + "Fichier d'état d'authentification Pronote %s absent, aucun token à charger.", + redact_secrets(str(self._state_file)), + ) + return None + try: + data: Any = json.loads(self._state_file.read_text(encoding="utf-8")) + except Exception as exc: + logger.warning( + "Impossible de charger le fichier d'état d'authentification Pronote %s : %s, " + "aucun token chargé.", + redact_secrets(str(self._state_file)), + redact_exception(exc), + ) + return None + if not isinstance(data, dict) or data.get("version") != _STATE_VERSION: + logger.warning( + "Fichier d'état d'authentification Pronote %s : version absente ou non supportée, " + "aucun token chargé.", + redact_secrets(str(self._state_file)), + ) + return None + credentials_data = data.get("credentials") + if not isinstance(credentials_data, dict): + logger.warning( + "Fichier d'état d'authentification Pronote %s : champ credentials absent ou invalide, " + "aucun token chargé.", + redact_secrets(str(self._state_file)), + ) + return None + credentials: dict[str, str] = {} + for key, value in credentials_data.items(): + if not isinstance(key, str) or not isinstance(value, str): + logger.warning( + "Fichier d'état d'authentification Pronote %s : champ credentials invalide, " + "aucun token chargé.", + redact_secrets(str(self._state_file)), + ) + return None + credentials[key] = value + return credentials + + def save(self, credentials: dict[str, str]) -> None: + """Sauvegarde les credentials dans le fichier d'état, de manière atomique. + + Le fichier contient ``{"version": 1, "credentials": ...}``. Le JSON est + d'abord écrit dans un fichier temporaire du même répertoire, créé avec + les permissions ``0600`` (lecture seule pour le propriétaire) dès son + ouverture via :func:`os.open` (avec ``O_EXCL`` et ``O_NOFOLLOW`` pour + résister aux attaques par lien symbolique), puis verrouillé via + :func:`os.fchmod` avant toute écriture ; le fichier temporaire remplace + ensuite atomiquement le fichier d'état via :func:`os.replace`. Un + éventuel fichier temporaire stale d'une exécution interrompue est + supprimé avant l'ouverture. Les credentials ne sont jamais journalisés. + + :param credentials: Dict des credentials pronotepy, tel que retourné + par ``pronotepy.Client.export_credentials()``. + :raises PronoteSyncError: Si l'écriture ou le remplacement du fichier + échoue. + """ + payload: dict[str, Any] = { + "version": _STATE_VERSION, + "credentials": credentials, + } + tmp_file = self._state_file.with_suffix(".tmp") + fd: int | None = None + try: + # Nettoie un éventuel fichier temporaire stale laissé par une exécution interrompue. + if tmp_file.exists(): + try: + tmp_file.unlink() + except OSError: + logger.debug( + "Impossible de supprimer le fichier temporaire stale %s, " + "l'ouverture en O_EXCL échouera.", + redact_secrets(str(tmp_file)), + ) + # O_EXCL empêche de créer par-dessus un fichier existant (attaque par lien + # symbolique) et O_NOFOLLOW refuse de suivre un lien symbolique. + fd = os.open( + str(tmp_file), + os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, + 0o600, + ) + # Verrouille les permissions en 0600 avant toute écriture, indépendamment de l'umask. + os.fchmod(fd, 0o600) + with os.fdopen(fd, "w", encoding="utf-8") as handle: + json.dump(payload, handle, indent=2) + os.replace(tmp_file, self._state_file) + except Exception as exc: + logger.error( + "Impossible d'écrire le fichier d'état d'authentification Pronote %s : %s.", + redact_secrets(str(self._state_file)), + redact_exception(exc), + ) + if fd is not None: + try: + os.close(fd) + except OSError: + pass + try: + tmp_file.unlink(missing_ok=True) + except Exception as cleanup_exc: + logger.debug( + "Nettoyage du fichier temporaire d'état d'authentification Pronote échoué : %s", + redact_exception(cleanup_exc), + ) + raise PronoteSyncError( + f"Impossible d'écrire le fichier d'état d'authentification Pronote " + f"{redact_secrets(str(self._state_file))}." + ) from None + + def clear(self) -> None: + """Supprime le fichier d'état d'authentification. + + Si le fichier n'existe pas, la méthode ne fait rien et aucune erreur + n'est levée. + + :raises OSError: Si la suppression du fichier existant échoue. + """ + if not self._state_file.exists(): + return + logger.debug( + "Suppression du fichier d'état d'authentification Pronote %s.", + redact_secrets(str(self._state_file)), + ) + self._state_file.unlink() diff --git a/pronote_sync/sources/pronote/client.py b/pronote_sync/sources/pronote/client.py index 297f5f1..1c2d024 100644 --- a/pronote_sync/sources/pronote/client.py +++ b/pronote_sync/sources/pronote/client.py @@ -10,19 +10,24 @@ des cours et des devoirs se propagent pour déclencher le repli iCal. from __future__ import annotations +import json import logging from datetime import date +from pathlib import Path from typing import Any, Protocol +from uuid import uuid4 import pronotepy import pronotepy.ent as pronotepy_ent import requests from pronote_sync.config.settings import PronoteSettings +from pronote_sync.errors import PronoteAuthRotationError from pronote_sync.models.agenda import Lesson, LessonStatus from pronote_sync.models.homework import Homework from pronote_sync.models.message import Message, MessageType -from pronote_sync.utils.redaction import redact_exception +from pronote_sync.sources.pronote.auth_state import PronoteAuthState +from pronote_sync.utils.redaction import redact_exception, redact_secrets from pronote_sync.utils.uid import generate_deterministic_uid, normalize_pronote_uid logger = logging.getLogger(__name__) @@ -91,6 +96,51 @@ def _resolve_ent(ent_name: str) -> Any: return resolver +def _collect_auth_secrets(client: PronoteClient) -> list[str]: + """Collecte toutes les valeurs sensibles d'authentification pour la redaction. + + Rassemble le mot de passe, le PIN QR, le contenu du fichier QR (jeton, + login, url) et les credentials persistés (token, username) afin de les + transmettre comme ``extra_secrets`` aux fonctions de masquage. Une valeur + vide ou ``None`` est ignorée. + + :param client: Le client Pronote dont on collecte les secrets. + :return: Liste des valeurs sensibles à expurger des logs. + :rtype: list[str] + """ + secrets: list[str] = [] + settings = client._settings + # Mot de passe + if settings.password is not None: + secrets.append(settings.password.get_secret_value()) + # PIN QR + if settings.qr_pin is not None: + secrets.append(settings.qr_pin.get_secret_value()) + # Contenu du fichier QR (jeton, login, url) + if settings.qr_code_file is not None: + try: + qr_path = Path(settings.qr_code_file) + qr_data: Any = json.loads(qr_path.read_text(encoding="utf-8")) + for key in ("jeton", "login", "url"): + val = qr_data.get(key) + if isinstance(val, str): + secrets.append(val) + except Exception as exc: + logger.debug( + "Impossible de lire le fichier QR %s : %s", + redact_secrets(settings.qr_code_file), + redact_exception(exc), + ) + # Credentials persistés (token, username du fichier d'état) + if client._auth_state is not None: + creds = client._auth_state.load() + if creds is not None: + for val in creds.values(): + if isinstance(val, str): + secrets.append(val) + return [s for s in secrets if s] + + class PronoteClientProtocol(Protocol): """Interface du client Pronote consommée par la logique de repli.""" @@ -143,24 +193,56 @@ class PronoteClient: exceptions se propager pour déclencher le repli iCal. """ - def __init__(self, settings: PronoteSettings) -> None: + def __init__( + self, + settings: PronoteSettings, + auth_state: PronoteAuthState | None = None, + ) -> None: """Initialise le client Pronote sans se connecter. - :param settings: Paramètres d'accès à Pronote (username, password, ent). + :param settings: Paramètres d'accès à Pronote (username, password, ent, + mode d'authentification, fichier QR et PIN). + :param auth_state: Gestionnaire de persistance du token + d'authentification (optionnel ; requis en mode ``qr_token`` pour + conserver le token entre les exécutions). """ self._settings: PronoteSettings = settings + self._auth_state: PronoteAuthState | None = auth_state self._client: pronotepy.Client | None = None def _connect(self) -> pronotepy.Client: """Crée et connecte le client ``pronotepy`` (connexion paresseuse). - Le client est créé une seule fois puis réutilisé pour les appels - suivants. Le nom d'ENT, s'il est configuré, est résolu via - :func:`_resolve_ent` ; en l'absence d'ENT, ``ent=None`` est transmis - à ``pronotepy`` pour une connexion directe. Le type de compte - (``student`` ou ``parent``) détermine la classe de client utilisée. - L'erreur de connexion est relancée sans journalisation, la méthode - publique appelante étant responsable de la journaliser. + En mode ``password``, utilise l'authentification classique (URL, + username, password, ENT). En mode ``qr_token``, utilise le token + persisté via :class:`PronoteAuthState`, ou procède à l'enrôlement + initial par QR code si aucun token n'est présent. + + :return: Le client ``pronotepy`` connecté. + :rtype: pronotepy.Client + :raises ValueError: Si les credentials requis sont manquants. + :raises PronoteAuthRotationError: Si le token persisté est invalide + (rotation requise) ou si l'enrôlement QR échoue. + :raises pronotepy.PronoteAPIError: Si la connexion échoue. + """ + if self._client is not None: + return self._client + + if self._settings.auth_mode == "qr_token": + self._client = self._connect_qr_token() + else: + self._client = self._connect_password() + return self._client + + def _connect_password(self) -> pronotepy.Client: + """Connecte le client ``pronotepy`` en mode ``password``. + + Le nom d'ENT, s'il est configuré, est résolu via :func:`_resolve_ent` ; + en l'absence d'ENT, ``ent=None`` est transmis à ``pronotepy`` pour une + connexion directe. Le type de compte (``student`` ou ``parent``) + détermine la classe de client utilisée. L'erreur de connexion est + relancée sans journalisation, la méthode publique appelante étant + responsable de la journaliser. :return: Le client ``pronotepy`` connecté. :rtype: pronotepy.Client @@ -168,27 +250,153 @@ class PronoteClient: est manquant, ou si l'ENT fourni est inconnu. :raises pronotepy.PronoteAPIError: Si la connexion à Pronote échoue. """ - if self._client is None: - url = self._settings.url - username = self._settings.username - password = self._settings.password - ent = self._settings.ent - if url is None or username is None or password is None: - raise ValueError("url, username et password sont requis pour pronotepy") - resolver = _resolve_ent(ent) if ent is not None else None - client_class: type[pronotepy.Client] = ( - pronotepy.ParentClient - if self._settings.account_type == "parent" - else pronotepy.Client - ) - self._client = client_class( - pronote_url=url, - username=username, - password=password.get_secret_value(), - ent=resolver, - ) + url = self._settings.url + username = self._settings.username + password = self._settings.password + ent = self._settings.ent + if url is None or username is None or password is None: + raise ValueError("url, username et password sont requis pour pronotepy") + resolver = _resolve_ent(ent) if ent is not None else None + client_class: type[pronotepy.Client] = ( + pronotepy.ParentClient if self._settings.account_type == "parent" else pronotepy.Client + ) + self._client = client_class( + pronote_url=url, + username=username, + password=password.get_secret_value(), + ent=resolver, + ) return self._client + def _connect_qr_token(self) -> pronotepy.Client: + """Connecte via token persisté ou enrôlement par QR code. + + En premier lieu, les credentials persistés (``pronote_url``, username, + ``password``/token, ``uuid``) sont rejoués via + ``pronotepy.Client.token_login`` si :class:`PronoteAuthState` est + disponible et fournit un état. En cas d'échec du login par token + (exception ou client non connecté), une :class:`PronoteAuthRotationError` + est levée immédiatement, sans repli vers l'enrôlement QR : la rotation + du token doit être déclenchée par l'opérateur. L'enrôlement par QR code + n'est tenté que lorsqu'aucun credential n'est persisté (premier login) ; + le nouveau token est ensuite persisté immédiatement. + + :return: Le client ``pronotepy`` connecté. + :rtype: pronotepy.Client + :raises PronoteAuthRotationError: Si le token persisté est invalide + (expiré ou refusé par Pronote), ou si l'enrôlement QR échoue + (fichier QR ou PIN manquant, fichier QR invalide ou expiré). + """ + client_class: type[pronotepy.Client] = ( + pronotepy.ParentClient if self._settings.account_type == "parent" else pronotepy.Client + ) + + # Login par token avec les credentials persistés + if self._auth_state is not None: + creds = self._auth_state.load() + if creds is not None: + try: + client = client_class.token_login(**creds) + if client.logged_in: + self._auth_state.save(client.export_credentials()) + return client + # logged_in est False — le token est invalide + raise PronoteAuthRotationError( + "Le token d'authentification Pronote est invalide (non connecté). " + "Action requise : supprimez le fichier .pronote_auth_state.json " + "et relancez avec un nouveau QR code." + ) from None + except PronoteAuthRotationError: + raise + except Exception as exc: + logger.error( + "Échec du login par token pronotepy : %s", + redact_exception(exc, extra_secrets=_collect_auth_secrets(self)), + ) + # Token expiré/invalide — pas de repli vers l'enrôlement QR + raise PronoteAuthRotationError( + "Le token d'authentification Pronote est expiré ou invalide. " + "Action requise : supprimez le fichier .pronote_auth_state.json " + "et relancez avec un nouveau QR code (PRONOTE_QR_CODE_FILE + " + "PRONOTE_QR_PIN)." + ) from None + + # Enrôlement : premier login via QR code (aucun credential persisté) + client = self._enroll_qr_code(client_class) + # Persister le token rotaté immédiatement + if self._auth_state is not None: + self._auth_state.save(client.export_credentials()) + return client + + def _enroll_qr_code(self, client_class: type[pronotepy.Client]) -> pronotepy.Client: + """Procède à l'enrôlement initial via QR code pronotepy. + + Le fichier QR JSON doit contenir les clés ``login``, ``jeton`` et + ``url``. Le PIN et le contenu du fichier ne sont jamais journalisés ; + les erreurs propagées sont expurgées. + + :param client_class: Classe de client pronotepy à utiliser. + :return: Le client ``pronotepy`` connecté après enrôlement. + :rtype: pronotepy.Client + :raises PronoteAuthRotationError: Si le fichier QR ou le PIN est + manquant, si le fichier QR est illisible ou incomplet, ou si le + login par QR code échoue (PIN invalide ou QR code expiré). + """ + qr_file = self._settings.qr_code_file + qr_pin = self._settings.qr_pin + + if qr_file is None or qr_pin is None: + raise PronoteAuthRotationError( + "Enrôlement QR requis : PRONOTE_QR_CODE_FILE et PRONOTE_QR_PIN sont " + "nécessaires pour le premier login en mode qr_token. Supprimez le " + "fichier .pronote_auth_state.json si présent et relancez avec un " + "QR code frais." + ) from None + + # Read and validate QR code JSON + try: + qr_path = Path(qr_file) + qr_data: Any = json.loads(qr_path.read_text(encoding="utf-8")) + except Exception as exc: + logger.error( + "Fichier QR invalide %s : %s", + redact_secrets(qr_file, extra_secrets=_collect_auth_secrets(self)), + redact_exception(exc, extra_secrets=_collect_auth_secrets(self)), + ) + raise PronoteAuthRotationError( + "Impossible de lire le fichier QR code : " + f"{redact_secrets(qr_file, extra_secrets=_collect_auth_secrets(self))}" + ) from None + + # Validate required keys + for key in ("login", "jeton", "url"): + if key not in qr_data: + raise PronoteAuthRotationError( + f"Le fichier QR code ne contient pas la clé requise : {key}" + ) from None + + pin_value = qr_pin.get_secret_value() + app_uuid = f"pronote-sync-{uuid4().hex}" + + try: + client = client_class.qrcode_login( + qr_code=qr_data, + pin=pin_value, + uuid=app_uuid, + ) + except Exception as exc: + logger.error( + "Échec de l'enrôlement QR : %s", + redact_exception(exc, extra_secrets=_collect_auth_secrets(self)), + ) + raise PronoteAuthRotationError( + "Échec de l'enrôlement par QR code : PIN invalide ou QR code expiré. " + "Générez un nouveau QR code dans l'application Pronote et mettez à " + "jour PRONOTE_QR_CODE_FILE." + ) from None + + return client + def get_messages(self) -> list[Message]: """Récupère les messages des discussions Pronote. @@ -284,6 +492,8 @@ class PronoteClient: :param end: Date de fin de la fenêtre (incluse). :return: Liste des cours. :rtype: list[Lesson] + :raises PronoteAuthRotationError: Si le token persisté est invalide et + qu'aucun ré-enrôlement n'est possible (fichier QR ou PIN manquant). :raises pronotepy.PronoteAPIError: Si l'API Pronote échoue. :raises ValueError: Si la configuration ou l'ENT est invalide. :raises requests.RequestException: Si une requête réseau échoue. @@ -334,6 +544,8 @@ class PronoteClient: :param end: Date de fin de la fenêtre (incluse). :return: Liste des devoirs. :rtype: list[Homework] + :raises PronoteAuthRotationError: Si le token persisté est invalide et + qu'aucun ré-enrôlement n'est possible (fichier QR ou PIN manquant). :raises pronotepy.PronoteAPIError: Si l'API Pronote échoue. :raises ValueError: Si la configuration ou l'ENT est invalide. :raises requests.RequestException: Si une requête réseau échoue. diff --git a/pronote_sync/sources/pronote/fallback.py b/pronote_sync/sources/pronote/fallback.py index 39ef545..257ff8c 100644 --- a/pronote_sync/sources/pronote/fallback.py +++ b/pronote_sync/sources/pronote/fallback.py @@ -23,7 +23,7 @@ from enum import StrEnum from typing import Literal, Protocol from pronote_sync.config.settings import Settings -from pronote_sync.errors import PipelineCriticalError +from pronote_sync.errors import PipelineCriticalError, PronoteAuthRotationError from pronote_sync.models.agenda import Lesson, SchoolEvent from pronote_sync.models.homework import Homework from pronote_sync.models.message import Message @@ -147,13 +147,19 @@ class PronoteFetcher: return self._settings.pronote.ical_url is not None def _is_pronotepy_configured(self) -> bool: - """Vérifie que la source pronotepy est entièrement configurée. + """Vérifie si la source pronotepy est utilisable selon le mode d'authentification. - :return: ``True`` si ``url``, ``username`` et ``password`` - sont tous définis, ``False`` sinon. + :return: ``True`` si pronotepy est configuré pour le mode + d'authentification actif, ``False`` sinon. :rtype: bool """ pronote = self._settings.pronote + if pronote.auth_mode == "qr_token": + # En mode qr_token, seul PRONOTE_URL est requis. + # Le QR code et le PIN ne sont nécessaires que pour l'enrôlement initial. + # Les exécutions suivantes utilisent le token persisté. + return pronote.url is not None + # En mode password, URL + identifiant + mot de passe sont requis. return ( pronote.url is not None and pronote.username is not None @@ -249,10 +255,14 @@ class PronoteFetcher: :return: Tuple ``(cours, événements scolaires)``. :rtype: tuple[list[Lesson], list[SchoolEvent]] :raises PipelineCriticalError: Si toutes les sources tentées échouent. + :raises PronoteAuthRotationError: Si une rotation du token d'authentification + pronotepy est nécessaire : propagée telle quelle, sans repli. """ primary, fallback = self._agenda_sources() try: return self._fetch_agenda_source(primary) + except PronoteAuthRotationError: + raise except Exception as exc: logger.error( "Échec de la récupération %s pour l'agenda : %s", @@ -266,6 +276,8 @@ class PronoteFetcher: logger.info("Repli sur %s pour l'agenda.", fallback) try: lessons, school_events = self._fetch_agenda_source(fallback) + except PronoteAuthRotationError: + raise except Exception as exc: logger.error( "Échec de la récupération %s pour l'agenda : %s", @@ -370,10 +382,14 @@ class PronoteFetcher: :return: Liste des devoirs. :rtype: list[Homework] :raises PipelineCriticalError: Si toutes les sources tentées échouent. + :raises PronoteAuthRotationError: Si une rotation du token d'authentification + pronotepy est nécessaire : propagée telle quelle, sans repli. """ primary, fallback = self._homework_sources() try: return self._fetch_homework_source(primary, target_date) + except PronoteAuthRotationError: + raise except Exception as exc: logger.error( "Échec de la récupération %s pour les devoirs : %s", @@ -387,6 +403,8 @@ class PronoteFetcher: logger.info("Repli sur %s pour les devoirs.", fallback) try: homeworks = self._fetch_homework_source(fallback, target_date) + except PronoteAuthRotationError: + raise except Exception as exc: logger.error( "Échec de la récupération %s pour les devoirs : %s", diff --git a/tests/integration/test_pipeline_runner.py b/tests/integration/test_pipeline_runner.py index 46feffc..85d77d2 100644 --- a/tests/integration/test_pipeline_runner.py +++ b/tests/integration/test_pipeline_runner.py @@ -9,17 +9,19 @@ import pytest from pydantic import SecretStr from pronote_sync.config.settings import AISettings, AppSettings, PronoteSettings, Settings -from pronote_sync.errors import PipelineCriticalError, PipelineWarning +from pronote_sync.errors import PipelineCriticalError, PipelineWarning, PronoteAuthRotationError from pronote_sync.models.agenda import Lesson, LessonStatus, SchoolEvent from pronote_sync.models.blog import BlogArticle from pronote_sync.models.diff import AgendaDiff from pronote_sync.models.homework import Homework from pronote_sync.models.message import Message from pronote_sync.models.sync import CalDAVSyncResult, CalDAVSyncStatus +from pronote_sync.models.xmpp import XmppMessage from pronote_sync.pipeline.run import PipelineRunner from pronote_sync.sources.blog.result import BlogRSSFetchResult from pronote_sync.sources.blog.rss import BlogRSSClient from pronote_sync.sources.blog.state import BlogRSSState +from pronote_sync.sources.pronote.auth_state import PronoteAuthState from pronote_sync.sources.pronote.fallback import PronoteFetcher from pronote_sync.sync.diff import AgendaComparator @@ -393,6 +395,60 @@ def test_from_settings_with_theoretical_agenda_instantiates_comparator( assert isinstance(runner._agenda_comparator, RecordingComparator) +def test_from_settings_password_mode_passes_auth_state_none( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Password mode (default) constructs PronoteClient with auth_state=None.""" + import pronote_sync.pipeline.run as run_module + + constructed: list[tuple[object, object]] = [] + + class RecordingClient: + """PronoteClient constructor recording the supplied auth_state.""" + + def __init__(self, settings: PronoteSettings, *, auth_state: object) -> None: + """Record the constructor arguments used by the composition root. + + :param settings: Pronote settings supplied by the composition root. + :param auth_state: Auth state handler supplied by the composition root. + """ + constructed.append((settings, auth_state)) + + monkeypatch.setattr(run_module, "PronoteClient", RecordingClient) + + PipelineRunner.from_settings(Settings()) + + assert len(constructed) == 1 + assert constructed[0][1] is None + + +def test_from_settings_qr_token_mode_passes_auth_state_instance( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """QR-token mode constructs PronoteClient with a PronoteAuthState instance.""" + import pronote_sync.pipeline.run as run_module + + constructed: list[tuple[object, object]] = [] + + class RecordingClient: + """PronoteClient constructor recording the supplied auth_state.""" + + def __init__(self, settings: PronoteSettings, *, auth_state: object) -> None: + """Record the constructor arguments used by the composition root. + + :param settings: Pronote settings supplied by the composition root. + :param auth_state: Auth state handler supplied by the composition root. + """ + constructed.append((settings, auth_state)) + + monkeypatch.setattr(run_module, "PronoteClient", RecordingClient) + + PipelineRunner.from_settings(Settings(pronote=PronoteSettings(auth_mode="qr_token"))) + + assert len(constructed) == 1 + assert isinstance(constructed[0][1], PronoteAuthState) + + def test_runner_reuses_ical_download_and_parse_within_one_run( monkeypatch: pytest.MonkeyPatch, pipeline_inputs: tuple[Lesson, Homework], @@ -1063,3 +1119,338 @@ def test_runner_ical_cache_cleanup_on_second_run( "https://pronote.example.test/calendar.ics", "https://pronote.example.test/calendar.ics", ] + + +def test_rotation_error_sends_xmpp_notification() -> None: + """Une PronoteAuthRotationError envoie une notification XMPP puis retourne un résultat dégradé. + + Ce test vérifie que l'erreur de rotation se propage à travers le pipeline réel + (PronoteFetcher → fetch_step → PipelineRunner.run) et déclenche une notification XMPP + avec un message actionnable. + """ + calls: list[str] = [] + channel = StubChannel(calls) + + # Créer un client Pronote qui lève PronoteAuthRotationError + class RotatingPronoteClient: + """Client Pronote qui simule une erreur de rotation de token.""" + + def get_lessons(self, start: date, end: date) -> list[Lesson]: + """Lève l'erreur de rotation lors de la récupération des cours. + + :param start: Début de la fenêtre (ignoré). + :param end: Fin de la fenêtre (ignoré). + :return: Ne retourne jamais. + :raises PronoteAuthRotationError: Toujours. + """ + del start, end + raise PronoteAuthRotationError("Token persisté expiré : ré-enrôlement requis") + + def get_homeworks(self, start: date, end: date) -> list[Homework]: + """Ne devrait pas être appelé si fetch_agenda échoue. + + :param start: Début de la fenêtre (ignoré). + :param end: Fin de la fenêtre (ignoré). + :return: Liste vide. + :rtype: list[Homework] + """ + del start, end + return [] + + def get_messages(self) -> list[Message]: + """Ne devrait pas être appelé si fetch_agenda échoue. + + :return: Liste vide. + :rtype: list[Message] + """ + return [] + + def get_informations(self) -> list[Message]: + """Ne devrait pas être appelé si fetch_agenda échoue. + + :return: Liste vide. + :rtype: list[Message] + """ + return [] + + settings = Settings( + pronote=PronoteSettings( + url="https://pronote.example.com", + username="test", + password=SecretStr("test_password"), + ent="bordeaux", + account_type="parent", + agenda_source="pronotepy", + homework_source="pronotepy", + ) + ) + + runner = PipelineRunner( + settings=settings, + pronote_fetcher=PronoteFetcher(settings, RotatingPronoteClient()), + channel=channel, + now_provider=lambda: datetime(2026, 9, 8, 7, 0), + ) + + data, errors = runner.run() + + assert data is None + assert len(errors) == 1 + assert isinstance(errors[0], PipelineCriticalError) + assert len(channel.messages) == 1 + message = channel.messages[0] + assert isinstance(message, XmppMessage) + assert message.target_date == date(2026, 9, 8) + assert message.synthesis is not None + assert "Rotation" in message.synthesis + assert "token" in message.synthesis + assert "QR code" in message.synthesis + + +def test_rotation_error_no_channel_no_xmpp_send() -> None: + """Sans canal XMPP, l'erreur de rotation ne tente aucun envoi. + + Ce test vérifie que même sans canal XMPP configuré, l'erreur de rotation + est correctement capturée et retournée dans la liste des erreurs. + """ + + class RotatingPronoteClient: + """Client Pronote qui simule une erreur de rotation de token.""" + + def get_lessons(self, start: date, end: date) -> list[Lesson]: + """Lève l'erreur de rotation lors de la récupération des cours. + + :param start: Début de la fenêtre (ignoré). + :param end: Fin de la fenêtre (ignoré). + :return: Ne retourne jamais. + :raises PronoteAuthRotationError: Toujours. + """ + del start, end + raise PronoteAuthRotationError("Token persisté expiré : ré-enrôlement requis") + + def get_homeworks(self, start: date, end: date) -> list[Homework]: + """Ne devrait pas être appelé. + + :param start: Début de la fenêtre (ignoré). + :param end: Fin de la fenêtre (ignoré). + :return: Liste vide. + :rtype: list[Homework] + """ + del start, end + return [] + + def get_messages(self) -> list[Message]: + """Ne devrait pas être appelé. + + :return: Liste vide. + :rtype: list[Message] + """ + return [] + + def get_informations(self) -> list[Message]: + """Ne devrait pas être appelé. + + :return: Liste vide. + :rtype: list[Message] + """ + return [] + + settings = Settings( + pronote=PronoteSettings( + url="https://pronote.example.com", + username="test", + password=SecretStr("test_password"), + ent="bordeaux", + account_type="parent", + agenda_source="pronotepy", + homework_source="pronotepy", + ) + ) + + runner = PipelineRunner( + settings=settings, + pronote_fetcher=PronoteFetcher(settings, RotatingPronoteClient()), + channel=None, + now_provider=lambda: datetime(2026, 9, 8, 7, 0), + ) + + data, errors = runner.run() + + assert data is None + assert len(errors) == 1 + assert isinstance(errors[0], PipelineCriticalError) + + +def test_rotation_error_dry_run_no_xmpp_send() -> None: + """En dry-run, l'erreur de rotation n'envoie aucune notification XMPP. + + Ce test vérifie que même en mode dry-run, l'erreur de rotation est correctement + capturée et retournée, mais aucune notification XMPP n'est envoyée. + """ + + class RotatingPronoteClient: + """Client Pronote qui simule une erreur de rotation de token.""" + + def get_lessons(self, start: date, end: date) -> list[Lesson]: + """Lève l'erreur de rotation lors de la récupération des cours. + + :param start: Début de la fenêtre (ignoré). + :param end: Fin de la fenêtre (ignoré). + :return: Ne retourne jamais. + :raises PronoteAuthRotationError: Toujours. + """ + del start, end + raise PronoteAuthRotationError("Token persisté expiré : ré-enrôlement requis") + + def get_homeworks(self, start: date, end: date) -> list[Homework]: + """Ne devrait pas être appelé. + + :param start: Début de la fenêtre (ignoré). + :param end: Fin de la fenêtre (ignoré). + :return: Liste vide. + :rtype: list[Homework] + """ + del start, end + return [] + + def get_messages(self) -> list[Message]: + """Ne devrait pas être appelé. + + :return: Liste vide. + :rtype: list[Message] + """ + return [] + + def get_informations(self) -> list[Message]: + """Ne devrait pas être appelé. + + :return: Liste vide. + :rtype: list[Message] + """ + return [] + + calls: list[str] = [] + channel = StubChannel(calls) + + settings = Settings( + pronote=PronoteSettings( + url="https://pronote.example.com", + username="test", + password=SecretStr("test_password"), + ent="bordeaux", + account_type="parent", + agenda_source="pronotepy", + homework_source="pronotepy", + messages_source="pronotepy", + auth_mode="password", + qr_code_file=None, + qr_pin=None, + ical_url=None, + ) + ) + + runner = PipelineRunner( + settings=settings, + pronote_fetcher=PronoteFetcher(settings, RotatingPronoteClient()), + channel=channel, + dry_run=True, + now_provider=lambda: datetime(2026, 9, 8, 7, 0), + ) + + data, errors = runner.run() + + assert data is None + assert len(errors) == 1 + assert isinstance(errors[0], PipelineCriticalError) + assert channel.messages == [] + assert "send" not in calls + + +def test_no_secrets_in_xmpp_message() -> None: + """La synthèse XMPP de rotation ne contient aucun secret (token, PIN, URL). + + Ce test vérifie que le message XMPP généré pour une erreur de rotation + ne contient aucun secret sensible, même si l'erreur originale en contenait. + """ + + class RotatingPronoteClient: + """Client Pronote qui simule une erreur de rotation avec secrets dans message.""" + + def get_lessons(self, start: date, end: date) -> list[Lesson]: + """Lève l'erreur de rotation avec message contenant des secrets. + + :param start: Début de la fenêtre (ignoré). + :param end: Fin de la fenêtre (ignoré). + :return: Ne retourne jamais. + :raises PronoteAuthRotationError: Toujours, avec des secrets dans le message. + """ + del start, end + raise PronoteAuthRotationError( + "Token sk-sentinel-token-987654 invalide et PIN 000000 pour " + "https://pronote.sentinel.example/icalsecurise" + ) + + def get_homeworks(self, start: date, end: date) -> list[Homework]: + """Ne devrait pas être appelé. + + :param start: Début de la fenêtre (ignoré). + :param end: Fin de la fenêtre (ignoré). + :return: Liste vide. + :rtype: list[Homework] + """ + del start, end + return [] + + def get_messages(self) -> list[Message]: + """Ne devrait pas être appelé. + + :return: Liste vide. + :rtype: list[Message] + """ + return [] + + def get_informations(self) -> list[Message]: + """Ne devrait pas être appelé. + + :return: Liste vide. + :rtype: list[Message] + """ + return [] + + channel = StubChannel([]) + + settings = Settings( + pronote=PronoteSettings( + url="https://pronote.example.com", + username="test", + password=SecretStr("test_password"), + ent="bordeaux", + account_type="parent", + agenda_source="pronotepy", + homework_source="pronotepy", + ) + ) + + runner = PipelineRunner( + settings=settings, + pronote_fetcher=PronoteFetcher(settings, RotatingPronoteClient()), + channel=channel, + now_provider=lambda: datetime(2026, 9, 8, 7, 0), + ) + + data, errors = runner.run() + + assert data is None + assert len(errors) == 1 + assert len(channel.messages) == 1 + message = channel.messages[0] + assert isinstance(message, XmppMessage) + assert message.synthesis is not None + # Vérifier que les secrets ne sont pas dans le message final + assert "sk-sentinel-token-987654" not in message.synthesis + assert "000000" not in message.synthesis + assert "pronote.sentinel.example" not in message.synthesis + # Vérifier que le message contient les instructions actionnables + assert ".pronote_auth_state.json" in message.synthesis + assert "PRONOTE_QR_CODE_FILE" in message.synthesis + assert "PRONOTE_QR_PIN" in message.synthesis diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index d5ea40e..81efb2d 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -131,4 +131,80 @@ def test_url_from_pronote_url_env_var(monkeypatch: MonkeyPatch) -> None: assert settings.pronote.url == test_url +def test_auth_mode_default_password() -> None: + """Vérifie que ``auth_mode`` vaut ``"password"`` par défaut. + + :return: None + """ + settings = PronoteSettings() + assert settings.auth_mode == "password" + + +def test_auth_mode_env_qr_token(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``PRONOTE_AUTH_MODE=qr_token`` est chargé correctement. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("PRONOTE_AUTH_MODE", "qr_token") + settings = load_settings() + assert settings.pronote.auth_mode == "qr_token" + + +def test_qr_pin_loaded_as_secretstr_and_masked(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``PRONOTE_QR_PIN`` est chargé en ``SecretStr`` et masqué. + + Le PIN ne doit apparaître nulle part dans les représentations textuelles + (str, repr, JSON) : seul le masque ``**********`` est visible. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("PRONOTE_QR_PIN", "123456") + settings = load_settings() + assert isinstance(settings.pronote.qr_pin, SecretStr) + assert settings.pronote.qr_pin.get_secret_value() == "123456" + + str_repr = str(settings) + assert "123456" not in str_repr + assert "**********" in str_repr + + repr_repr = repr(settings) + assert "123456" not in repr_repr + assert "**********" in repr_repr + + json_str = settings.model_dump_json() + assert "123456" not in json_str + assert "**********" in json_str + + +def test_qr_code_file_loaded_as_plain_string(monkeypatch: MonkeyPatch) -> None: + """Vérifie que ``PRONOTE_QR_CODE_FILE`` est chargé comme chaîne simple. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("PRONOTE_QR_CODE_FILE", "/data/qr_code.png") + settings = load_settings() + assert isinstance(settings.pronote.qr_code_file, str) + assert settings.pronote.qr_code_file == "/data/qr_code.png" + + +def test_qr_pin_in_redaction_secrets(monkeypatch: MonkeyPatch) -> None: + """Vérifie que le PIN QR est collecté pour la rédaction des secrets. + + Le ``SecretStr`` du PIN doit figurer dans ``redaction_secrets()`` et sa + représentation textuelle doit rester masquée. + + :param monkeypatch: Fixture pytest pour modifier temporairement l'environnement. + :return: None + """ + monkeypatch.setenv("PRONOTE_QR_PIN", "654321") + settings = load_settings() + secrets = settings.redaction_secrets() + assert settings.pronote.qr_pin in secrets + assert "654321" not in repr(settings.pronote.qr_pin) + assert "**********" in repr(settings.pronote.qr_pin) + + # Ensure trailing newline diff --git a/tests/unit/test_fallback.py b/tests/unit/test_fallback.py index 282fc4e..c6e48ab 100644 --- a/tests/unit/test_fallback.py +++ b/tests/unit/test_fallback.py @@ -1315,6 +1315,110 @@ def test_is_pronotepy_configured_without_ent_returns_true() -> None: assert fetcher._is_pronotepy_configured() is True +def test_is_pronotepy_configured_password_mode_all_set() -> None: + """Test _is_pronotepy_configured() en mode password avec tous les champs définis. + + URL, identifiant et mot de passe sont présents : la fonction retourne True. + + :return: None + :rtype: None + """ + settings = Settings( + pronote=PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + agenda_source="pronotepy", + homework_source="pronotepy", + ), + app=Settings().app, + ) + + client: _MockPronoteClientProtocol = MagicMock() + fetcher = PronoteFetcher(settings=settings, pronote_client=client) + + assert fetcher._is_pronotepy_configured() is True + + +def test_is_pronotepy_configured_password_mode_missing_password() -> None: + """Test _is_pronotepy_configured() en mode password sans mot de passe. + + Le mot de passe est None : la fonction retourne False. + + :return: None + :rtype: None + """ + settings = Settings( + pronote=PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=None, + agenda_source="pronotepy", + homework_source="pronotepy", + ), + app=Settings().app, + ) + + client: _MockPronoteClientProtocol = MagicMock() + fetcher = PronoteFetcher(settings=settings, pronote_client=client) + + assert fetcher._is_pronotepy_configured() is False + + +def test_is_pronotepy_configured_qr_token_mode_url_only() -> None: + """Test _is_pronotepy_configured() en mode qr_token avec URL uniquement. + + En mode qr_token, seul l'URL est requis : l'identifiant et le mot de + passe peuvent être absents, la fonction retourne True. + + :return: None + :rtype: None + """ + settings = Settings( + pronote=PronoteSettings( + url="https://pronote.example.com", + username=None, + password=None, + auth_mode="qr_token", + agenda_source="pronotepy", + homework_source="pronotepy", + ), + app=Settings().app, + ) + + client: _MockPronoteClientProtocol = MagicMock() + fetcher = PronoteFetcher(settings=settings, pronote_client=client) + + assert fetcher._is_pronotepy_configured() is True + + +def test_is_pronotepy_configured_qr_token_mode_no_url() -> None: + """Test _is_pronotepy_configured() en mode qr_token sans URL. + + L'URL est None : la fonction retourne False, même si le mode qr_token + ne requiert que PRONOTE_URL. + + :return: None + :rtype: None + """ + settings = Settings( + pronote=PronoteSettings( + url=None, + username=None, + password=None, + auth_mode="qr_token", + agenda_source="pronotepy", + homework_source="pronotepy", + ), + app=Settings().app, + ) + + client: _MockPronoteClientProtocol = MagicMock() + fetcher = PronoteFetcher(settings=settings, pronote_client=client) + + assert fetcher._is_pronotepy_configured() is False + + def test_agenda_sources_auto_without_ical_and_without_ent_returns_pronotepy() -> None: """Test _agenda_sources() en mode AUTO sans iCal URL et sans ent retourne pronotepy. diff --git a/tests/unit/test_pronote_auth_state.py b/tests/unit/test_pronote_auth_state.py new file mode 100644 index 0000000..fcc1e30 --- /dev/null +++ b/tests/unit/test_pronote_auth_state.py @@ -0,0 +1,208 @@ +"""Tests unitaires pour le gestionnaire d'état d'authentification Pronote. + +Ce module valide le comportement de :class:`PronoteAuthState` dans +:mod:`pronote_sync.sources.pronote.auth_state`. Les tests couvrent : + +- Le chargement des credentials (absent, corrompu, version invalide), +- La persistance et le rechargement des credentials, +- Les permissions ``0600`` du fichier d'état, +- La suppression via :meth:`clear`, +- L'absence de fuite des credentials dans les journaux. + +Tous les tests utilisent des fichiers temporaires via la fixture ``tmp_path``. +""" + +from __future__ import annotations + +import json +import logging +import os +from pathlib import Path + +import pytest + +from pronote_sync.sources.pronote.auth_state import PronoteAuthState + + +def test_load_no_file_returns_none(tmp_path: Path) -> None: + """Vérifie qu'un fichier d'état absent renvoie ``None``. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :return: None + """ + state = PronoteAuthState(tmp_path / "missing.json") + + assert state.load() is None + + +def test_save_then_load_roundtrip(tmp_path: Path) -> None: + """Vérifie que des credentials sauvegardés sont rechargés à l'identique. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :return: None + """ + state_file = tmp_path / "auth.json" + credentials = { + "pronote_url": "https://example.com/pronote", + "username": "parent-1", + "password": "token-123", # pragma: allowlist secret + "uuid": "uuid-456", + } + + state = PronoteAuthState(state_file) + state.save(credentials) + loaded = PronoteAuthState(state_file).load() + + assert loaded == credentials + + +def test_load_corrupted_json_returns_none(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: + """Vérifie qu'un fichier JSON corrompu renvoie ``None`` et journalise un avertissement. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :param caplog: Fixture pytest pour capturer les logs. + :return: None + """ + state_file = tmp_path / "corrupt.json" + state_file.write_text("not json{", encoding="utf-8") + + with caplog.at_level("WARNING"): + result = PronoteAuthState(state_file).load() + + assert result is None + assert "Impossible de charger le fichier d'état d'authentification Pronote" in caplog.text + + +def test_load_wrong_version_returns_none(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: + """Vérifie qu'une version non supportée renvoie ``None`` et journalise un avertissement. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :param caplog: Fixture pytest pour capturer les logs. + :return: None + """ + state_file = tmp_path / "wrong_version.json" + state_file.write_text( + json.dumps( + { + "version": 2, + "credentials": { + "pronote_url": "https://example.com", + "username": "u", + "password": "t", + "uuid": "i", + }, + } + ), + encoding="utf-8", + ) + + with caplog.at_level("WARNING"): + result = PronoteAuthState(state_file).load() + + assert result is None + assert "version absente ou non supportée" in caplog.text + + +def test_load_missing_version_returns_none( + tmp_path: Path, caplog: pytest.LogCaptureFixture +) -> None: + """Vérifie qu'un fichier sans champ version renvoie ``None``. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :param caplog: Fixture pytest pour capturer les logs. + :return: None + """ + state_file = tmp_path / "missing_version.json" + state_file.write_text( + json.dumps( + { + "credentials": { + "pronote_url": "https://example.com", + "username": "u", + "password": "t", + "uuid": "i", + } + } + ), + encoding="utf-8", + ) + + with caplog.at_level("WARNING"): + result = PronoteAuthState(state_file).load() + + assert result is None + + +def test_clear_removes_file(tmp_path: Path) -> None: + """Vérifie que clear supprime le fichier d'état existant. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :return: None + """ + state_file = tmp_path / "auth.json" + state = PronoteAuthState(state_file) + state.save({"pronote_url": "u", "username": "u", "password": "t", "uuid": "i"}) + + assert state_file.exists() + state.clear() + + assert not state_file.exists() + + +def test_clear_no_file_noop(tmp_path: Path) -> None: + """Vérifie que clear ne fait rien quand le fichier n'existe pas. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :return: None + """ + state = PronoteAuthState(tmp_path / "missing.json") + + state.clear() + + +def test_save_creates_file_with_0600_permissions(tmp_path: Path) -> None: + """Vérifie que le fichier d'état est créé avec les permissions ``0600``. + + Le fichier contient un token vivant : il doit être lisible uniquement + par le propriétaire. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :return: None + """ + state_file = tmp_path / "auth.json" + state = PronoteAuthState(state_file) + state.save({"pronote_url": "u", "username": "u", "password": "t", "uuid": "i"}) + + assert os.stat(state_file).st_mode & 0o777 == 0o600 + + +def test_no_credentials_in_logs(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: + """Vérifie qu'aucun contenu des credentials n'apparaît dans les journaux. + + Des sentinelles distinctes sont utilisées pour ``pronote_url``, + ``username``, ``password`` et ``uuid`` ; aucun de ces marqueurs ne doit + apparaître dans les messages journalisés lors d'une sauvegarde, d'un + chargement et d'une suppression. + + :param tmp_path: Fixture pytest pour un répertoire temporaire. + :param caplog: Fixture pytest pour capturer les logs. + :return: None + """ + state_file = tmp_path / "auth.json" + credentials = { + "pronote_url": "https://SENTINEL_URL_ZZZ.example/pronote", + "username": "SENTINEL_USER_ZZZ", + "password": "SENTINEL_PASSWORD_ZZZ", # pragma: allowlist secret + "uuid": "SENTINEL_UUID_ZZZ", + } + + state = PronoteAuthState(state_file) + with caplog.at_level(logging.DEBUG): + state.save(credentials) + state.load() + state.clear() + + assert "SENTINEL_URL_ZZZ" not in caplog.text + assert "SENTINEL_USER_ZZZ" not in caplog.text + assert "SENTINEL_PASSWORD_ZZZ" not in caplog.text + assert "SENTINEL_UUID_ZZZ" not in caplog.text diff --git a/tests/unit/test_pronote_client.py b/tests/unit/test_pronote_client.py index e6e96d9..ad4185d 100644 --- a/tests/unit/test_pronote_client.py +++ b/tests/unit/test_pronote_client.py @@ -7,7 +7,10 @@ utilisent des mocks pour éviter tout accès réseau réel à Pronote. from __future__ import annotations +import json +import logging from datetime import date, datetime +from pathlib import Path import pronotepy import pytest @@ -15,9 +18,11 @@ import pytest_mock from pydantic import SecretStr from pronote_sync.config.settings import PronoteSettings +from pronote_sync.errors import PronoteAuthRotationError from pronote_sync.models.agenda import Lesson, LessonStatus from pronote_sync.models.homework import Homework from pronote_sync.models.message import Message, MessageType +from pronote_sync.sources.pronote.auth_state import PronoteAuthState from pronote_sync.sources.pronote.client import PronoteClient, PronoteClientProtocol # --- Protocol tests --- @@ -613,4 +618,585 @@ def test_get_informations_degraded_on_error( assert messages == [] +# --- QR code / token authentication tests --- + + +def test_connect_password_mode_unchanged( + mocker: pytest_mock.MockerFixture, + pronote_settings: PronoteSettings, +) -> None: + """Vérifie que le mode password conserve le comportement historique. + + :param mocker: Fixture pytest-mock pour le mocking. + :param pronote_settings: Paramètres Pronote valides en mode password. + :return: None + """ + from unittest.mock import Mock + + mock_client = mocker.MagicMock() + mock_client_class = Mock(return_value=mock_client) + mocker.patch("pronotepy.ParentClient", new=mock_client_class) + mocker.patch("pronotepy.Client") + + client = PronoteClient(pronote_settings, auth_state=None) + connected = client._connect() + + assert connected is mock_client + mock_client_class.assert_called_once_with( + pronote_url="https://pronote.example.com", + username="testuser", + password="testpass", # pragma: allowlist secret + ent=mocker.ANY, + ) + # Connexion paresseuse : un second appel réutilise le client déjà créé + client._connect() + assert mock_client_class.call_count == 1 + + +def test_connect_qr_token_with_persisted_creds( + mocker: pytest_mock.MockerFixture, +) -> None: + """Vérifie le login par token persisté en mode qr_token. + + Les credentials chargés depuis :class:`PronoteAuthState` sont rejoués via + ``token_login`` et le token rotate est resauvegardé. + + :param mocker: Fixture pytest-mock pour le mocking. + :return: None + """ + creds = { + "pronote_url": "https://pronote.example.com", + "username": "testuser", + "password": "persisted-token", # pragma: allowlist secret + "uuid": "persisted-uuid", + } + rotated_creds = {**creds, "uuid": "rotated-uuid"} + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = creds + + mock_client = mocker.MagicMock() + mock_client.logged_in = True + mock_client.export_credentials.return_value = rotated_creds + mocker.patch("pronotepy.ParentClient.token_login", return_value=mock_client) + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + ) + client = PronoteClient(settings, auth_state=auth_state) + connected = client._connect() + + assert connected is mock_client + pronotepy.ParentClient.token_login.assert_called_once_with(**creds) # type: ignore[attr-defined] + auth_state.save.assert_called_once_with(rotated_creds) + + +def test_connect_qr_token_no_creds_with_qr_code( + mocker: pytest_mock.MockerFixture, + tmp_path: Path, +) -> None: + """Vérifie l'enrôlement initial par QR code quand aucun token n'est persisté. + + :param mocker: Fixture pytest-mock pour le mocking. + :param tmp_path: Répertoire temporaire de test. + :return: None + """ + qr_file = tmp_path / "qr_code.json" + qr_file.write_text( + json.dumps( + { + "login": "testuser", + "jeton": "qr-jeton", + "url": "https://pronote.example.com", + } + ), + encoding="utf-8", + ) + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = None + + creds = { + "pronote_url": "https://pronote.example.com", + "username": "testuser", + "password": "new-token", # pragma: allowlist secret + "uuid": "new-uuid", + } + mock_client = mocker.MagicMock() + mock_client.logged_in = True + mock_client.export_credentials.return_value = creds + mocker.patch("pronotepy.ParentClient.qrcode_login", return_value=mock_client) + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + qr_code_file=str(qr_file), + qr_pin=SecretStr("123456"), + ) + client = PronoteClient(settings, auth_state=auth_state) + connected = client._connect() + + assert connected is mock_client + qrcode_login = pronotepy.ParentClient.qrcode_login + qrcode_login.assert_called_once() # type: ignore[attr-defined] + kwargs = qrcode_login.call_args.kwargs # type: ignore[attr-defined] + assert kwargs["pin"] == "123456" + assert kwargs["qr_code"] == { + "login": "testuser", + "jeton": "qr-jeton", + "url": "https://pronote.example.com", + } + assert kwargs["uuid"].startswith("pronote-sync-") + auth_state.save.assert_called_once_with(creds) + + +def test_connect_qr_token_token_login_fails_raises_rotation_error( + mocker: pytest_mock.MockerFixture, + tmp_path: Path, +) -> None: + """Vérifie la levée de PronoteAuthRotationError quand le token persisté est invalide. + + En cas d'échec du login par token, aucun repli vers l'enrôlement QR + n'est tenté : l'erreur de rotation est levée immédiatement, même si un + fichier QR est disponible. + + :param mocker: Fixture pytest-mock pour le mocking. + :param tmp_path: Répertoire temporaire de test. + :return: None + """ + qr_file = tmp_path / "qr_code.json" + qr_file.write_text( + json.dumps( + { + "login": "testuser", + "jeton": "qr-jeton", + "url": "https://pronote.example.com", + } + ), + encoding="utf-8", + ) + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = { + "pronote_url": "https://pronote.example.com", + "username": "testuser", + "password": "expired-token", # pragma: allowlist secret + "uuid": "old-uuid", + } + + token_login = mocker.patch("pronotepy.ParentClient.token_login") + token_login.side_effect = pronotepy.PronoteAPIError("token invalide") + qrcode_login = mocker.patch("pronotepy.ParentClient.qrcode_login") + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + qr_code_file=str(qr_file), + qr_pin=SecretStr("123456"), + ) + client = PronoteClient(settings, auth_state=auth_state) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + token_login.assert_called_once() + qrcode_login.assert_not_called() + auth_state.save.assert_not_called() + message = str(exc_info.value) + assert "expiré ou invalide" in message + assert ".pronote_auth_state.json" in message + assert "PRONOTE_QR_CODE_FILE" in message + + +def test_token_login_failure_raises_rotation_not_enroll( + mocker: pytest_mock.MockerFixture, + tmp_path: Path, +) -> None: + """Vérifie qu'un login par token non connecté lève PronoteAuthRotationError sans enrôlement QR. + + ``token_login`` retourne un client non connecté (``logged_in`` False) : + l'erreur de rotation est levée immédiatement et ``qrcode_login`` n'est + jamais appelé, même avec un QR code disponible. + + :param mocker: Fixture pytest-mock pour le mocking. + :param tmp_path: Répertoire temporaire de test. + :return: None + """ + qr_file = tmp_path / "qr_code.json" + qr_file.write_text( + json.dumps( + { + "login": "testuser", + "jeton": "qr-jeton", + "url": "https://pronote.example.com", + } + ), + encoding="utf-8", + ) + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = { + "pronote_url": "https://pronote.example.com", + "username": "testuser", + "password": "expired-token", # pragma: allowlist secret + "uuid": "old-uuid", + } + + mock_client = mocker.MagicMock() + mock_client.logged_in = False + token_login = mocker.patch("pronotepy.ParentClient.token_login", return_value=mock_client) + qrcode_login = mocker.patch("pronotepy.ParentClient.qrcode_login") + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + qr_code_file=str(qr_file), + qr_pin=SecretStr("123456"), + ) + client = PronoteClient(settings, auth_state=auth_state) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + token_login.assert_called_once() + qrcode_login.assert_not_called() + auth_state.save.assert_not_called() + message = str(exc_info.value) + assert "non connecté" in message + assert ".pronote_auth_state.json" in message + + +def test_connect_qr_token_no_creds_no_qr_raises_rotation_error( + mocker: pytest_mock.MockerFixture, +) -> None: + """Vérifie la levée de PronoteAuthRotationError sans token persisté ni QR code. + + :param mocker: Fixture pytest-mock pour le mocking. + :return: None + """ + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = None + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + ) + client = PronoteClient(settings, auth_state=auth_state) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + message = str(exc_info.value) + assert "PRONOTE_QR_CODE_FILE" in message + assert "PRONOTE_QR_PIN" in message + assert ".pronote_auth_state.json" in message + + +def test_connect_qr_token_token_login_fails_no_qr_raises_rotation_error( + mocker: pytest_mock.MockerFixture, +) -> None: + """Vérifie la levée de PronoteAuthRotationError quand le token échoue sans QR. + + :param mocker: Fixture pytest-mock pour le mocking. + :return: None + """ + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = { + "pronote_url": "https://pronote.example.com", + "username": "testuser", + "password": "expired-token", # pragma: allowlist secret + "uuid": "old-uuid", + } + token_login = mocker.patch("pronotepy.ParentClient.token_login") + token_login.side_effect = pronotepy.PronoteAPIError("token invalide") + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + ) + client = PronoteClient(settings, auth_state=auth_state) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + message = str(exc_info.value) + assert "expiré ou invalide" in message + assert ".pronote_auth_state.json" in message + assert "PRONOTE_QR_CODE_FILE" in message + + +def test_connect_qr_token_invalid_qr_json_raises_rotation_error( + mocker: pytest_mock.MockerFixture, + tmp_path: Path, +) -> None: + """Vérifie la levée de PronoteAuthRotationError pour un fichier QR illisible. + + :param mocker: Fixture pytest-mock pour le mocking. + :param tmp_path: Répertoire temporaire de test. + :return: None + """ + qr_file = tmp_path / "qr_code.json" + qr_file.write_text("{json invalide", encoding="utf-8") + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = None + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + qr_code_file=str(qr_file), + qr_pin=SecretStr("123456"), + ) + client = PronoteClient(settings, auth_state=auth_state) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + assert "Impossible de lire le fichier QR code" in str(exc_info.value) + + +def test_connect_qr_token_missing_qr_key_raises_rotation_error( + mocker: pytest_mock.MockerFixture, + tmp_path: Path, +) -> None: + """Vérifie la levée de PronoteAuthRotationError quand une clé QR requise manque. + + :param mocker: Fixture pytest-mock pour le mocking. + :param tmp_path: Répertoire temporaire de test. + :return: None + """ + qr_file = tmp_path / "qr_code.json" + qr_file.write_text( + json.dumps({"login": "testuser", "url": "https://pronote.example.com"}), + encoding="utf-8", + ) + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = None + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + qr_code_file=str(qr_file), + qr_pin=SecretStr("123456"), + ) + client = PronoteClient(settings, auth_state=auth_state) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + assert "jeton" in str(exc_info.value) + + +def test_connect_qr_token_qrcode_login_fails_raises_rotation_error( + mocker: pytest_mock.MockerFixture, + tmp_path: Path, +) -> None: + """Vérifie la levée de PronoteAuthRotationError quand le login QR échoue. + + :param mocker: Fixture pytest-mock pour le mocking. + :param tmp_path: Répertoire temporaire de test. + :return: None + """ + qr_file = tmp_path / "qr_code.json" + qr_file.write_text( + json.dumps( + { + "login": "testuser", + "jeton": "qr-jeton", + "url": "https://pronote.example.com", + } + ), + encoding="utf-8", + ) + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = None + mocker.patch( + "pronotepy.ParentClient.qrcode_login", + side_effect=pronotepy.exceptions.QRCodeDecryptError("PIN incorrect"), + ) + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + qr_code_file=str(qr_file), + qr_pin=SecretStr("123456"), + ) + client = PronoteClient(settings, auth_state=auth_state) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + message = str(exc_info.value) + assert "PIN invalide ou QR code expiré" in message + assert "PRONOTE_QR_CODE_FILE" in message + + +def test_no_secrets_in_rotation_error_messages( + mocker: pytest_mock.MockerFixture, + tmp_path: Path, + caplog: pytest.LogCaptureFixture, +) -> None: + """Vérifie qu'aucun secret ne fuit dans les erreurs ni les logs de rotation. + + :param mocker: Fixture pytest-mock pour le mocking. + :param tmp_path: Répertoire temporaire de test. + :param caplog: Fixture pytest de capture des logs. + :return: None + """ + sentinel_pin = "SENTINEL_PIN_42" + sentinel_token = "SENTINEL_TOKEN_7" + sentinel_url = "https://sentinel-url.pronote.example.com" + + qr_file = tmp_path / "qr_code.json" + qr_file.write_text( + json.dumps( + { + "login": "testuser", + "jeton": sentinel_token, + "url": sentinel_url, + } + ), + encoding="utf-8", + ) + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = None + + mocker.patch( + "pronotepy.ParentClient.qrcode_login", + side_effect=pronotepy.exceptions.QRCodeDecryptError( + f"token: {sentinel_token} password: {sentinel_pin}" + ), + ) + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + qr_code_file=str(qr_file), + qr_pin=SecretStr(sentinel_pin), + ) + client = PronoteClient(settings, auth_state=auth_state) + + with caplog.at_level(logging.ERROR, logger="pronote_sync.sources.pronote.client"): + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + message = str(exc_info.value) + assert sentinel_pin not in message + assert sentinel_token not in message + assert "sentinel-url" not in message + assert caplog.text + assert sentinel_pin not in caplog.text + assert sentinel_token not in caplog.text + assert "sentinel-url" not in caplog.text + + +def test_no_raw_secrets_in_logs( + mocker: pytest_mock.MockerFixture, + tmp_path: Path, + caplog: pytest.LogCaptureFixture, +) -> None: + """Vérifie l'expurgation de secrets bruts sans motif reconnaissable dans les logs. + + Des sentinelles distinctes pour le token persisté, le PIN QR et le jeton + QR sont injectées dans le message d'exception de ``token_login`` sans + motif ``cle=valeur`` ni format d'URL ; elles ne doivent apparaître ni + dans les logs ni dans l'erreur de rotation levée. + + :param mocker: Fixture pytest-mock pour le mocking. + :param tmp_path: Répertoire temporaire de test. + :param caplog: Fixture pytest de capture des logs. + :return: None + """ + sentinel_token = "SENTINEL_RAW_TOKEN_ALPHA" + sentinel_pin = "SENTINEL_RAW_PIN_BRAVO" + sentinel_jeton = "SENTINEL_RAW_JETON_CHARLIE" + + qr_file = tmp_path / "qr_code.json" + qr_file.write_text( + json.dumps( + { + "login": "testuser", + "jeton": sentinel_jeton, + "url": "https://pronote.example.com", + } + ), + encoding="utf-8", + ) + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = { + "pronote_url": "https://pronote.example.com", + "username": "testuser", + "password": sentinel_token, + "uuid": "old-uuid", + } + + mocker.patch( + "pronotepy.ParentClient.token_login", + side_effect=pronotepy.PronoteAPIError( + f"login refusé {sentinel_token} puis {sentinel_pin} puis {sentinel_jeton}" + ), + ) + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + qr_code_file=str(qr_file), + qr_pin=SecretStr(sentinel_pin), + ) + client = PronoteClient(settings, auth_state=auth_state) + + with caplog.at_level(logging.ERROR, logger="pronote_sync.sources.pronote.client"): + with pytest.raises(PronoteAuthRotationError) as exc_info: + client._connect() + + message = str(exc_info.value) + assert sentinel_token not in message + assert sentinel_pin not in message + assert sentinel_jeton not in message + assert caplog.text + assert sentinel_token not in caplog.text + assert sentinel_pin not in caplog.text + assert sentinel_jeton not in caplog.text + + # Ensure trailing newline diff --git a/tests/unit/test_rotation_propagation.py b/tests/unit/test_rotation_propagation.py new file mode 100644 index 0000000..84994b1 --- /dev/null +++ b/tests/unit/test_rotation_propagation.py @@ -0,0 +1,180 @@ +"""Unit tests for PronoteAuthRotationError propagation through each layer. + +These tests verify that the rotation error propagates correctly through the +real call chain without being wrapped in PipelineCriticalError at any layer. +""" + +from __future__ import annotations + +from datetime import date + +import pytest +from pydantic import SecretStr + +from pronote_sync.config.settings import PronoteSettings, Settings +from pronote_sync.errors import PipelineCriticalError, PronoteAuthRotationError +from pronote_sync.models.agenda import Lesson, SchoolEvent +from pronote_sync.models.homework import Homework +from pronote_sync.models.message import Message +from pronote_sync.pipeline.steps.fetch import fetch_step +from pronote_sync.sources.pronote.fallback import PronoteFetcher + + +class StubPronoteClientWithRotationError: + """Stub PronoteClient that raises PronoteAuthRotationError from its methods.""" + + def get_lessons(self, start: date, end: date) -> list[Lesson]: + """Raise rotation error when fetching lessons. + + :param start: Start date (unused). + :param end: End date (unused). + :return: Never returns. + :raises PronoteAuthRotationError: Always. + """ + del start, end + raise PronoteAuthRotationError("Token persisté expiré : ré-enrôlement requis") + + def get_homeworks(self, start: date, end: date) -> list[Homework]: + """Raise rotation error when fetching homeworks. + + :param start: Start date (unused). + :param end: End date (unused). + :return: Never returns. + :raises PronoteAuthRotationError: Always. + """ + del start, end + raise PronoteAuthRotationError("Token persisté expiré : ré-enrôlement requis") + + def get_messages(self) -> list[Message]: + """Return empty messages list. + + :return: Empty list. + :rtype: list[Message] + """ + return [] + + def get_informations(self) -> list[Message]: + """Return empty information messages list. + + :return: Empty list. + :rtype: list[Message] + """ + return [] + + +class StubSettings: + """Minimal settings stub for PronoteFetcher.""" + + def __init__(self) -> None: + """Initialize with minimal configuration.""" + self.pronote = PronoteSettings( + url="https://pronote.example.com", + username="test", + password=SecretStr("test_password"), + ent="bordeaux", + account_type="parent", + agenda_source="pronotepy", + homework_source="pronotepy", + messages_source="pronotepy", + auth_mode="password", + qr_code_file=None, + qr_pin=None, + ical_url=None, + ) + self.app = type("AppSettings", (), {"sync_past_days": 7, "sync_future_days": 7})() + + +class StubFetcherWithRotationError: + """Stub PronoteFetcher that raises PronoteAuthRotationError from its methods.""" + + def __init__(self) -> None: + """Initialize the stub fetcher.""" + self._settings = StubSettings() + self._client = StubPronoteClientWithRotationError() + + def fetch_agenda(self) -> tuple[list[Lesson], list[SchoolEvent]]: + """Raise rotation error when fetching agenda. + + :return: Never returns. + :rtype: tuple[list[Lesson], list[SchoolEvent]] + :raises PronoteAuthRotationError: Always. + """ + raise PronoteAuthRotationError("Token persisté expiré : ré-enrôlement requis") + + def fetch_homework(self, target_date: date) -> list[Homework]: + """Raise rotation error when fetching homework. + + :param target_date: Target date (unused). + :return: Never returns. + :rtype: list[Homework] + :raises PronoteAuthRotationError: Always. + """ + del target_date + raise PronoteAuthRotationError("Token persisté expiré : ré-enrôlement requis") + + def fetch_messages(self) -> list[Message]: + """Return empty messages list. + + :return: Empty list. + :rtype: list[Message] + """ + return [] + + def fetch_informations(self) -> list[Message]: + """Return empty information messages list. + + :return: Empty list. + :rtype: list[Message] + """ + return [] + + +def test_pronote_fetcher_fetch_agenda_propagates_rotation_error() -> None: + """PronoteFetcher.fetch_agenda() propagates PronoteAuthRotationError without wrapping. + + This test verifies that when the underlying PronoteClient raises + PronoteAuthRotationError, the fetcher propagates it directly without + converting it to PipelineCriticalError. + """ + settings = Settings(pronote=StubSettings().pronote) + fetcher = PronoteFetcher(settings, StubPronoteClientWithRotationError()) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + fetcher.fetch_agenda() + + assert "Token persisté expiré" in str(exc_info.value) + assert not isinstance(exc_info.value, PipelineCriticalError) + + +def test_pronote_fetcher_fetch_homework_propagates_rotation_error() -> None: + """PronoteFetcher.fetch_homework() propagates PronoteAuthRotationError without wrapping. + + This test verifies that when the underlying PronoteClient raises + PronoteAuthRotationError, the fetcher propagates it directly without + converting it to PipelineCriticalError. + """ + settings = Settings(pronote=StubSettings().pronote) + fetcher = PronoteFetcher(settings, StubPronoteClientWithRotationError()) + target_date = date(2026, 9, 9) + + with pytest.raises(PronoteAuthRotationError) as exc_info: + fetcher.fetch_homework(target_date) + + assert "Token persisté expiré" in str(exc_info.value) + assert not isinstance(exc_info.value, PipelineCriticalError) + + +def test_fetch_step_propagates_rotation_error() -> None: + """fetch_step() propagates PronoteAuthRotationError without wrapping. + + This test verifies that the pipeline step fetch_step() propagates + PronoteAuthRotationError directly from the fetcher without converting + it to PipelineCriticalError. + """ + fetcher = StubFetcherWithRotationError() + + with pytest.raises(PronoteAuthRotationError) as exc_info: + fetch_step(fetcher, today=date(2026, 9, 8)) + + assert "Token persisté expiré" in str(exc_info.value) + assert not isinstance(exc_info.value, PipelineCriticalError) From bc79ebf680f0ef369806ff75f93b700b8bb5b774 Mon Sep 17 00:00:00 2001 From: Antoine Van Elstraete Date: Thu, 10 Sep 2026 11:25:49 +0200 Subject: [PATCH 2/5] =?UTF-8?q?docs:=20cr=C3=A9ation=20d'un=20document=20d?= =?UTF-8?q?e=20r=C3=A9f=C3=A9rence=20(non=20officiel)=20pour=20l'authentif?= =?UTF-8?q?ication=20pronote.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/pronote-auth.md | 570 ++++++++++++++++++++++++++++++++++++++++++ docs/pronote-auth.pdf | Bin 0 -> 249546 bytes docs/pronote-auth.tex | 56 +++++ 3 files changed, 626 insertions(+) create mode 100644 docs/pronote-auth.md create mode 100644 docs/pronote-auth.pdf create mode 100644 docs/pronote-auth.tex diff --git a/docs/pronote-auth.md b/docs/pronote-auth.md new file mode 100644 index 0000000..b1d98e4 --- /dev/null +++ b/docs/pronote-auth.md @@ -0,0 +1,570 @@ +> ⚠️ **AVERTISSEMENT** +> +> Ce document **n'est pas un guide officiel**. Il n'est ni approuvé, ni validé, ni autorisé par le Ministère de l'Éducation Nationale française ni par Docaposte (éditeur de Pronote / Index Éducation). Les informations présentées reposent sur des recherches publiques, des travaux de rétro-ingénierie menés par la communauté open-source et des analyses techniques. Les protocoles décrits ne sont pas officiellement publiés par Index Éducation et peuvent évoluer sans préavis. +> +> Ce document a été produit à l'aide de plusieurs agents basés sur des modèles de langage (LLM) : +> - **Mercury 2.5** (agent explorer) — exploration du code pour établir les faits techniques. +> - **Gemini 3.5 Flash** (agent web-explorer) — recherche des méthodes d'authentification externes. +> - **Hy3** (agent planner) — planification de la structure du document et découpage en sections. +> - **Mistral Medium** (agent tech-writer) — rédaction du document. +> - **GPT-5.6 Luna** (agent reviewer) — revue du contenu pour l'exactitude et la cohérence. +> - **GLM-5.2** (agent orchestrator) — coordination et intégration du travail. +> - **Gemini 3.7 Flash** (agent ui-designer) — conception de la version LaTeX/PDF. +# Authentification Pronote — Référence technique + +## Introduction + +Ce manuel documente l’ensemble des méthodes d’authentification connues pour accéder aux données élèves/parents de Pronote (notes, emploi du temps, devoirs, absences, etc.). Il couvre à la fois les mécanismes officiellement supportés et les protocoles issus de l’analyse communautaire. + +Le public visé inclut les développeurs, ingénieurs sécurité et intégrateurs système devant maîtriser l’authentification Pronote au niveau protocolaire. Les descriptions utilisent du pseudocode générique, des échanges HTTP et des schémas protocoles, sans présupposer de langage ou framework spécifique. + +*Remarque méthodologique* : Les méthodes au-delà de l’export iCal s’appuient sur des recherches publiques et l’analyse de la communauté open source. Ces protocoles, non publiés officiellement par Index Éducation, peuvent évoluer sans préavis. + +## Vue d'ensemble comparative + +| Méthode | Périmètre de données | Identifiants requis | Expiration du jeton | Complexité | Statut | +|---|---|---|---|---|---| +| URL iCal sécurisée (`icalsecurise`) | Emploi du temps et, si inclus, cahier de textes/devoirs | Jeton dans l'URL | Longue durée | Très faible | Officiel | +| Connexion directe (identifiant/mot de passe) | Complet | Identifiant + mot de passe établissement | Session ~15–30 min | Élevée | Reverse-engineered | +| SSO ENT (CAS / SAML / Oze) | Complet | Identifiants ENT | Dépend de la session ENT | Très élevée | Reverse-engineered | +| SSO EduConnect | Complet | Identifiants nationaux EduConnect | Dépend de la session EduConnect | Très élevée | Reverse-engineered | +| CAS (Central Authentication Service) | Complet | Identifiants CAS | Dépend du ticket de service | Modérée | Officiel | +| QR Code + jeton mobile | Complet | Code PIN à 4 chiffres → `jetonConnexionAppliMobile` + UUID | QR valable 10 min ; jeton longue durée | Modérée | Reverse-engineered | +| API publique | N/A | N/A | N/A | N/A | Inexistante | + +*Complet* désigne l’accès aux notes, emploi du temps, devoirs, absences, messagerie et paramètres, tandis que la méthode iCal se limite à l’emploi du temps et éventuellement aux devoirs. + +## Méthode 1 : URL iCal sécurisée (icalsecurise) + +### Principe général +Pronote expose un flux de calendrier en lecture seule conforme à la norme iCalendar (RFC 5545). L’accès est contrôlé par un jeton secret intégré dans l’URL sous forme de paramètre de requête `icalsecurise`. Ce jeton est unique par utilisateur et par établissement. **Le jeton constitue la seule crédentiale** : il doit être traité comme un mot de passe. Toute requête HTTP GET vers cette URL permet de récupérer les données du calendrier, sans nécessiter de cookies, de session ni d’en-têtes d’authentification. + + +### Obtention du jeton +Le jeton s’obtient manuellement depuis l’interface web de Pronote : +1. Se connecter à Pronote (Espace Parents ou Espace Élève) via n’importe quelle méthode d’authentification. +2. Accéder à la vue « Emploi du temps ». +3. Utiliser la fonction « Export iCal » ou « Exporter ». +4. Pronote génère une URL contenant le paramètre `icalsecurise`. +5. Copier cette URL : elle constitue la crédentiale. + +Cette URL doit être stockée de manière sécurisée (ex. : gestionnaire de secrets, variables protégées). **Elle ne doit jamais être versionnée ou partagée en clair.** + + +### Format de l'URL +L’URL suit la structure suivante : +``` +https://{etablissement}.index-education.net/pronote/ical/Edt_{prenom}.ics?icalsecurise={jeton}&version={version}¶m={param} +``` +Exemple masqué : +`https://XXXXXXX.index-education.net/pronote/ical/Edt_Alice.ics?icalsecurise=••••••••&version=2023¶m=...` + +Paramètres de requête : +- `icalsecurise` : jeton secret (crédentiale). +- `version` : version de Pronote. +- `param` : paramètres supplémentaires (optionnels). + + +### Flux d'authentification (protocole) +Le protocole d’authentification se résume ainsi : + +1. **Préparation** : + Le client dispose de l’URL iCal sécurisée (obtenue comme décrit ci-dessus). + +2. **Requête HTTP** : + Le client effectue une requête HTTP GET : + ``` + GET {ical-url} + Accept: text/calendar, */*;q=0.5 + User-Agent: {identifiant-client} + ``` + - Délai d’attente : configurable (recommandé : 20 secondes). + +3. **Validation de la réponse** : + - Si le code HTTP n’est pas 2xx → échec d’authentification ou erreur serveur. + - Si le corps de la réponse ne contient pas `BEGIN:VCALENDAR` → le jeton est probablement expiré ou l’URL est invalide. Le serveur peut retourner une page HTML d’erreur au lieu des données de calendrier. + +4. **Traitement** : + Si la validation réussit, le corps de la réponse est une donnée iCalendar valide, prête à être analysée. + +### Données échangées + +- Le client envoie une seule requête HTTP GET vers l'URL iCal sécurisée. +- En-têtes de requête : `Accept: text/calendar, */*;q=0.5` et `User-Agent: `. +- Le serveur retourne une charge utile iCalendar (RFC 5545) si le jeton est valide. +- Si le jeton est invalide ou expiré, le serveur retourne une réponse non-calendrier (typiquement du HTML). +- Aucun cookie, jeton de session ou en-tête d'authentification n'est échangé — l'URL **est** la crédentiale. + +### Identifiants et jetons + +Le seul identifiant utilisé est le jeton `icalsecurise`, intégré directement dans l’URL. Ce jeton est : +- **Unique** par utilisateur et par établissement. +- **Sensible** : il équivaut à un mot de passe et doit être traité comme tel. +- **Transmis en clair** dans la chaîne de requête de l’URL, mais protégé en transit par HTTPS. +- **Autosuffisant** : aucune autre information (nom d’utilisateur, mot de passe, cookie de session ou jeton OAuth) n’est requise. L’URL **est** l’authentification. + +### Cycle de vie + +Le jeton est **pérenne** : il reste valide jusqu’à sa révocation manuelle par l’utilisateur dans les paramètres Pronote, ou jusqu’à sa régénération par l’établissement (généralement à la rentrée scolaire). +Aucun mécanisme de rafraîchissement automatique ou de rotation n’existe. En cas d’expiration ou de rotation, le serveur retourne une réponse non-iCalendar (souvent une page HTML mentionnant *« Session expirée »*). Le client détecte cette situation en vérifiant l’absence de `BEGIN:VCALENDAR` dans le corps de la réponse. + +La récupération nécessite une **réextraction manuelle** d’une nouvelle URL iCal depuis l’interface Pronote. +**Bonnes pratiques** : tester l’URL avant chaque rentrée et la régénérer proactivement si l’établissement est connu pour rotater les jetons à cette période. + +### Sécurité + +1. **Surface d’attaque** : Le jeton est encodé dans l’URL. Il peut être exposé via les logs d’accès du serveur, les logs proxy, les en-têtes `Referer`, l’historique du navigateur ou une interception réseau (atténué par HTTPS). +2. **Exposition des identifiants** : En cas de fuite, le jeton accorde un accès en lecture à l’emploi du temps (et aux devoirs, si inclus) jusqu’à sa rotation par l’établissement. +3. **Résistance au rejeu** : Faible — absence de *nonce*, de validation temporelle ou de protection contre le *replay*. Toute entité disposant de l’URL peut récupérer les données à tout moment. +4. **Rotation** : Manuellement uniquement, via la régénération de l’URL dans Pronote. L’établissement contrôle les réinitialisations côté serveur. +5. **Recommandations** : Conserver l’URL comme un secret (jamais en contrôle de version). Utiliser HTTPS (par défaut). Limiter l’accès à l’URL en besoin d’en connaître. Rotater proactivement aux changements d’année scolaire. Masquer l’URL dans les messages d’erreur et les logs pour éviter les fuites accidentelles. + +### Limitations + +- **Périmètre des données** : limité à l’emploi du temps et, si inclus par l’établissement, aux devoirs (*cahier de textes*). +- **Accès en lecture seule** : aucune modification possible. +- **Exclusions** : notes, absences, messagerie, bulletins ou paramètres sont inaccessibles. +- **Latence** : l’export iCal peut présenter un délai de mise à jour (plusieurs heures) avant de refléter les modifications Pronote. +- **Rafraîchissement** : impossible par programmation — une intervention manuelle est toujours requise. + +### Statut + +**Officiel** — L’export iCal est une fonctionnalité supportée par Pronote, éditée par Index Éducation. Le mécanisme de jeton fait partie intégrante du produit, bien que son format interne et sa logique de génération ne soient pas documentés publiquement. + +## Méthode 2 : Connexion directe (identifiant / mot de passe) + +### Principe général +La connexion directe utilise un protocole propriétaire de type JSON sur HTTP(S), sécurisé par un chiffrement AES-256-CBC spécifique à la session et un mécanisme de défi-réponse. Il s’agit du protocole natif de Pronote, tel qu’utilisé par son interface web. + +### Flux détaillé + +**Étape 1 — Initialisation de la session (`GET /pronote/.html`)** +Le client effectue une requête GET vers l’espace cible (ex. `/pronote/eleve.html` pour un élève, `/pronote/parent.html` pour un parent). La réponse HTML contient un gestionnaire JavaScript `onload` avec les paramètres de session : +``` +Session initialization parameters: + h = + a = (3 = Élève, 7 = Parent) + sCrA = + sCoA = +``` +- `h` : identifiant de session (chaîne ou nombre unique). +- `a` : identifiant de l’espace (`3` pour Élève, `7` pour Parent). +- `sCrA` / `sCoA` : indicateurs de chiffrement et de compression. + +**Étape 2 — Échange de clés (`POST /pronote/appelfonction///`)** +Le client envoie une charge utile `FonctionParametres` contenant `donneesSec.donnees.Uuid` : +- En HTTPS : un IV AES de 16 octets encodé en base64. +- En HTTP non sécurisé : un IV chiffré avec RSA-1024. +- `numeroOrdre` est un compteur incrémental (début à 1). +- Pour la première requête, `numeroOrdre` est chiffré avec AES-256-CBC, une clé vide MD5 (`d41d8cd98f00b204e9800998ecf8427e`) et un IV nul. +- Pour les requêtes suivantes, l’IV de session (issu de `Uuid`) est utilisé. + +**Étape 3 — Identification (`POST ... / Identification`)** +Le client soumet une charge utile JSON : +``` +{ + "nom": "Identification", + "session": "", + "numeroOrdre": "", + "donneesSec": { + "donnees": { + "identifiant": "", + "genreConnexion": 0, + "genreEspace": , + "pourENT": false, + "enConnexionAuto": false + } + } +} +``` +Le serveur retourne : `alea` (sel aléatoire), `challenge` (chaîne hexadécimale), `modeCompLog` et `modeCompMdp` (indicateurs de normalisation de casse). + +**Étape 4 — Résolution du défi** +Le client calcule le hachage du mot de passe : +``` +mtp = MAJUSCULE(HEX(SHA256(alea + mot_de_passe_utilisateur))) +``` +La clé de déchiffrement du défi est dérivée : +``` +key_challenge = MD5(nom_utilisateur + mtp) +``` +Le client déchiffre `challenge` avec AES-256-CBC, `key_challenge` et l’IV de session. Il supprime ensuite un caractère sur deux dans le texte en clair (ex. `abcdef` → `ace`), puis rechiffre la chaîne modifiée avec les mêmes paramètres AES et l’encode en hexadécimal. + +**Étape 5 — Authentification (`POST ... / Authentification`)** +Le client envoie la réponse au défi via la fonction `Authentification`. Le serveur retourne les métadonnées utilisateur et une chaîne `cle` (entiers séparés par des virgules). Le client déchiffre `cle`, analyse les octets et calcule `MD5(octets)` pour obtenir la clé principale de chiffrement de session pour toutes les appels API ultérieurs. + +### Données échangées +Identifiant de session, identifiant d’espace, IV AES (base64 ou chiffré RSA), compteur `numeroOrdre`, sel `alea`, chaîne de défi `challenge`, clé de session (`cle`). Toutes les données sensibles sont chiffrées en transit (AES-256-CBC). + +### Identifiants et jetons +- **Identifiants** : nom d’utilisateur Pronote et mot de passe attribués par l’établissement. +- **Jetons de session** : identifiant de session (`h`), compteur de séquence (`numeroOrdre`), clé symétrique AES-256 (dérivée de `cle`). + +### Cycle de vie +- Les identifiants de session expirent après une courte période d’inactivité (généralement 15 à 30 minutes). +- La clé de session doit être utilisée pour tous les appels API ultérieurs dans la même session. +- Une réauthentification est nécessaire après expiration de la session. + +### Sécurité +1. **Surface d’attaque** : protocole propriétaire avec cryptographie personnalisée. Le point de terminaison de connexion est accessible depuis Internet. Les attaques MITM sont atténuées par HTTPS (mais RSA-1024 est utilisé pour l’échange d’IV en HTTP non sécurisé, ce qui est faible selon les normes modernes). +2. **Exposition des identifiants** : le nom d’utilisateur est transmis dans la requête d’identification (chiffré). Le mot de passe n’est jamais transmis en clair : seule la réponse au défi est envoyée. +3. **Résistance au rejeu** : modérée. Le mécanisme de défi-réponse utilise un sel aléatoire (`alea`) par session, rendant difficile le rejou d’une réponse de défi capturée. Cependant, le compteur `numeroOrdre` doit être géré avec soin pour éviter toute manipulation de séquence. +4. **Rotation** : aucune rotation automatique des jetons. La rotation des mots de passe dépend de la politique de l’établissement. Les clés de session expirent avec la session. +5. **Recommandations** : utiliser systématiquement HTTPS. Implémenter une gestion rigoureuse de `numeroOrdre`. Stocker les identifiants de manière sécurisée. Noter que la cryptographie personnalisée n’est pas équivalente à une authentification TLS standard : s’appuyer sur HTTPS pour la sécurité du transport. + +### Limitations +- La plupart des établissements secondaires français liés à un ENT ou à EduConnect bloquent les connexions directes par nom d’utilisateur/mot de passe et imposent le SSO. +- Le protocole propriétaire n’est pas officiellement documenté et peut changer sans préavis. +- La cryptographie personnalisée (AES-256-CBC avec des clés dérivées de MD5) est non standard et n’a pas fait l’objet d’un audit indépendant. + +### Statut +**Rétro-conçu** — Index Éducation ne publie pas le protocole. Toutes les connaissances proviennent de l’analyse communautaire du client web Pronote en JavaScript. + +## Méthode 3 : ENT (Espace Numérique de Travail) + +### Principe général +La plupart des collèges et lycées français accèdent à Pronote via un ENT (Espace Numérique de Travail) régional ou départemental. L’ENT agit comme fournisseur d’identité (IdP) : l’utilisateur s’authentifie auprès de l’ENT, qui établit ensuite une session avec Pronote par SSO (Single Sign-On). Pronote reçoit des identifiants délégués sans gérer directement la connexion. + +### Flux détaillé +1. **Connexion ENT** : L’utilisateur soumet ses identifiants à l’endpoint de connexion spécifique à l’ENT. Chaque ENT utilise son propre mécanisme (formulaire, CAS, SAML, Keycloak, etc.). + +2. **Redirection SSO vers Pronote** : L’ENT redirige la session authentifiée vers Pronote via un lien connecteur ou une URL proxy (ex. `/cas/proxySSO/...` ou un lien SAML direct). L’ENT valide la session et redirige vers l’instance Pronote de l’établissement avec des cookies ou assertions SAML valides. + +3. **Handshake Pronote** : La réponse HTML initiale de Pronote contient des identifiants temporaires dans l’attribut `onload` du `` : + ``` + Session initialization parameters: + e = + f = + ... + ``` + - `e` : chaîne de connexion temporaire (unique par session SSO). + - `f` : jeton d’authentification temporaire. + +4. **Challenge de session** : Le client exécute la requête standard `Identification` de Pronote (comme en Méthode 2) avec `pourENT: true`. Le challenge est résolu via une dérivation simplifiée : + ``` + key_ENT = MD5(UPPERCASE(HEX(SHA256(f)))) + ``` + Cela remplace la dérivation `MD5(username + mtp)` utilisée en connexion directe. Aucun mot de passe utilisateur n’est nécessaire : le jeton `f` émis par l’ENT sert de justificatif. + + +### Architectures ENT prises en charge + +| Architecture | Exemples | Mécanisme SSO | +|---|---|---| +| Open ENT NG / Open Digital Education | ent.iledefrance.fr, Paris Classe Numérique, Mon Collège Val d’Oise, L’Éduc de Normandie | SAML / redirection | +| Kosmos / Skolengo CAS | Mon Bureau Numérique, Mon-ENT-Occitanie, Cybercollèges42 | CAS | +| Oze ENT | (divers) | Keycloak avec endpoints proxy `/v1/ozapps` | +| WAYF / Shibboleth | e-lyco (Pays de la Loire) | SAML / Shibboleth | +| Portails personnalisés | Atrium Sud, LaClasse Lyon | Formulaires simples | + + +### Données échangées +Cookies/assertions de session ENT → identifiants temporaires Pronote (`e`, `f`) → session Pronote (via challenge-response avec clé dérivée de l’ENT). + + +### Identifiants et jetons +- **Identifiants principaux** : nom d’utilisateur et mot de passe ENT (spécifiques à chaque plateforme ENT). +- **Identifiants délégués** : `e` (connexion temporaire) et `f` (jeton) émis par Pronote après la redirection SSO. +- **Jeton de session** : clé AES-256 standard de Pronote (identique à la Méthode 2, dérivée après résolution du challenge). + + +### Cycle de vie +- Les sessions ENT sont temporaires : leur durée dépend des politiques de chaque plateforme. +- La session Pronote établie via l’ENT suit le même cycle qu’une session en connexion directe (timeout d’inactivité ~15–30 min). +- Les tâches automatisées récurrentes doivent se réauthentifier régulièrement auprès de l’ENT. + + +### Sécurité +1. **Surface d’attaque** : Les chaînes de redirection multiples (ENT → Pronote) augmentent la surface d’attaque. Chaque redirection est une opportunité d’interception de jetons. +2. **Exposition des identifiants** : Les identifiants utilisateur n’atteignent jamais Pronote directement. L’ENT agit comme intermédiaire de confiance. Le jeton `f` est éphémère (valide uniquement pour l’établissement initial de la session). +3. **Résistance au rejeu** : Le mécanisme challenge-response (identique à la Méthode 2) offre une résistance au rejeu pour la session Pronote. Les jetons de redirection SSO sont à usage unique. +4. **Rotation** : Le cycle de vie de la session ENT contrôle la rotation des identifiants. La clé de session Pronote est rotative par session. +5. **Recommandations** : Valider les certificats SSL à chaque étape de redirection. Ne pas journaliser les jetons intermédiaires. Les formulaires de connexion ENT évoluent fréquemment, ce qui peut rompre les clients automatisés. + + +### Limitations +- Les modifications des formulaires web de connexion ENT, des endpoints SAML ou de l’authentification multifacteur (MFA) rompent souvent les clients automatisés sans interface. +- Chaque ENT possède un flux de connexion différent : aucune automatisation universelle n’est possible. +- Les sessions ENT sont temporaires ; les tâches automatisées récurrentes doivent se réauthentifier régulièrement. +- Certains ENT implémentent du MFA ou des CAPTCHA empêchant une automatisation complète. + + +### Statut +Implémentation SSO standardisée par Index Éducation et les éditeurs d’ENT, mais la consommation du protocole par des clients tiers est **reverse-engineered**. + +## Méthode 4 : EduConnect + +### Principe général +EduConnect est le service national d’authentification et de gestion des accès opéré par le Ministère de l’Éducation Nationale et de la Jeunesse (MENJ). Il agit comme fournisseur d’identité (IdP) pour les élèves et parents en France. L’authentification vers Pronote s’effectue selon deux modes, selon l’infrastructure de l’établissement. + +### Flux détaillé + +### Mode A : EduConnect via ENT régional +1. Le client initie une requête vers la page de connexion de l’ENT régional avec le paramètre `selection=EDU_parent_eleve`. +2. L’ENT redirige vers l’endpoint SAML2 d’EduConnect : + ``` + https://educonnect.education.gouv.fr/idp/profile/SAML2/Unsolicited/SSO + ``` +3. Le client soumet les identifiants à EduConnect : + - `j_username` : identifiant EduConnect, + - `j_password` : mot de passe EduConnect, + - `_eventId_proceed` : chaîne vide. +4. EduConnect retourne un formulaire `SAMLResponse` signé, posté vers le service de consommation d’assertions de l’ENT (ex. `/Shibboleth.sso/SAML2/POST`). +5. L’ENT établit des cookies de session et redirige vers Pronote (le flux ENT→Pronote suit alors la Méthode 3). + +### Mode B : HubEduConnect direct (SSO Index Éducation Cloud) +Pour les établissements sans ENT régional : +1. Le client accède à la passerelle CAS centralisée d’Index Éducation : + ``` + https://hubeduconnect.index-education.net/EduConnect/cas/login?service= + ``` +2. La passerelle initie une requête SAML vers `educonnect.education.gouv.fr`. +3. L’utilisateur s’authentifie sur EduConnect (mêmes identifiants que le Mode A). +4. HubEduConnect reçoit l’assertion, la valide via une liste blanche, et redirige vers Pronote avec un ticket de service. +5. Pronote valide le ticket et établit la session (flux côté Pronote identique à la Méthode 2). + +### Données échangées +- **Mode A** : Identifiants EduConnect → assertion SAML2 → cookies ENT → handshake SSO Pronote. +- **Mode B** : Identifiants EduConnect → assertion SAML2 → ticket CAS → session Pronote. + +### Identifiants et jetons +- Identifiants principaux : identifiants nationaux EduConnect (gérés par le MENJ). +- Jetons intermédiaires : assertions SAML2 (Modes A/B), tickets de service CAS (Mode B). +- Jeton final : clé de session AES-256 Pronote (identique à la Méthode 2). + +### Cycle de vie +- Les sessions EduConnect sont temporaires (durée définie par le MENJ). +- Le ticket CAS (Mode B) est à usage unique et consommé lors de la validation par Pronote. +- La session Pronote suit un timeout d’inactivité standard (~15–30 min). + +### Sécurité +1. **Surface d’attaque** : Chaînes de redirections multiples (EduConnect → ENT/Hub → Pronote). Chaque redirection est un point d’interception. Les assertions SAML sont signées, réduisant les risques de contrefaçon. +2. **Exposition des identifiants** : Les identifiants EduConnect sont soumis directement à l’IdP EduConnect et ne transitent jamais vers Pronote ou l’ENT. Seule l’assertion SAML signée est transmise. +3. **Résistance au rejeu** : Les assertions SAML incluent des timestamps et sont à usage unique. Les tickets CAS le sont également. +4. **Rotation** : Gérée par le MENJ. Aucun mécanisme local. +5. **Recommandations** : Valider les signatures des assertions SAML. Ne pas mettre en cache les identifiants EduConnect. Les authentifications 2FA par SMS ou via FranceConnect ne sont pas gérables par des clients automatisés. + +### Limitations +- Les authentifications 2FA par SMS ou FranceConnect ne sont pas contournables par des clients programmatiques. +- Le flux repose sur des redirections HTTP multiples, fragiles et sensibles à la gestion des cookies. +- Les identifiants nationaux sont hautement sensibles : leur compromission affecte tous les services éducatifs. + +### Statut +**Service officiel (MENJ / Index Éducation)** — EduConnect est un service gouvernemental officiel. Cependant, l’interaction programmatique par des clients tiers relève du **reverse engineering**. + +## Méthode 5 : CAS (Central Authentication Service) + +### Principe général +CAS (Central Authentication Service) est le protocole SSO sous-jacent utilisé dans les réseaux scolaires propriétaires et les ENT régionaux pour autoriser l’accès à Pronote. Protocole standardisé (RFC 4520), il est implémenté côté serveur. Pronote délègue l’authentification au serveur CAS, sans gérer directement les identifiants. + +### Flux détaillé +1. **Demande de ticket de service** : + Pronote redirige l’utilisateur vers le serveur CAS : + ``` + https:///login?service=https:///pronote/.html + ``` + +2. **Authentification CAS** : + L’utilisateur soumet ses identifiants (ou effectue une authentification fédérée via EduConnect) sur le formulaire CAS. + +3. **Génération du ticket** : + CAS renvoie une redirection HTTP 302 vers Pronote avec un paramètre `ticket` : + ``` + https:///pronote/.html?ticket=ST-XXXXX-cas + ``` + Le ticket, préfixé par `ST-` (Service Ticket), est à usage unique. + +4. **Validation du ticket de service** : + Le backend Pronote contacte directement l’URL de validation CAS pour vérifier le ticket et obtenir les attributs utilisateur : + ``` + https:///serviceValidate?service=https:///pronote/.html&ticket=ST-XXXXX-cas + ``` + Le serveur CAS retourne les attributs (ex. : code UAI de l’établissement, identifiant élève). Pronote génère la session active et injecte le contexte d’initialisation dans le HTML client (mécanisme `onload` identique aux Méthodes 2 et 3). + +### Données échangées +Identifiants CAS → Validation serveur CAS → Ticket de service (`ST-XXXXX-cas`) → Réponse de validation (attributs utilisateur : UAI, identifiant élève) → Initialisation de la session Pronote. + +### Identifiants et jetons +- **Identifiants principaux** : Nom d’utilisateur et mot de passe CAS (ou identifiants fédérés EduConnect). +- **Jeton** : Ticket de service CAS (`ST-`, à usage unique). +- **Jeton de session** : Clé de session AES-256 Pronote (identique à la Méthode 2). + +### Cycle de vie +- Le ticket de service CAS est **à usage unique** : il est consommé lors de la validation et ne peut être réutilisé. +- La session Pronote résultante suit un timeout d’inactivité standard (~15–30 min). +- La durée de vie de la session CAS est régie par la politique de *Ticket-Granting Ticket* (TGT) du serveur CAS. + +### Sécurité +1. **Surface d’attaque** : Protocole standardisé et documenté. La surface d’attaque concerne principalement le formulaire de connexion CAS et la transmission du ticket (protégée par HTTPS). +2. **Exposition des identifiants** : Les identifiants sont soumis uniquement au serveur CAS — Pronote ne les voit jamais. Seul le ticket de service est transmis à Pronote. +3. **Résistance au rejeu** : Élevée — les tickets de service sont à usage unique et liés à une URL de service spécifique. +4. **Rotation** : La rotation des TGT est gérée par la politique du serveur CAS. Les tickets de service expirent rapidement (généralement en quelques secondes ou minutes). +5. **Recommandations** : Utiliser systématiquement HTTPS. Valider le paramètre `service` pour éviter le vol de tickets via des URL de service malveillantes. Appliquer une gestion rigoureuse du cycle de vie des TGT. + +### Limitations +- CAS est un protocole côté serveur : le serveur CAS doit être correctement configuré et accessible. +- L’appel `serviceValidate` s’effectue de serveur à serveur (backend Pronote → serveur CAS), nécessitant une connectivité réseau entre eux. +- Toutes les écoles n’utilisent pas CAS : certaines privilégient SAML ou des solutions SSO personnalisées. + +### Statut +**Officiel** — CAS est un protocole standardisé (RFC 4520) officiellement implémenté côté backend Pronote et serveurs CAS. + +## Méthode 6 : QR Code et jeton mobile + +### Principe général +Pronote propose un mécanisme d’appairage par QR code pour connecter les appareils mobiles sans saisir les identifiants ENT complexes. L’utilisateur génère un QR code depuis l’interface web, définit un code PIN temporaire à 4 chiffres, puis le scanne avec l’application mobile. Le QR code contient des identifiants chiffrés qui, une fois déchiffrés, permettent un échange de jeton à longue durée de vie. Ce mécanisme contourne entièrement l’authentification ENT/EduConnect. + + +### Flux détaillé + +**Phase 1 — Génération (interface web Pronote)** +1. Dans l’interface web, l’utilisateur accède à *Paramètres → Accès mobile / Application mobile*. +2. Il définit un code PIN temporaire à 4 chiffres. +3. Pronote affiche un QR code contenant un JSON chiffré : +``` +{ + "login": "", + "jeton": "", + "url": "https:///pronote/mobile.eleve.html" +} +``` + +**Phase 2 — Déchiffrement** +- Algorithme : **AES-256-CBC**. +- IV : 16 octets nuls (`0x00...`). +- Clé : `MD5(PIN_code_string)` (ex. `MD5("1234")`). +- Résultat : `login` et `jeton` en clair. + +**Phase 3 — Échange d’appairage initial** +1. Le client génère un UUID permanent (`uuidAppliMobile`). +2. Il envoie une requête à : `https:///pronote/mobile..html?login=true` (contourne la redirection ENT). +3. Requête `Identification` avec : + - `demandeConnexionAppliMobile: true` + - `demandeConnexionAppliMobileJeton: true` + - `uuidAppliMobile: ""` + - `identifiant: ""` +4. Le défi est résolu avec `` comme mot de passe (mécanisme identique à la Méthode 2). +5. Le serveur retourne `jetonConnexionAppliMobile` (jeton à longue durée de vie). + +**Phase 4 — Connexions ultérieures (auto-login)** +- `identifiant: ` +- `uuidAppliMobile: ` +- `enConnexionAppliMobile: true` +- Mot de passe pour le défi : `jetonConnexionAppliMobile` +- À chaque connexion réussie, Pronote retourne un nouveau `jetonConnexionAppliMobile` à conserver. + + +### Données échangées +QR code JSON (login + jeton chiffrés + URL) → déchiffrement → `Identification` avec drapeaux mobiles → défi-réponse → `jetonConnexionAppliMobile`. + + +### Identifiants et jetons +- **Initial** : Code PIN à 4 chiffres (temporaire, utilisé uniquement pour le déchiffrement du QR code). +- **Déchiffrés** : `login` et `jeton` extraits du QR code (usage unique pour l’appairage initial). +- **Persistants** : `uuidAppliMobile` (UUID de l’appareil, permanent) + `jetonConnexionAppliMobile` (jeton à longue durée de vie, rafraîchi à chaque connexion). + + +### Cycle de vie +- Le QR code est valide **10 minutes** après génération. +- Le `jetonConnexionAppliMobile` reste valide indéfiniment (souvent toute l’année scolaire), sauf : + - Révoqué par l’utilisateur dans les paramètres Pronote. + - Invalidé côté serveur. +- Le jeton est rafraîchi à chaque connexion réussie. + + +### Sécurité +1. **Surface d’attaque** : Le QR code est affiché à l’écran (risque de *shoulder-surfing*). Le PIN à 4 chiffres offre 10 000 combinaisons. Le `jetonConnexionAppliMobile` est un identifiant à longue durée de vie stocké sur l’appareil. +2. **Exposition des identifiants** : Le QR code contient des identifiants chiffrés. Si intercepté avant déchiffrement, l’attaquant a besoin du PIN. Une fois le `jetonConnexionAppliMobile` obtenu, aucun PIN ni mot de passe n’est requis. +3. **Résistance au rejeu** : Le `jetonConnexionAppliMobile` est un *bearer token* : toute partie en possession du jeton peut s’authentifier. Le `uuidAppliMobile` offre un lien faible avec l’appareil, mais non vérifié cryptographiquement. +4. **Rotation** : Le `jetonConnexionAppliMobile` est rafraîchi à chaque connexion, mais l’ancien reste valide jusqu’à invalidation côté serveur. Aucune expiration automatique. +5. **Recommandations** : Utiliser un PIN robuste. Traiter le `jetonConnexionAppliMobile` comme un identifiant à longue durée de vie (stockage sécurisé). En cas de vol de l’appareil, révoquer l’accès mobile dans Pronote. + + +### Limitations +- Le QR code expire après **10 minutes** : l’appairage doit être rapide. +- Le PIN à 4 chiffres est faible selon les normes modernes. +- Le `jetonConnexionAppliMobile` n’a pas d’expiration automatique : il persiste jusqu’à révocation manuelle. +- Le `uuidAppliMobile` n’est pas lié cryptographiquement à l’appareil : il peut être copié. + + +### Statut +Mécanisme **officiellement intégré** à l’application mobile Index Éducation. L’utilisation par des clients tiers repose sur de l’**ingénierie inverse**. + +## Méthode 7 : API officielle et application mobile + +### Principe général +Index Éducation ne propose pas d’API publique pour Pronote. L’accès tiers aux données repose sur l’ingénierie inverse des mêmes endpoints JSON-over-HTTP(S) utilisés par les clients web et mobile officiels. L’application mobile officielle s’authentifie via le mécanisme d’appairage par QR Code (Méthode 6) et communique via le même protocole propriétaire que le client web. + +### Flux détaillé + +L'application mobile s'authentifie via le flux d'appairage par QR Code (Méthode 6) puis communique via le même protocole JSON-over-HTTP(S) que le client web, en utilisant les endpoints `/pronote/mobile..html` et `/pronote/appelfonction///`. + +### Disponibilité d'une API développeur +- **API publique** : Inexistante. Index Éducation ne propose ni API REST ni GraphQL pour les élèves, parents ou développeurs tiers. +- **API institutionnelle/entreprise** : Des services d’intégration propriétaires sont proposés pour les systèmes partenaires (ex. connecteurs UDTS, HYPERPLANNING, ENT officiels). Ceux-ci nécessitent des accords de partenariat signés et des licences serveurs institutionnelles. + +### Authentification de l'application mobile officielle +L’application mobile officielle (iOS/Android) se connecte via les mêmes endpoints JSON-over-HTTP(S) que l’interface web mobile : +- Chemins de base : `/pronote/mobile..html` +- Dispatcher de fonctions : `/pronote/appelfonction///` +- Authentification : Utilise le flux d’appairage par QR Code (Méthode 6) et le jeton mobile persistant (`jetonConnexionAppliMobile` + `uuidAppliMobile`). + +### Données échangées +Mêmes charges utiles JSON chiffrées en AES-256-CBC que le client web (Méthode 2). La variante mobile (`mobile..html`) contourne les redirections SSO ENT/EduConnect. + +### Identifiants et jetons +- `jetonConnexionAppliMobile` : Jeton bearer à longue durée de vie (voir Méthode 6). +- `uuidAppliMobile` : UUID de l’appareil. +- Clé de session : Clé AES-256 (même dérivation que la Méthode 2). + +### Cycle de vie +Le `jetonConnexionAppliMobile` est rafraîchi à chaque connexion. La durée de vie de la session suit le même délai d’inactivité (~15–30 min) que le client web. + +### Sécurité +1. **Surface d’attaque** : Les endpoints mobiles sont accessibles publiquement. Aucune clé API ni enregistrement développeur n’existe — néanmoins, l’accès exige un matériel de session Pronote valide et, pour l’auto-login mobile, le matériel d’authentification correspondant (`login`, `uuidAppliMobile`, `jetonConnexionAppliMobile`) issu du flux d’appairage par QR Code (Méthode 6). +2. **Exposition des identifiants** : Le `jetonConnexionAppliMobile` est un jeton bearer stocké sur l’appareil. S’il est extrait, il accorde un accès complet jusqu’à révocation. +3. **Résistance au rejeu** : Faible — le jeton est basé sur un bearer. Le `uuidAppliMobile` offre un lien faible avec l’appareil, non appliqué cryptographiquement. +4. **Rotation** : Le jeton est rafraîchi à chaque connexion, mais les anciens restent valides. Aucune expiration automatique. +5. **Recommandations** : Évitez l’ingénierie inverse du protocole pour un usage en production sans comprendre les implications légales. Stockez les jetons mobiles de manière sécurisée. Soyez conscient que Index Éducation peut modifier le protocole à tout moment. + +### Limitations +- Aucune documentation ou support officiel pour les développeurs tiers. +- Protocole propriétaire susceptible de changer sans préavis. +- Les implémentations basées sur l’ingénierie inverse peuvent cesser de fonctionner après les mises à jour de Pronote. +- Le statut légal de l’ingénierie inverse est incertain dans certaines juridictions. + +### Statut +- API publique : **Inexistante**. +- Endpoints mobiles : **Ingénierie inverse** (même protocole que le client web, accessible via appairage QR Code). + +## Annexe A : Bibliothèques open source tierces + +Les bibliothèques open source ci-dessous implémentent les protocoles d'authentification Pronote décrits dans ce manuel. Ces projets sont maintenus par la communauté et ne sont pas affiliés à Index Éducation. Les fonctionnalités décrites reposent sur les informations publiques disponibles dans leurs dépôts et peuvent avoir évolué depuis la rédaction de ce document. + +| Bibliothèque | Langage | Dépôt | Méthodes d'authentification prises en charge | +|---|---|---|---| +| **pronotepy** | Python | `bain3/pronotepy` | Connexion directe, jeton mobile / QR code, CAS SSO, EduConnect (HubEduConnect direct et via 30+ ENT régionaux : Open ENT NG, Skolengo, Oze, Shibboleth/WAYF). | +| **pronote-api** | TypeScript / JS | `Litarvan/pronote-api` | Connexion directe, CAS SSO, redirections ENT, authentification par jeton. | +| **pronote-qrcode-api** | JavaScript | `Androz2091/pronote-qrcode-api` | Implémentation de référence pour le déchiffrement des QR codes Pronote et l'échange initial de jeton mobile. | +| **pawnote / Blocksnote** | TypeScript / JS | `BlocksHub/Blocksnote` | Clients TypeScript modernes implémentant le protocole Pronote complet (direct, ENT, QR code, jetons). | + +Ces bibliothèques illustrent la faisabilité des méthodes d'authentification présentées. Leur utilisation en environnement de production comporte des risques : les modifications de protocole par Index Éducation peuvent rendre les implémentations obsolètes sans préavis, et le statut juridique de l'ingénierie inverse des protocoles propriétaires varie selon les juridictions. + +## Annexe B : Tableau comparatif synthétique + +Cette annexe consolide les caractéristiques clés des 7 méthodes d'authentification Pronote en un tableau de référence unique, incluant les dimensions d'analyse de sécurité. + +| Méthode | Périmètre | Identifiants | Expiration | Complexité | Surface d'attaque | Exposition | Rejeu | Rotation | Recommandation | Statut | +|---------|-----------|--------------|------------|-----------|-------------------|-------------|-------|----------|----------------|--------| +| **URL iCal sécurisée** | EDT + devoirs (si inclus) | Jeton URL | Longue durée | Très faible | Jeton dans URL (logs, referers) | Credential longue durée | Faible (pas de nonce) | Manuelle | Stocker en secret, HTTPS, rotate à la rentrée | Officiel | +| **Connexion directe** | Complet | Identifiant + mot de passe | Session ~15–30 min | Élevée | Endpoint public, crypto custom | Mot de passe non transmis (challenge) | Modérée (alea aléatoire) | Session seulement | HTTPS obligatoire, gérer `numeroOrdre` | Reverse-engineered | +| **SSO ENT** | Complet | Identifiants ENT | Session ENT | Très élevée | Redirections multiples | Credentials via ENT (intermédiaire) | Jetons SSO single-use | Session ENT | Valider SSL à chaque redirection | Reverse-engineered | +| **SSO EduConnect** | Complet | Identifiants nationaux | Session EduConnect | Très élevée | Redirections EduConnect→ENT/Hub | Credentials MENJ (très sensibles) | SAML single-use + timestamps | MENJ | Ne pas cacher les credentials, 2FA bloque automation | Reverse-engineered | +| **CAS** | Complet | Identifiants CAS | Ticket single-use | Modérée | Login form CAS | Credentials via CAS uniquement | Ticket single-use | TGT (politique CAS) | Valider paramètre `service`, HTTPS | Officiel | +| **QR Code + jeton mobile** | Complet | PIN 4 chiffres → jeton + UUID | QR 10 min ; jeton longue durée | Modérée | QR écran, PIN faible, jeton bearer | Jeton longue durée stocké sur appareil | Faible (bearer) | À chaque login (ancien reste valide) | PIN fort, stockage sécurisé, révoquer si perte | Reverse-engineered | +| **API publique** | N/A | N/A | N/A | N/A | N/A | N/A | N/A | N/A | N/A | Inexistante | + +**Légende** : +- *Complet* : accès aux notes, emploi du temps, devoirs, absences, messagerie et paramètres. +- *Reverse-engineered* : protocole non publié officiellement par Index Éducation, basé sur l'analyse communautaire. +- *Officiel* : mécanisme fourni et pris en charge par Index Éducation. diff --git a/docs/pronote-auth.pdf b/docs/pronote-auth.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6fd2ac75c2ec0eae924359db93646b1db6d29bd0 GIT binary patch literal 249546 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

*_8_ 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 G8pWH*$e;K6Z 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%0A2*; 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%0Do3KfmGcTf-4khp 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 Ror|4~8;O!qTu}n)zW~MZa1;Ol literal 0 HcmV?d00001 diff --git a/docs/pronote-auth.tex b/docs/pronote-auth.tex new file mode 100644 index 0000000..8e91424 --- /dev/null +++ b/docs/pronote-auth.tex @@ -0,0 +1,56 @@ +%!TEX TS-program = lualatex +%!TEX encoding = UTF-8 Unicode +%% ============================================================================= +%% FICHIER : pronote-auth.tex +%% DESCRIPTION : Manuel technique & académique — Authentification Pronote +%% TITRE : Authentification Pronote — Référence technique +%% AUTEUR : Équipe Architecture & Sécurité +%% DATE : Septembre 2026 +%% +%% INSTRUCTIONS DE COMPILATION : +%% Ce document est conçu pour être compilé avec LuaLaTeX : +%% lualatex -interaction=nonstopmode docs/pronote-auth.tex +%% lualatex -interaction=nonstopmode docs/pronote-auth.tex +%% (Deux passes nécessaires pour la génération de la table des matières, +%% des références croisées et des hyperliens). +%% +%% CHOIX TYPOGRAPHIQUES & DESIGN : +%% 1. Police Principale (Sérif) : EB Garamond +%% Justification : Référence historique et académique de la typographie +%% française, apportant élégance, chaleur et excellente lisibilité sur +%% papier ou écran haute densité. +%% 2. Police Sans-Sérif : Carlito (compatible métriquement Calibri) +%% Justification : Modernité sobre, sans empattement, idéale pour les +%% titres techniques, étiquettes de tableaux et métadonnées. +%% 3. Police Chasse Fixe (Monospace) : DejaVu Sans Mono +%% Justification : Rendu précis des glyphes informatiques, alignement +%% parfait pour les algorithmes, JSON et requêtes HTTP. +%% 4. Palette Chromatique : Palette "Bleu Institutionnel & Ardoise" +%% - primaryNavy (#142D55) : Titres, identité principale, reliure visuelle +%% - secondarySlate (#465F7D) : Sous-titres, filets, structure +%% - accentSteel (#007396) : Liens hypertexte, repères visuels +%% - Teintes fonctionnelles douces pour encadrés et callouts. +%% ============================================================================= + +\input{preamble} + +\begin{document} + +\input{frontmatter} + +\include{ch-introduction} +\include{ch-overview} +\include{ch-method1} +\include{ch-method2} +\include{ch-method3} +\include{ch-method4} +\include{ch-method5} +\include{ch-method6} +\include{ch-method7} + +\appendix + +\include{annex-libraries} +\include{annex-comparison} + +\end{document} From 7dc48f6f434a9e3ecaf87d8b72a8a01ff08d21d8 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Thu, 10 Sep 2026 11:30:33 +0200 Subject: [PATCH 3/5] =?UTF-8?q?docs:=20nettoyer=20AGENTS.md=20des=20r?= =?UTF-8?q?=C3=A8gles=20redondantes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retrait des sections de règles de développement de AGENTS.md, désormais centralisées dans orchestrator.md. Co-Authored-By: Warp --- AGENTS.md | 152 ------------------------------------------------------ 1 file changed, 152 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index aae347a..be8ff2a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -243,155 +243,3 @@ Le projet suit un plan séquentiel en **15 jalons** (M1-M15) décrits dans [`TOD |----------|------| | [`GUIDE_DEV_PYTHON.md`](./GUIDE_DEV_PYTHON.md) | Spécification complète (architecture, modèles, parsing, déploiement) | | [`TODO.md`](./TODO.md) | Plan de développement détaillé (15 jalons) | - ---- - -## 9. Agents OpenCode - -Cette section s'applique uniquement lorsque le travail est exécuté avec le système d'agents d'OpenCode. - -Les rôles d'agents disponibles pour ce projet sont les suivants : - -- `@architect` : Arbitrages d'architecture et choix techniques structurants. **Ne produit pas de code.** -- `@coder` : Écrit et modifie du code, de la configuration et des scripts. **Ne valide pas** (ruff, mypy, pytest) — c'est le rôle de `@verifier`. **Ne diagnostique pas** — c'est le rôle de `@debugger`. -- `@debugger` : Reproduit un symptôme et établit sa cause profonde. **Ne modifie pas le code.** -- `@explorer` : Explore le dépôt en lecture seule. **Ne modifie rien, n'exécute pas de commandes.** -- `@orchestrator` : Compréhension globale, définition des jalons, coordination et garantie du résultat. **N'écrit pas de code.** -- `@planner` : Transforme une demande complexe en unités exécutables. **Ne dirige aucun technicien.** -- `@reviewer` : Revues indépendantes de correction, régression, contrats et maintenabilité. **Ne modifie pas le code.** -- `@security-auditor` : Audit indépendant d'une surface de sécurité. **Ne modifie pas le code.** -- `@tech-writer` : Rédige et maintient la documentation. **N'écrit pas de code applicatif.** -- `@test-engineer` : Conçoit, écrit et exécute des tests ciblés. **N'écrit pas de code de production.** -- `@ui-designer` : Conçoit et implémente les interfaces Web et terminal. -- `@verifier` : Vérifie indépendamment le comportement livré, les régressions et le respect des conventions (ruff, mypy, pytest, bandit, idempotence, mode dégradé). **Ne modifie pas le code.** -- `@web-explorer` : Recherche et extrait des sources Web vérifiables. **Ne modifie pas le dépôt.** - -> **Note** : Ne pas utiliser `@coder` pour les tâches de documentation (`@tech-writer`) ni pour les tests (`@test-engineer`). - -### Séparation des rôles - -| Type de tâche | Agent responsable | Ne pas confier à | -|---|---|---| -| Écrire/modifier du code | `@coder` | `@verifier`, `@explorer` | -| Valider (ruff, mypy, pytest, bandit) | `@verifier` | `@coder` | -| Diagnostiquer un bug | `@debugger` | `@coder` | -| Écrire un test | `@test-engineer` | `@coder` | -| Rédiger de la documentation | `@tech-writer` | `@coder` | -| Explorer le dépôt (lecture) | `@explorer` | `@coder`, `@verifier` | -| Arbitrage technique structurant | `@architect` | `@coder`, `@planner` | -| Revue de code | `@reviewer` | `@coder`, `@verifier` | -| Audit de sécurité | `@security-auditor` | `@coder`, `@verifier` | -| Recherche web | `@web-explorer` | `@explorer` | -| Découpage de travail complexe | `@planner` | `@coder` | - ---- - -## 10. Workflow de modification - -1. Lire la demande, [`TODO.md`](./TODO.md), les sections pertinentes de - [`GUIDE_DEV_PYTHON.md`](./GUIDE_DEV_PYTHON.md), `git status` et les fichiers concernés. -2. Préserver les changements existants de l'utilisateur. -3. Pour une correction, reproduire d'abord le défaut avec un test automatisé lorsque c'est raisonnable. -4. Faire une modification étroite et cohérente, en respectant les conventions du projet - (idempotence, modes explicites stricts et repli iCal → `pronotepy` uniquement en mode `auto`). -5. Faire vérifier le comportement par `@verifier` (ruff, mypy, pytest, bandit) et les cas d'erreur, notamment : - - Succès de la synchronisation Pronote → CalDAV/XMPP. - - Repli d'iCal vers `pronotepy` en mode `auto`, sans repli dans les modes explicites. - - Distinction entre résultat vide et échec des sources. - - Gestion des erreurs explicites sans fuite dans les causes ou tracebacks. -6. Mettre à jour la documentation et les exemples dans le même changement si leur comportement public évolue. -7. Cocher dans [`TODO.md`](./TODO.md) uniquement les éléments entièrement réalisés et validés. -8. Terminer avec un *handoff* concis : fichiers modifiés, validations exécutées, limites et prochaine étape. - -> **Pour les changements larges ou risqués** : Produire d'abord un audit ou un aperçu. -> **Règle de commit** : Ne pas committer sans autorisation explicite. Une autorisation de commit ne vaut pas autorisation de push. - ---- - -## 11. Branches et commits - -- Une évolution cohérente se fait sur une **branche dédiée**. -- Nommer les branches selon le format `/`, où `` est l'un des suivants : - `feature`, `fix`, `docs`, `chore` ou `refactor`. -- Partir de l'état validé de la branche principale (`main` ou `dev`), sauf demande explicite. -- Garder **un commit atomique par objectif vérifiable**. Utiliser les préfixes conventionnels pour les messages de commit : - - `feat:` pour une nouvelle fonctionnalité. - - `fix:` pour une correction de bug. - - `docs:` pour une mise à jour de documentation. - - `chore:` pour une tâche de maintenance. - - `refactor:` pour une refactorisation de code. -- Quand un agent a contribué au changement, ajouter un *trailer* Git standard au commit : - ``` - Co-authored-by: / - ``` -- Avant un commit autorisé, vérifier : - - `git status` (fichiers modifiés attendus). - - Le diff complet (`git diff`). - - L'absence de secret ou de configuration locale dans le diff. - - Les validations pertinentes (`ruff`, `mypy`, `pytest`, `bandit`). - - `git diff --check` (pas de problèmes d'espaces blancs). -- Après le commit, rapporter : - - Le hash du commit. - - Le contenu du commit. - - Les validations exécutées. - -> **Règle absolue** : Ne jamais pousser (`git push`) sans demande distincte et explicite. - ---- - -## 12. Définition de terminé - -Un changement est considéré comme **terminé** lorsque : - -- Le cas nominal et les échecs pertinents sont testés (ex. : synchronisation réussie, repli - iCal → `pronotepy` en mode `auto`, modes explicites stricts, erreurs explicites). -- Les outils de validation (`ruff`, `mypy`, `pytest`, `bandit`) passent sans erreur. -- Aucun secret ni configuration locale n'apparaît dans le diff ou les fichiers suivis. -- La documentation reste cohérente avec le code (ex. : mise à jour des exemples, des contrats ou des décisions d'architecture). -- Le *handoff* distingue clairement : - - Ce qui a été vérifié localement (ex. : tests unitaires, linter). - - Ce qui nécessite encore une vérification manuelle (ex. : tests d'intégration avec un serveur CalDAV réel). - ---- - -## 13. Versionnage et releases - -### Politique de versionnage - -Le projet suit **Semantic Versioning** (semver.org v2.0.0). Phase actuelle : `0.x` (pré-`1.0.0`). - -| Changement | Incrément | -|------------|----------| -| Défaut constaté au déploiement | Patch (`0.1.Z`) — correction rétrocompatible | -| Ajout ou cassure en phase `0.x` | Minor (`0.Y.0`) | -| Déploiement réel validé | `1.0.0` | - -### Règle absolue de validation - -**Aucune montée de version (tag + release) ne peut être effectuée -sans validation préalable en environnement réel.** Les tests automatisés et la revue de code -ne suffisent pas ; le correctif ou la fonctionnalité doit avoir été testé avec succès -sur le serveur de production (ou un environnement équivalent) avant de tagger. - -### Procédure de release - -1. **Valider en environnement réel** : le correctif ou la fonctionnalité est testé - sur le serveur de production. -2. **Mettre à jour `pyproject.toml`** : incrémenter le champ `version` à la nouvelle version. -3. **Mettre à jour `CHANGELOG.md`** : ajouter une entrée sous le format - [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) avec la nouvelle version et la date. -4. **Committer** : un commit `chore: monter en version x.y.z` regroupe - les mises à jour de `pyproject.toml` et `CHANGELOG.md`. -5. **Tagger** : créer un tag annoté `vx.y.z` sur le commit de version. -6. **Pousser le tag** : `git push origin vx.y.z`. -7. **Créer la release** sur Gitea avec le changelog correspondant. - -### Cohérence des versions - -Les trois sources de version doivent toujours être synchronisées au moment d'un tag : -- Le tag Git (`vx.y.z`) -- `pyproject.toml` (`version = "x.y.z"`) -- `CHANGELOG.md` (`## [x.y.z] - YYYY-MM-DD`) - -> **Rappel** : Ne jamais créer un tag sans avoir d'abord mis à jour -> `pyproject.toml` et `CHANGELOG.md`. From d26cef8d3da4a8cefcbc120fc8061e92a47b0ff0 Mon Sep 17 00:00:00 2001 From: Antoine Van Elstraete Date: Thu, 10 Sep 2026 12:10:42 +0200 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20persister=20le=20token=20apr=C3=A8s?= =?UTF-8?q?=20chaque=20op=C3=A9ration=20de=20donn=C3=A9es=20+=20corriger?= =?UTF-8?q?=20doc=20QR=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cause racine : pronotepy peut rafraîchir (rotater) le token en mémoire pendant l'exécution via refresh() automatique après une PronoteAPIError. L'ancien code ne persistait les credentials qu'après le login initial, pas après les opérations de données. Le token roté en mémoire était perdu → au run suivant, token_login échouait avec le token périmé (KeyError 'dataSec'). Correction : - PronoteClient._persist_credentials() : méthode centralisée qui persiste export_credentials() après chaque opération réussie (get_lessons, get_homeworks, get_messages, get_informations) - Le token rafraîchi par le serveur pendant l'exécution est maintenant toujours persisté, même si le pipeline échoue ensuite Documentation : - .env.example : variables QR plus visibles (exemple qr_token décommentable) - AGENTS.md : QR code depuis le site web Pronote (pas l'app mobile), persistance après chaque opération de données - Wiki GuidePronote : procédure corrigée (site web, pas app Android/iOS), mention de la persistance après chaque opération Tests : 5 nouveaux tests de persistance (691 passés, couverture 94.92%) --- .env.example | 16 +- AGENTS.md | 9 +- pronote_sync/sources/pronote/client.py | 29 +++- tests/unit/test_pronote_client.py | 200 ++++++++++++++++++++++++- 4 files changed, 240 insertions(+), 14 deletions(-) diff --git a/.env.example b/.env.example index 6c35742..0b6ed02 100644 --- a/.env.example +++ b/.env.example @@ -11,17 +11,17 @@ PRONOTE_AGENDA_SOURCE=auto PRONOTE_HOMEWORK_SOURCE=auto PRONOTE_MESSAGES_SOURCE=pronotepy -# Mode d'authentification Pronote -# "password" (défaut) : authentification classique URL + identifiant + mot de passe -# "qr_token" : authentification par QR code puis token persistant +# --- Authentification Pronote --- +# Mode d'authentification : "password" (défaut) ou "qr_token" +# qr_token : pour les instances utilisant HubEduConnect/EduConnect où le mot de passe échoue +# Voir le wiki GuidePronote pour la procédure d'enrôlement QR code PRONOTE_AUTH_MODE=password -# Fichier JSON du QR code Pronote (enrôlement initial, mode qr_token uniquement) -# À générer depuis l'application mobile Pronote. Le QR code expire ~10 minutes. +# --- Mode qr_token (décommenter et renseigner pour l'authentification par QR code) --- +# Le QR code se génère sur le site web Pronote (espace parent > paramètres > QR code) +# Le QR code expire ~10 minutes après génération +# PRONOTE_AUTH_MODE=qr_token # PRONOTE_QR_CODE_FILE=/path/to/qr_code.json - -# PIN à 4 chiffres pour l'enrôlement QR code (mode qr_token uniquement) -# SENSIBLE : ne jamais committer cette valeur # PRONOTE_QR_PIN=1234 # --- CalDAV --- diff --git a/AGENTS.md b/AGENTS.md index be8ff2a..e994041 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -153,10 +153,15 @@ pronote-sync --dry-run - `qr_token` : authentification par QR code puis token persistant (pour les instances Pronote utilisant HubEduConnect/EduConnect où l'authentification par mot de passe échoue). - En mode `qr_token`, le premier login utilise `pronotepy.qrcode_login(qr_code, pin, uuid)` avec - les paramètres `PRONOTE_QR_CODE_FILE` (chemin du JSON QR) et `PRONOTE_QR_PIN` (PIN SecretStr). + les paramètres `PRONOTE_QR_CODE_FILE` (chemin du JSON QR) et `PRONOTE_QR_PIN` (PIN SecretStr). Le + QR code est obtenu depuis le site web Pronote (espace parent → paramètres → QR code), pas depuis + l'application mobile. - Après chaque login réussi, les credentials exportées par `pronotepy.export_credentials()` sont persistées dans `.pronote_auth_state.json` (permissions `0600`, format JSON versionné, écriture - atomique). Le token rotate à chaque session — le fichier doit être mis à jour après chaque run. + atomique). Le token rotate à chaque session et peut également être rafraîchi pendant l'exécution + (refresh automatique pronotepy après une `PronoteAPIError`). Les credentials sont persistées après + chaque login réussi **et après chaque opération de données réussie** (agenda, devoirs, messages, + informations) pour garantir la persistance du token valide. - Les logins suivants utilisent `pronotepy.token_login(**credentials)` avec le token persisté. - En cas d'échec de `token_login` (token expiré/invalide), une `PronoteAuthRotationError` est levée. Cette erreur se propage sans wrapping à travers `PronoteFetcher` et `fetch_step` jusqu'à diff --git a/pronote_sync/sources/pronote/client.py b/pronote_sync/sources/pronote/client.py index 1c2d024..86719c1 100644 --- a/pronote_sync/sources/pronote/client.py +++ b/pronote_sync/sources/pronote/client.py @@ -234,6 +234,24 @@ class PronoteClient: self._client = self._connect_password() return self._client + def _persist_credentials(self) -> None: + """Persiste les credentials d'authentification après une opération réussie. + + Le token pronotepy peut être rafraîchi (rotaté) par le serveur lors d'un + appel de données (agenda, devoirs, messages). Cette méthode persiste + systématiquement les credentials courantes pour garantir la disponibilité + du token valide au prochain run. + + Ne fait rien si aucun :class:`PronoteAuthState` n'est configuré (mode + ``password``) ou si le client n'est pas connecté. + """ + if self._auth_state is None or self._client is None: + return + try: + self._auth_state.save(self._client.export_credentials()) + except Exception as exc: + logger.debug("Échec de la persistance des credentials : %s", redact_exception(exc)) + def _connect_password(self) -> pronotepy.Client: """Connecte le client ``pronotepy`` en mode ``password``. @@ -298,7 +316,8 @@ class PronoteClient: try: client = client_class.token_login(**creds) if client.logged_in: - self._auth_state.save(client.export_credentials()) + self._client = client + self._persist_credentials() return client # logged_in est False — le token est invalide raise PronoteAuthRotationError( @@ -324,8 +343,8 @@ class PronoteClient: # Enrôlement : premier login via QR code (aucun credential persisté) client = self._enroll_qr_code(client_class) # Persister le token rotaté immédiatement - if self._auth_state is not None: - self._auth_state.save(client.export_credentials()) + self._client = client + self._persist_credentials() return client def _enroll_qr_code(self, client_class: type[pronotepy.Client]) -> pronotepy.Client: @@ -423,6 +442,7 @@ class PronoteClient: read=message.seen, ) ) + self._persist_credentials() return messages except ( pronotepy.PronoteAPIError, @@ -461,6 +481,7 @@ class PronoteClient: read=info.read, ) ) + self._persist_credentials() return messages except ( pronotepy.PronoteAPIError, @@ -529,6 +550,7 @@ class PronoteClient: content=content.description if content is not None else None, ) ) + self._persist_credentials() return lessons def get_homeworks(self, start: date, end: date) -> list[Homework]: @@ -566,4 +588,5 @@ class PronoteClient: html=hw.description, ) ) + self._persist_credentials() return homeworks diff --git a/tests/unit/test_pronote_client.py b/tests/unit/test_pronote_client.py index ad4185d..f5d5d1d 100644 --- a/tests/unit/test_pronote_client.py +++ b/tests/unit/test_pronote_client.py @@ -11,6 +11,7 @@ import json import logging from datetime import date, datetime from pathlib import Path +from unittest.mock import MagicMock import pronotepy import pytest @@ -1162,7 +1163,7 @@ def test_no_raw_secrets_in_logs( auth_state.load.return_value = { "pronote_url": "https://pronote.example.com", "username": "testuser", - "password": sentinel_token, + "password": sentinel_token, # pragma: allowlist secret "uuid": "old-uuid", } @@ -1199,4 +1200,201 @@ def test_no_raw_secrets_in_logs( assert sentinel_jeton not in caplog.text +# --- Persistence of credentials after data operations --- + + +def _make_auth_state_mock(mocker: pytest_mock.MockerFixture) -> MagicMock: + """Retourne un mock de PronoteAuthState sans credentials persistés. + + :param mocker: Fixture pytest-mock pour le mocking. + :return: Mock de PronoteAuthState (load → None). + """ + auth_state = mocker.MagicMock(spec=PronoteAuthState) + auth_state.load.return_value = None + return auth_state # type: ignore[no-any-return] + + +def _make_lessons_mock_client(mocker: pytest_mock.MockerFixture) -> MagicMock: + """Retourne un mock client pronotepy retournant un cours exploitable. + + :param mocker: Fixture pytest-mock pour le mocking. + :return: Mock client avec une leçon mockée. + """ + mock_client = mocker.MagicMock() + mock_lesson = mocker.MagicMock() + mock_lesson.id = "lesson-789" + mock_lesson.start = datetime(2024, 9, 1, 8, 0, 0) + mock_lesson.end = datetime(2024, 9, 1, 9, 30, 0) + mock_lesson.subject = mocker.MagicMock() + mock_lesson.subject.name = "Maths" + mock_lesson.teacher_names = ["Prof A"] + mock_lesson.classrooms = ["Salle 101"] + mock_lesson.group_name = "Classe 1" + mock_lesson.canceled = False + mock_content = mocker.MagicMock() + mock_content.description = "Lesson content" + mock_lesson.content = mock_content + mock_client.lessons.return_value = [mock_lesson] + return mock_client # type: ignore[no-any-return] + + +def test_persist_credentials_after_get_lessons( + mocker: pytest_mock.MockerFixture, pronote_settings: PronoteSettings +) -> None: + """Vérifie que get_lessons persiste les credentials (token rotaté) après succès. + + ``export_credentials()`` retourne des valeurs différentes à chaque appel : + la dernière valeur (token rotaté) doit être celle passée à ``save``. + + :param mocker: Fixture pytest-mock pour le mocking. + :param pronote_settings: Paramètres Pronote valides. + :return: None + """ + mock_client = _make_lessons_mock_client(mocker) + creds_sequence = iter( + [ + {"password": "token-1"}, # pragma: allowlist secret + {"password": "token-2"}, # pragma: allowlist secret + ] + ) + mock_client.export_credentials.side_effect = lambda: next(creds_sequence) + mocker.patch.object(PronoteClient, "_connect", return_value=mock_client) + + auth_state = _make_auth_state_mock(mocker) + client = PronoteClient(pronote_settings, auth_state=auth_state) + client._client = mock_client + lessons = client.get_lessons(date(2024, 9, 1), date(2024, 9, 30)) + assert len(lessons) == 1 + # Deuxième opération sur le même client : export_credentials() retourne + # alors le token rotaté, qui doit être celui persisté. + client.get_lessons(date(2024, 9, 1), date(2024, 9, 30)) + + assert auth_state.save.call_count == 2 + auth_state.save.assert_called_with({"password": "token-2"}) # pragma: allowlist secret + + +def test_persist_credentials_after_get_homeworks( + mocker: pytest_mock.MockerFixture, pronote_settings: PronoteSettings +) -> None: + """Vérifie que get_homeworks persiste les credentials (token rotaté) après succès. + + :param mocker: Fixture pytest-mock pour le mocking. + :param pronote_settings: Paramètres Pronote valides. + :return: None + """ + mock_client = mocker.MagicMock() + mock_hw = mocker.MagicMock() + mock_hw.id = "hw-101" + mock_hw.subject = mocker.MagicMock() + mock_hw.subject.name = "Maths" + mock_hw.date = date(2024, 9, 15) + mock_hw.description = "Do your homework" + mock_client.homework.return_value = [mock_hw] + creds_sequence = iter( + [ + {"password": "token-1"}, # pragma: allowlist secret + {"password": "token-2"}, # pragma: allowlist secret + ] + ) + mock_client.export_credentials.side_effect = lambda: next(creds_sequence) + mocker.patch.object(PronoteClient, "_connect", return_value=mock_client) + + auth_state = _make_auth_state_mock(mocker) + client = PronoteClient(pronote_settings, auth_state=auth_state) + client._client = mock_client + homeworks = client.get_homeworks(date(2024, 9, 1), date(2024, 9, 30)) + assert len(homeworks) == 1 + # Deuxième opération sur le même client : export_credentials() retourne + # alors le token rotaté, qui doit être celui persisté. + client.get_homeworks(date(2024, 9, 1), date(2024, 9, 30)) + + assert auth_state.save.call_count == 2 + auth_state.save.assert_called_with({"password": "token-2"}) # pragma: allowlist secret + + +def test_persist_credentials_not_called_in_password_mode( + mocker: pytest_mock.MockerFixture, pronote_settings: PronoteSettings +) -> None: + """Vérifie qu'aucune persistance n'est tentée sans PronoteAuthState (mode password). + + :param mocker: Fixture pytest-mock pour le mocking. + :param pronote_settings: Paramètres Pronote valides. + :return: None + """ + mock_client = _make_lessons_mock_client(mocker) + mocker.patch.object(PronoteClient, "_connect", return_value=mock_client) + + client = PronoteClient(pronote_settings, auth_state=None) + client._client = mock_client + lessons = client.get_lessons(date(2024, 9, 1), date(2024, 9, 30)) + + assert len(lessons) == 1 # Aucune exception levée malgré auth_state=None + + +def test_persist_credentials_called_after_login( + mocker: pytest_mock.MockerFixture, +) -> None: + """Vérifie que _connect_qr_token persiste toujours les credentials après login. + + Comportement historique préservé via la nouvelle méthode + ``_persist_credentials()``. + + :param mocker: Fixture pytest-mock pour le mocking. + :return: None + """ + creds = { + "pronote_url": "https://pronote.example.com", + "username": "testuser", + "password": "persisted-token", # pragma: allowlist secret + "uuid": "persisted-uuid", + } + auth_state = _make_auth_state_mock(mocker) + auth_state.load.return_value = creds + + mock_client = mocker.MagicMock() + mock_client.logged_in = True + mock_client.export_credentials.return_value = {**creds, "uuid": "rotated-uuid"} + mocker.patch("pronotepy.ParentClient.token_login", return_value=mock_client) + + settings = PronoteSettings( + url="https://pronote.example.com", + username="testuser", + password=SecretStr("testpass"), + ent=None, + account_type="parent", + auth_mode="qr_token", + ) + client = PronoteClient(settings, auth_state=auth_state) + client._connect() + + auth_state.save.assert_called_once_with({**creds, "uuid": "rotated-uuid"}) + + +def test_persist_credentials_failure_does_not_crash( + mocker: pytest_mock.MockerFixture, + pronote_settings: PronoteSettings, + caplog: pytest.LogCaptureFixture, +) -> None: + """Vérifie qu'un échec de persistance n'interrompt pas la récupération des cours. + + :param mocker: Fixture pytest-mock pour le mocking. + :param pronote_settings: Paramètres Pronote valides. + :param caplog: Fixture pytest de capture des logs. + :return: None + """ + mock_client = _make_lessons_mock_client(mocker) + mocker.patch.object(PronoteClient, "_connect", return_value=mock_client) + + auth_state = _make_auth_state_mock(mocker) + auth_state.save.side_effect = OSError("disque plein") + client = PronoteClient(pronote_settings, auth_state=auth_state) + client._client = mock_client + + with caplog.at_level(logging.DEBUG, logger="pronote_sync.sources.pronote.client"): + lessons = client.get_lessons(date(2024, 9, 1), date(2024, 9, 30)) + + assert len(lessons) == 1 + assert "persistance des credentials" in caplog.text + + # Ensure trailing newline From 4df930bfe6d35df8c6547a7aad92c2e570c29ff6 Mon Sep 17 00:00:00 2001 From: Antoine Van Elstraete Date: Thu, 10 Sep 2026 13:34:51 +0200 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20persister=20le=20token=20m=C3=AAme?= =?UTF-8?q?=20dans=20le=20chemin=20d'erreur=20de=20get=5Finformations/get?= =?UTF-8?q?=5Fmessages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cause racine : quand pronotepy reçoit une PronoteAPIError (ex. code 20 « page expirée »), il appelle refresh() en interne, qui peut roter le token en mémoire (client.password mis à jour avec un nouveau jetonConnexionAppliMobile). Le retry peut aussi échouer — l'exception atteint get_informations()/get_messages() qui l'attrapent et retournent [] (mode dégradé). Mais _persist_credentials() n'était appelé que dans le chemin de SUCCÈS — le token rafraîchi en mémoire n'était jamais persisté. Au run suivant, token_login utilisait le token périmé → échec KeyError 'dataSec' → PronoteAuthRotationError. Correction : appeler _persist_credentials() aussi dans le chemin d'erreur de get_informations() et get_messages(), avant le return []. L'ancien token est déjà invalidé côté serveur lors du refresh — ne pas persister le nouveau token garantit la perte du seul token valide. Tests : 3 nouveaux tests (persistance dans le chemin d'erreur, sauvegarde du token rafraîchi sur erreur). 694 passés, couverture 94.93%. --- pronote_sync/sources/pronote/client.py | 2 + tests/unit/test_pronote_client.py | 93 ++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/pronote_sync/sources/pronote/client.py b/pronote_sync/sources/pronote/client.py index 86719c1..57051c3 100644 --- a/pronote_sync/sources/pronote/client.py +++ b/pronote_sync/sources/pronote/client.py @@ -455,6 +455,7 @@ class PronoteClient: "Échec de la récupération des messages Pronote : %s", redact_exception(exc), ) + self._persist_credentials() return [] def get_informations(self) -> list[Message]: @@ -494,6 +495,7 @@ class PronoteClient: "Échec de la récupération des informations Pronote : %s", redact_exception(exc), ) + self._persist_credentials() return [] def get_lessons(self, start: date, end: date) -> list[Lesson]: diff --git a/tests/unit/test_pronote_client.py b/tests/unit/test_pronote_client.py index f5d5d1d..838ef2b 100644 --- a/tests/unit/test_pronote_client.py +++ b/tests/unit/test_pronote_client.py @@ -1397,4 +1397,97 @@ def test_persist_credentials_failure_does_not_crash( assert "persistance des credentials" in caplog.text +def test_persist_credentials_in_error_path_get_informations( + mocker: pytest_mock.MockerFixture, + pronote_settings: PronoteSettings, +) -> None: + """Vérifie que get_informations persiste les credentials même en mode dégradé. + + Le refresh pronotepy peut avoir roté le token en mémoire alors que la + requête échoue ; la persistance doit intervenir aussi dans le chemin + d'erreur, avant le ``return []``. + + :param mocker: Fixture pytest-mock pour le mocking. + :param pronote_settings: Paramètres Pronote valides. + :return: None + """ + mock_client = mocker.MagicMock() + mock_client.information_and_surveys.side_effect = pronotepy.PronoteAPIError("API error") + mock_client.export_credentials.return_value = { + "password": "token-apres-refresh", # pragma: allowlist secret + } + mocker.patch.object(PronoteClient, "_connect", return_value=mock_client) + + auth_state = _make_auth_state_mock(mocker) + client = PronoteClient(pronote_settings, auth_state=auth_state) + client._client = mock_client + messages = client.get_informations() + + assert messages == [] + auth_state.save.assert_called_once_with( + {"password": "token-apres-refresh"} # pragma: allowlist secret + ) + + +def test_persist_credentials_in_error_path_get_messages( + mocker: pytest_mock.MockerFixture, + pronote_settings: PronoteSettings, +) -> None: + """Vérifie que get_messages persiste les credentials même en mode dégradé. + + :param mocker: Fixture pytest-mock pour le mocking. + :param pronote_settings: Paramètres Pronote valides. + :return: None + """ + mock_client = mocker.MagicMock() + mock_client.discussions.side_effect = pronotepy.PronoteAPIError("API error") + mock_client.export_credentials.return_value = { + "password": "token-apres-refresh", # pragma: allowlist secret + } + mocker.patch.object(PronoteClient, "_connect", return_value=mock_client) + + auth_state = _make_auth_state_mock(mocker) + client = PronoteClient(pronote_settings, auth_state=auth_state) + client._client = mock_client + messages = client.get_messages() + + assert messages == [] + auth_state.save.assert_called_once_with( + {"password": "token-apres-refresh"} # pragma: allowlist secret + ) + + +def test_persist_credentials_saves_refreshed_token_on_error( + mocker: pytest_mock.MockerFixture, + pronote_settings: PronoteSettings, +) -> None: + """Vérifie que le token roté par le refresh pronotepy est persisté en cas d'erreur. + + ``export_credentials()`` retourne un mot de passe (token) différent du + token initial, simulant la rotation opérée par ``refresh()`` : c'est ce + nouveau credential qui doit être passé à ``save``. + + :param mocker: Fixture pytest-mock pour le mocking. + :param pronote_settings: Paramètres Pronote valides. + :return: None + """ + initial_creds = {"password": "token-initial"} # pragma: allowlist secret + refreshed_creds = {"password": "token-rafraichi"} # pragma: allowlist secret + mock_client = mocker.MagicMock() + mock_client.information_and_surveys.side_effect = pronotepy.PronoteAPIError("API error") + mock_client.export_credentials.return_value = refreshed_creds + mocker.patch.object(PronoteClient, "_connect", return_value=mock_client) + + auth_state = _make_auth_state_mock(mocker) + client = PronoteClient(pronote_settings, auth_state=auth_state) + client._client = mock_client + messages = client.get_informations() + + assert messages == [] + assert auth_state.save.call_count == 1 + saved = auth_state.save.call_args.args[0] + assert saved == refreshed_creds + assert saved != initial_creds + + # Ensure trailing newline