Regles de gestion & contraintes
Nature du repo
- C'est un gabarit, pas un site fini. Les exemples (
exampleSection,exampleElement, themescave/piscines, module JSexampleSection) sont des demonstrations a adapter ou retirer dans un projet aval. - Projets
Application/Domain/Persistence: a creer au besoin, absents du template. - En cas de conflit lors d'un merge depuis
upstream, convention : garder la version du template.
SEO & contenu
- Titres et meta resolus par priorite (
PageExtensions) : head title -> meta title -> nom de page ; og title/description retombent sur les meta. - Le
_MasterLayoutemet : canonical,robots noindexsi la page est masquee du sitemap (HideFromSitemap), hreflang en variantes*-CH, etx-defaultvers le canonical. - Sitemap (
/sitemap) : XML unique multilingue, force https, n'inclut qu'un noeud qui a un url segment, un template et n'est pas masque du sitemap. - robots.txt : en production, ne sert que la ligne
sitemap:; hors production,disallow: /(blocage total de l'indexation).
Cookies & marketing
- Aucun outil collectant des donnees ne doit se charger avant consentement. Google Consent Mode v2 :
analytics_storage='denied'par defaut ; le consentement met a jour viagtag('consent','update',...). - Le popup de consentement (vanilla-cookieconsent) ne s'affiche que si
CookieConsent:Enabled=true. Mettre afalsequand un CMP tiers (OneTrust, Didomi...) est integre. - GA4 ne s'injecte que si
GoogleAnalytics4Idest renseigne sur lemarketingSettings;anonymize_ipactif.
Skins APOL / PEL
- Un seul depot, deux deploiements : les DataTypes uSync contiennent les choix des deux marques ; c'est
Site:Themequi decide, au demarrage, de ce que voit l'editeur. Ne jamais dupliquer un DataType ni un fichier uSync par skin — la divergence rendrait chaque merge upstream conflictuel. - Options propres a un skin : leur alias (bloc) ou leur valeur (couleur, motif de fond...) doit etre ajoute au jeu correspondant dans
BlockGridSkinFilterHandler/DataTypeSkinFilterHandler, sinon elles sont proposees aux deux skins. Les entrees absentes des deux jeux sont considerees communes (blocs partages, neutres blanc/gris/noir). Filtrer une nouvelle liste de choix = une ligne dans la tableFiltersdeDataTypeSkinFilterHandler. - Export uSync depuis une instance demarree : les handlers ont deja ampute les DataTypes en base, l'export reecrit donc une config incomplete. Verifier
ApprovedColor.configet le BlockGrid avant de committer un export.
Securite
DevLoginController(/l, auto-login backoffice) : double garde obligatoire#if DEBUG+ check runtimeIsDevelopment(). Ne jamais retirer.- Politique de mot de passe backoffice (
appsettings.json) : 15 caracteres min, majuscule + minuscule + chiffre + non-alphanumerique.AllowConcurrentLogins=false. - 2FA backoffice via Google Authenticator (
UmbracoUserAppAuthenticator). - Secrets (chaine de connexion prod,
SentryUrl, cles) : jamais committes ; viaappsettings.local.json(git-ignore) ou variables d'env. UmbracoApplicationUrlobligatoire derriere un reverse proxy (breaking change 17.4) sinon spoofing possible sur les emails de reset/invitation.
Intranet & Active Directory (PEL)
- Accès à l'intranet : réservé aux comptes AD appartenant à au moins un groupe du mapping
Ldap:GroupMappings(groupe AD → groupe membre Umbraco). Confirmé avec le SSI de Pully (15.07.2026) : même mode de fonctionnement que Felix, annuaire joignable en LDAPS (chiffré). - Mot de passe : validé uniquement par l'AD (bind LDAPS) ; jamais stocké, jamais loggué. Anti-brute-force = politique de verrouillage de l'AD ; anti-énumération = message d'échec générique + délai additionnel fixe (400 ms) sur les échecs côté site.
- Date de naissance : n'existe pas dans l'AD de Pully ni dans Entra ID (aucun attribut standard) →
birthDateest une saisie manuelle backoffice, commephotoetshowInDirectory. Confirmé côté Pully (15.07.2026), à confirmer côté APOL. La sync AD ne doit jamais écraser ces trois champs. APOL aurait voulu une reprise automatique depuis l'AD, mais l'AD de Pully disparaît sous ~3 mois et l'attribut anniversaire Entra/Graph (birthday) est généralement vide côté M365 → saisie manuelle en MVP sur les deux skins, avecshowBirthdaypour l'opt-out (voir ci-dessous). - Contacts externes vs personnel : un contact hors personnel (commune, partenaire, service) doit toujours être saisi comme contenu (
externalContact), jamais commeintranetMember. La sync AD désactive (IsApproved=false) tout membre absent de l'annuaire AD ; un contact externe créé comme membre disparaîtrait donc à la synchronisation suivante. Umbraco impose en plus un e-mail et un username uniques par membre, contrainte que beaucoup de contacts externes ne remplissent pas. showBirthdaydistinct deshowInDirectory: un collaborateur peut vouloir figurer à l'annuaire sans exposer sa date de naissance. Même sémantique queshowInDirectory: visible sauffalseexplicite, donc les membres existants sans valeur restent visibles par défaut. CommebirthDate,photoetshowInDirectory, cette propriété n'est jamais écrite par la synchronisation AD.- Départ d'un collaborateur : la sync passe le membre en
IsApproved=false(plus de login, exclu de l'annuaire/anniversaires) ; jamais de suppression automatique (les données manuelles seraient perdues). Suppression manuelle possible au backoffice. - Garde-fou anti-vidage : si l'AD est injoignable ou renvoie 0 utilisateur, la sync ne modifie rien — un raté réseau ne doit jamais vider l'annuaire.
- SSO transparent (Windows Auth) : l'hébergement sera Windows IIS chez Pully (confirmé 15.07.2026) → SSO Windows Authentication implémenté (
WindowsSsoMiddleware, activé parIntranet:WindowsSso=true) : identité Windows IIS → fiche LDAP → provisioning → connexion membre, sans saisie de mot de passe. Le login formulaire/loginreste le repli (postes hors domaine, IIS anonymous actif, annuaire indisponible). Modalités de déploiement IIS à préciser (compte-rendu Xavier attendu) — le pipeline Docker du template ne s'applique pas à ce projet. - Page « Mon profil » : un collaborateur ne peut modifier que sa photo et sa date de naissance — exactement les champs que la sync AD n'écrit jamais (avec
showInDirectory/showBirthday). Tout le reste (prénom, nom, e-mail, fonction, numéros) vient de l'AD et reste en lecture seule, sinon une saisie manuelle serait écrasée à la synchronisation suivante et donnerait l'impression d'un bug. L'écriture porte toujours sur le membre connecté : le formulaire ne transporte aucun identifiant, la clé est lue dans la session, donc une personne ne peut pas modifier la fiche d'une autre en altérant le POST. Felix n'avait pas cette page en service (les vues du kit Nanoxi existent mais l'entrée de menu desktop est commentée et aucunePageLoginn'est publiée) : c'est un ajout, hors devis. Les opt-outshowInDirectory/showBirthdayrestent pour l'instant réservés au backoffice — à arbitrer avec le client. - Âge affiché aux anniversaires : le bloc Anniversaires indique l'âge atteint, comme le widget felix. L'âge n'est affiché que s'il tombe entre 15 et 100 ans :
birthDateest une saisie manuelle, et une date choisie sans toucher à l'année prend l'année courante — ce qui afficherait « 0 ans » à un collègue. Hors de cette plage, aucun âge n'est affiché plutôt qu'un âge faux (DirectoryRules.AgeOn). À noter : afficher l'âge expose l'année de naissance, que le seul jour/mois ne révélait pas ;showBirthdayreste l'unique opt-out (tout ou rien), comme sur felix. - Actualités lues / non lues : repris de felix (
Nanoxi.Customization/Felix, en service depuis 2019), avec la même mécanique — les ids des articles ouverts sont stockés en CSV sur le membre (memberReadArticles), et un titre non lu ressort en rouge tandis qu'un titre lu passe en couleur de texte. Pas de badge : sur une liste longue, la couleur seule se lit plus vite. Ouvrir l'article suffit à le marquer lu (ArticlePage.cshtml), il n'existe pas de « marquer comme lu » manuel. Trois écarts assumés avec l'original : lecture null-safe (felix plantait sur un membre n'ayant jamais ouvert d'article), déduplication, et liste bornée à 2000 ids. Sans membre connecté, aucune distinction n'est affichée — surtout pas « tout non lu ». Les états de lecture felix ne sont pas migrés : au basculement, tout repart non lu (décision 22.07.2026). Fonctionnalité hors devis. - Risque accepté (décision Clément, 15.07.2026) : avec
RequireLogin=true,/media(dont les PDF migrés de Felix) et/sitemaprestent accessibles sans login (allowlist du MemberGate + bypass des fichiers à extension). Accepté car l'intranet sera hébergé à l'interne chez Pully, sur un réseau protégé non exposé au public. À réévaluer si l'hébergement ou l'exposition réseau change.
Auth & infra APOL (M365/Entra ID)
- Authentification : SSO membre via Microsoft Entra ID (M365 APOL), pas d'AD LDAPS comme PEL. Auto-link JIT au premier login (membre
intranetMembercréé à la volée) ; tout le tenant APOL est autorisé, pas de filtrage par groupe (contrairement auLdap:GroupMappingsde Pully) — voirmodules/extensibility.md. - Hébergement : chez APOL, via leur prestataire infogérant ANSAM (pas Pully/Windows IIS comme PEL). Modalités de déploiement précises à confirmer avec ANSAM.
- Rôles/admin backoffice : hors périmètre du devis actuel — pas de gestion fine des rôles éditeurs côté APOL dans le MVP.
- 2FA membre : bypass assumé. Le callback
EntraAuthController.CallbackappelleExternalLoginSignInAsync(..., bypassTwoFactor: true): Entra impose déjà le MFA en amont (au niveau du tenant M365), et il n'existe aucune UI de 2FA côté membre dans ce codebase (la 2FA existante,UmbracoUserAppAuthenticator, est backoffice uniquement). Une future fonctionnalité de 2FA membre devra composer avec ce bypass déjà en place — ne pas supposer qu'ExternalLoginSignInAsyncrespecte un 2FA membre qui n'existe pas encore.
Base de donnees & deploiement
UpgradeUnattended=true: migrations jouees au boot.- Cache statique :
/csset/js(fingerprintes) enimmutable1 an ; autres assets 7 jours. - Rewrites IIS et Sentry actifs uniquement en Staging/Production.
- Image Docker buildee depuis la racine du repo (pas depuis
hosting/).
Fichiers generes / a ne pas editer
src/Web/umbraco/Models/*.generated.cs(ModelsBuilder),src/Web/wwwroot/css|js(Vite).gulpfile.js: legacy, conserve mais inutilise (pipeline = Vite).
Contributors
No contributors
Changelog
No recent changes

