Commit Graph

7 Commits

Author SHA1 Message Date
d26cef8d3d fix: persister le token après chaque opération de données + corriger doc QR code
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%)
2026-09-10 12:10:42 +02:00
0363898669 feat: authentification QR code / token pour Pronote
Ajoute le mode d'authentification PRONOTE_AUTH_MODE=qr_token comme alternative
au mode password pour les instances Pronote utilisant HubEduConnect/EduConnect
où l'authentification par mot de passe échoue (CAPTCHA, MFA, flux SAML).

Nouveaux éléments :
- PronoteSettings : auth_mode, qr_code_file, qr_pin (SecretStr)
- PronoteAuthState : persistance du token rotatif dans .pronote_auth_state.json
  (écriture atomique, permissions 0600, symlink-safe via O_EXCL|O_NOFOLLOW)
- PronoteClient._connect_qr_token() : token_login avec creds persistés,
  qrcode_login pour l'enrôlement initial, export_credentials persisté après
  chaque login réussi
- PronoteAuthRotationError : levée en cas d'échec de rotation du token,
  propagée sans wrapping à travers PronoteFetcher et fetch_step jusqu'à
  PipelineRunner.run() qui notifie via XMPP (si canal disponible et dry_run inactif)
- _is_pronotepy_configured() mode-aware : qr_token ne requiert que PRONOTE_URL
- _collect_auth_secrets() : redaction des secrets explicites (token, PIN, jeton QR)
  dans tous les logs du chemin d'authentification

Documentation :
- .env.example : PRONOTE_AUTH_MODE, PRONOTE_QR_CODE_FILE, PRONOTE_QR_PIN
- AGENTS.md : contrat d'authentification QR code / token
- Wiki GuidePronote : section enrôlement, exécutions suivantes, ré-enrôlement

Tests (686 passés, couverture 94.87%) :
- 5 tests config QR, 9 tests auth_state, 10 tests client QR, 3 tests propagation,
  4 tests intégration rotation end-to-end, 4 tests fallback mode-aware
- Tests de non-fuite : sentinelles distinctes pour token, PIN, jeton QR

Co-authored-by: coder/litellm/coder <coder@agents.invalid>
2026-09-08 23:15:06 +02:00
3b38253575 fix: PRONOTE_URL ignoré à cause du double préfixe env_prefix
Le champ pronote_url dans PronoteSettings avec env_prefix=PRONOTE_ produisait PRONOTE_PRONOTE_URL au lieu de PRONOTE_URL. Renomme le champ en url pour que le mécanisme standard produise PRONOTE_URL. Toutes les références mises à jour dans le code de production et les tests. Décision d'architecture : renommage préféré à un contournement par alias (mypy + dette technique).

Co-authored-by: Antoine Van Elstraete <antoine@van-elstraete.net>
Co-committed-by: Antoine Van Elstraete <antoine@van-elstraete.net>
2026-09-08 21:24:10 +02:00
82b9877aad fix: PRONOTE_ENT optionnel pour les connexions pronotepy directes
PRONOTE_ENT était incorrectement traité comme obligatoire pour pronotepy. Rend ENT optionnel pour les connexions directes, conformément à la spécification. Corrige le mode pronotepy explicite et le mode auto sans iCal. 10 tests de régression ajoutés.

Co-authored-by: Antoine Van Elstraete <antoine@van-elstraete.net>
Co-committed-by: Antoine Van Elstraete <antoine@van-elstraete.net>
2026-09-08 20:54:03 +02:00
b4b0247919 feat(M7): synchronisation différentielle CalDAV
Implémente la synchronisation des événements Pronote vers un calendrier
CalDAV (Nextcloud) de façon idempotente et sécurisée.

Production :
- sync/serialization.py : sérialisation Lesson/Homework/SchoolEvent vers
  VEVENT, signature sémantique (exclut DTSTAMP/CREATED/LAST-MODIFIED),
  enveloppe VCALENDAR complète avec VERSION:2.0 et PRODID
- sync/caldav.py : passerelle CalDAV isolant caldav>=1.3.0, résolution du
  calendrier via principal().calendars() avec boundary matching, upsert par
  UID (fetch-then-save), exceptions expurgées et __context__ propre, mot de
  passe non stocké en clair, context manager
- sync/planner.py : calcul explicite du CalDAVSyncPlan (add/update/remove
  par comparaison de signatures sémantiques, routage par préfixe d'UID)
- sync/executor.py : exécution du plan avec dry-run (aucune écriture),
  isolation des erreurs par événement, statut FAILED/SKIPPED/SUCCESS
- sync/synchronizer.py : orchestration en trois phases (scan, plan,
  exécution), SKIPPED si CalDAV non configuré
- sync/__init__.py : export synchronize()
- sources/pronote/client.py : normalisation UID via normalize_pronote_uid/
  generate_deterministic_uid (parité avec ical.py)
- config/settings.py : CalDAVSettings durci (url SecretStr, validation
  HTTPS, allow_insecure_http pour localhost, serializer redact_url)

Tests (381 passés, couverture 95.58%) :
- tests/unit/test_sync_serialization.py (21 tests)
- tests/unit/test_caldav_planner.py (16 tests)
- tests/unit/test_caldav_executor.py (18 tests)
- tests/unit/test_caldav_gateway.py (24 tests)
- tests/unit/test_caldav_security.py (18 tests)
- tests/unit/test_uid_equivalence.py (8 tests)
- tests/integration/test_caldav_sync.py (11 tests, faux serveur en mémoire)
- tests/conftest.py : fixtures partagées

Documentation :
- GUIDE_DEV_PYTHON.md §7 : API réelle caldav>=1.3.0, principal().calendars(),
  VCALENDAR complet, upsert par UID, pas d'état local, événements non gérés
  protégés, CalDAVSettings durci (SecretStr, HTTPS, allow_insecure_http)
- TODO.md : M7 coché
- .env.example : CALDAV_ALLOW_INSECURE_HTTP=false

Co-authored-by: opencode/coder <coder@agents.invalid>
Co-authored-by: opencode/test-engineer <test-engineer@agents.invalid>
Co-authored-by: opencode/tech-writer <tech-writer@agents.invalid>
2026-09-07 09:24:18 +02:00
1b550aa818 fix(M4): pronote_url + account_type et parsing iCal (STATUS:CANCELLED + blocs multi-dates)
Configuration (D1+D3) :
- PronoteSettings : ajout pronote_url (str | None) et account_type
  (Literal student/parent, défaut parent)

Parsing iCal (Fix 4+7) :
- parse_ical : détection STATUS:CANCELLED en plus de CATEGORIES
- parse_body : list[tuple[date, str]] au lieu de dict[date, str]
  pour préserver les blocs multiples à la même date
- parse_homework_blocks : adapté aux listes

Co-authored-by: opencode/coder <coder@agents.invalid>
2026-09-06 15:23:34 +02:00
f3177e70f6 feat(M4): sources/pronote/client.py — client pronotepy avec Protocol
- PronoteClientProtocol : Protocol (get_messages, get_informations,
  get_agenda_fallback) pour la logique de repli
- PronoteClient : encapsule pronotepy.Client (connexion paresseuse)
  avec API réelle 2.15.7 (lessons/homework/discussions/information_and_surveys)
- Erreurs PronoteAPIError et ValueError catchées, loggées avec
  redact_exception, retournent des résultats vides
- Aucune fuite de secret (get_secret_value() uniquement à la connexion)
- pre-commit : ajout de pronotepy au hook mypy

Co-authored-by: opencode/coder <coder@agents.invalid>
2026-09-06 13:32:54 +02:00