fix(config): valider l'alias CalDAV legacy et retirer file:// de fetch_ical

Supprime le contournement model_construct : l'alias CALDAV_URL est désormais validé comme le champ canonique (userinfo, host et port rejetés). Retire le support file:// de fetch_ical et corrige l'exemple résiduel du guide. Ajoute les tests négatifs de l'alias legacy et adapte le test de sécurité CalDAV au contrat durci.

Refs #63
This commit is contained in:
2026-09-13 15:46:07 +02:00
parent c89113ba36
commit 5a3e251ad6
6 changed files with 89 additions and 76 deletions
+7 -21
View File
@@ -244,13 +244,12 @@ class CalDAVSettings(BaseSettings):
def _migrate_legacy_url(cls, data: object) -> object:
"""Migre ``url`` vers l'endpoint commun avec un avertissement.
L'alias historique ``CALDAV_URL`` conserve ses sémantiques passées
pendant la transition : lorsqu'il embarque des identifiants
(``user:pass@hôte``), l'endpoint est construit sans revalidation pour
ne pas casser une configuration existante, alors que le contrat
canonique ``CALDAV_ENDPOINT__URL`` refuse désormais les credentials
embarqués. La politique de transport (HTTPS, ou HTTP loopback
uniquement avec ``allow_insecure_http``) reste appliquée ensuite.
L'alias historique ``CALDAV_URL`` est migré via le chemin de validation
canonique : il est donc soumis exactement aux mêmes règles que
``CALDAV_ENDPOINT__URL`` (schémas réseau uniquement, hôte obligatoire,
port valide, credentials embarqués refusés). La politique de transport
(HTTPS, ou HTTP loopback uniquement avec ``allow_insecure_http``) reste
appliquée ensuite.
:param data: Données brutes du modèle.
:return: Données complétées avec ``endpoint`` si nécessaire.
@@ -265,20 +264,7 @@ class CalDAVSettings(BaseSettings):
stacklevel=2,
)
if migrated_data.get("endpoint") is None:
legacy_url = migrated_data["url"]
raw_url = (
legacy_url.get_secret_value()
if isinstance(legacy_url, SecretStr)
else str(legacy_url)
)
parsed = urlparse(raw_url)
if parsed.username is not None or parsed.password is not None:
# Alias obsolète : grandfathered, on préserve l'URL telle quelle.
migrated_data["endpoint"] = ExternalEndpoint.model_construct(
url=legacy_url if isinstance(legacy_url, SecretStr) else SecretStr(legacy_url)
)
else:
migrated_data["endpoint"] = {"url": legacy_url}
migrated_data["endpoint"] = {"url": migrated_data["url"]}
return migrated_data
@model_validator(mode="after")