Skip to main content

Paramétrage en masse — détails techniques

Fiche fonctionnelle

Propagation du jeu d'accès d'un groupe au groupe de même profil dans plusieurs établissements, depuis Gestion des accès (/admin/access → « Étendre au périmètre »).

Route et écran

ÉlémentEmplacement
Dialogue « Étendre ces accès à votre périmètre »apps/dashboard/src/app/admin/access/page-view.tsx (ExtendAccessDialog)
APIapps/dashboard/src/pages/api/admin/access-bulk.ts (Pages Router)
Résolution du périmètreapps/dashboard/src/pages/api/_perimeter.ts (resolveAdminPerimeter, splitBulkTargets)
  • GET /api/admin/access-bulk → les établissements réellement administrables par l'appelant, pour alimenter la liste à cocher.
  • POST /api/admin/access-bulk avec { structureIds, profile, levels } → applique le jeu de niveaux et renvoie { profile, total, applied, results }.

Les niveaux envoyés sont ceux du groupe affiché, recalculés côté client par le même code que « Modifier les accès » (getGroupLevel). Aucune écriture Neo4j directe : tout passe par appregistry et directory.

Périmètre : le client n'est jamais cru sur parole

resolveAdminPerimeter interroge GET /directory/structure/admin/list — tous les établissements pour un SUPER_ADMIN, le scope de la fonction (héritage HAS_ATTACHMENT compris) pour un ADMIN_LOCAL. La liste envoyée par le navigateur ne sert qu'à choisir dans ce périmètre.

splitBulkTargets sépare la sélection en targets et unauthorized plutôt que de rejeter tout le lot : un référent territorial peut porter un périmètre ADML partiel, et les établissements hors périmètre ressortent comme des lignes de compte rendu (status: 'unauthorized'), pas comme un 403 global. Seul le plafond est bloquant : MAX_STRUCTURES = 40 par requête (413 au-delà), chaque établissement coûtant plusieurs appels — au-delà, la requête dépasserait le délai d'attente du navigateur.

Fonctions et périmètre du référent collectivité

Le rôle métier de référent collectivité combine actuellement ADMIN_COLLECTIVITE et un périmètre ADMIN_LOCAL. La première fonction ouvre notamment les commandes groupées de structure-info ; la seconde détermine les établissements administrables par les API entcore. Le serveur ne remplace pas ce périmètre par la portée de ADMIN_COLLECTIVITE : cela proposerait des établissements sur lesquels les opérations locales pourraient être refusées.

Attribuer ADMIN_LOCAL sur la structure de tête avec héritage pour couvrir le réseau. Voir Référent collectivité et Établissements & structures.

Le bon groupe : celui de l'établissement, pas d'une de ses classes

Pour chaque établissement, quatre lectures en parallèle : appregistry/groups/roles, directory/group/admin/list, appregistry/roles et appregistry/applications.

Deux pièges y sont traités explicitement :

  • groups/roles ne porte pas le champ type — sans lui, profileIdForGroup ne peut rattacher aucun groupe à un profil. Le type est fusionné par id depuis l'annuaire (group/admin/list), comme le fait déjà l'écran « Gestion des accès ».
  • Un groupe de classe porte un libellé voisin du groupe d'établissement (« Enseignants du groupe 3ème-1. » / « Enseignants du groupe Collège André Malraux-Dijon. »). Un simple find aurait posé les droits sur une classe : le groupe retenu est celui dont le nom contient le nom de l'établissement (comparaison normalisée, accents retirés). Sans candidat, l'établissement ressort skipped avec le motif — jamais une écriture au hasard.

Écriture : retirer avant de reposer

Les rôles gérés sont tous les niveaux (- Lecture, - Gestion, - Administration, plus les conventions historiques <app> et <app>- tous les droits, résolus par resolveRoleId) des applications visées. Ils sont retirés de la liste courante, puis les rôles cibles sont ajoutés — c'est ce qui fait qu'un passage à « Aucun accès » ou une rétrogradation prend effet, là où un simple ajout n'aurait jamais rien enlevé. Les rôles étrangers aux applications visées sont conservés : l'opération ne remet pas le groupe à plat. L'écriture se fait en un seul POST /appregistry/authorize/group (groupId, roleIds).

Relecture post-écriture (verified)

Un HTTP 200 sur l'écriture ne prouve pas que l'annuaire a propagé le changement. Après chaque établissement appliqué, appregistry/groups/roles est relu et comparé au paramétrage demandé : tous les rôles cibles présents, tous les rôles retirés absents. Le résultat alimente le drapeau verified de la ligne de compte rendu (« droits vérifiés » / « non vérifié » à l'écran) ; un échec de la relecture elle-même est reporté en clair (Vérification impossible : …) plutôt que d'être confondu avec un succès.

Chaque ligne de results porte structureId, structureName, status (applied / skipped / error / unauthorized), le group effectivement modifié, rolesApplied, verified et un message. L'opération n'est donc jamais un « tout ou rien » : une application partielle se voit.

Couverture de tests

Pas de scénario e2e dédié à ce jour. L'écran /admin/access est visité par apps/dashboard-e2e/src/modules/03_admin/05_configuration_openent.spec.ts (contrôle d'accès aux pages d'administration) et par apps/dashboard-e2e/src/tests/10_app_access_grant.spec.ts (attribution d'application sur l'établissement courant) ; ni l'un ni l'autre n'ouvre le dialogue « Étendre au périmètre » ni ne vérifie la propagation. Les opérations de masse par établissements — écran structure-info, qui partage _perimeter.ts — sont, elles, couvertes par 07_operations_masse.spec.ts (voir Établissements & structures).

Séparation des fonctions collectivité

  • ADMIN_COLLECTIVITE conserve le rôle territorial et le nom de groupe historique Collectivite. Son libellé dans le Dashboard est Référent collectivité. L'attribution est réservée au super-administrateur, dans le proxy Dashboard et les filtres AddFunctionFilter des backends.
  • COLLECTIVITE_LOCALE est la fonction métier pour les réservations et le patrimoine. Son groupe fonctionnel porte le suffixe CollectiviteLocale. Elle n'est pas acceptée par les contrôles réservés à ADMIN_COLLECTIVITE (actions groupées, registre, thème, sauvegardes).
  • ADMIN_LOCAL reste la source du périmètre administrable des opérations locales ; la nouvelle fonction métier ne la remplace pas et n'est jamais attribuée à sa place.

Mise en service

Dans le dépôt open-ent-mods, le script scripts/demo/creer_fonctions_metier.py crée notamment COLLECTIVITE_LOCALE sous le profil PROFILE_PERSONNEL. Il accepte --dry-run pour vérifier les créations prévues. Il ne réaffecte aucun utilisateur et peut être rejoué.

Déployer le Dashboard et les filtres d'attribution du backend utilisé, puis créer la nouvelle fonction avant de l'attribuer. Les référents existants gardent ADMIN_COLLECTIVITE. Les comptes métier doivent être réexaminés individuellement : attribuer la nouvelle fonction et retirer les anciennes habilitations qui ne sont plus justifiées. Le simple changement de libellé ne retire aucun droit existant.