Aller au contenu

Modèle d'accès — identités, rôles, délégation

Vous êtes ici : Référence — le contrat d’autorisation de toute la suite. Comment GS-Apps décide qui a le droit de faire quoi. Lecture 15 min. Audience : devs, auditeurs sécurité, et administrateurs de la Console UG (volet utilisateur en fin de page). Date : 2026-09-30 | Version : 1.9 — ADR-067 §8.quater, cinq garde-fous de D4 et « Voir comme » (D5 suspendu sous regard). (1.8, 2026-07-07 — précision véracité : le multi-tenant est porté par la validation backend (JWKS /common + allowlist tid) ; l’élargissement signInAudience de l’inscription Entra est une cible d’exploitation, plus affirmé comme fait accompli.) (1.7 : §6 extrait vers le guide, tenant-id retiré)

Question Réponse Source de vérité
Qui es-tu ? Un compte du tenant Workforce, ou un invité/membre d’un tenant autorisé Azure AD (allowlist tid)
Que peux-tu faire dans l’app ? Tes rôles (compagnie × module) BD ug.UserCompanyBcRoles
Qui administre les rôles ? Le corporate délègue : un administrateur de compagnie gère les rôles de sa seule compagnie — scope borné, non re-délégable Console UG
Qui peut tout voir (superuser) ? Les administrateurs d’organisation, par octroi explicite (double-clé — pas l’appartenance, §3.bis) ug.UserCompanyAccess + rôle ADM/ADMIN
Qui touche l’infra Azure ? Une poignée d’humains, durcis par PIM/MFA/CA Rôles d’annuaire Entra (plan séparé)

Avant les détails, le système en un coup d’œil. Trois plans : en haut qui gère les accès (Console UG), au milieu le flux identité → autorisation → apps, en bas l’infra Azure (plan séparé, qui n’accorde aucun droit applicatif). Les sections suivantes zooment sur chaque sous-ensemble.

Carte du système d’accès GS-AppsTrois plans. En haut l’administration (Console UG) : un administrateur d’organisation désigne un administrateur de compagnie qui attribue les rôles des membres. Au milieu le flux : l’identité (Entra, multi-tenant) une fois validée donne l’autorisation (BD ug, rôles par compagnie et module sous RLS), qui ouvre les apps. En bas, séparé, l’infra Azure (PIM, MFA, Conditional Access) qui n’accorde aucun droit applicatif.ADMINISTRATION — Console UGqui peut gérer les accèsAdministrateur d’organisationAdministrateur de compagnieMembredésigne · attribue les rôlesIdentité (Entra)multi-tenant · allowlist tididentité validéeAutorisation (BD ug)rôles compagnie × module · RLSaccès scopéLes appsfacturation · inventaire…Infra Azure — plan séparéPIM · MFA · Conditional Access · break-glassn’accorde aucun droit applicatif
La carte du système : l’administration (Console UG) en haut, le flux identité → autorisation → apps au milieu, et l’infra Azure — plan séparé qui n’accorde aucun droit applicatif — en bas.
Identité vs Autorisation — deux plans orthogonauxÀ gauche le plan identité (Azure AD) : le tenant Workforce et les tenants autorisés passent par l’allowlist tid. À droite, une fois le JWT validé, le plan autorisation (BD ug) : rôles par compagnie et module, scope cross-compagnie, et isCorporateAdmin.PLAN IDENTITÉ — qui es-tuTenant Workforceemployés · MembersTenants autorisésGuests / B2Ballowlist tidug.Companies.azure_tidPLAN AUTORISATION — ce que tu peuxug.UserCompanyBcRolesuser × compagnie × module × rôleug.UserCompanyAccessscope cross-compagnieisCorporateAdmingrant data (god-view)JWT validé
Deux plans orthogonaux : l’identité (Entra, à gauche) prouve qui tu es ; l’autorisation (BD ug, à droite) décide ce que tu peux faire.

Le JWT prouve l’identité (signature + issuer exact-match v1/v2 + tid dans l’allowlist — ADR-063 D-N1). Tout le reste — ce que tu peux faire — se résout côté serveur depuis la BD ug. Conséquence directe : un superuser peut venir d’un autre tenant (membre ou invité d’un tenant autorisé), parce que son pouvoir est un grant ug, découplé du tenant.

Tenant unifié Workforce (ADR-055), trois populations :

Population userType Entra Auth Statut
Corporate Member MSAL Azure AD Actif
Terrain Guest Email OTP En pause (Phase 2+)
Externes / B2B Guest OTP / OIDC fédéré Phase 2+

La validation backend est multi-tenant par conception : JWKS /common, issuer exact-match v1/v2 substitué par le tid du jeton, audience double (l’élargissement signInAudience de l’inscription Entra vers multi-org est la cible — une étape d’exploitation, détail). Le backend valide JWT.tid contre l’allowlist ug.Companies.azure_tid (contrôle critique), puis résout l’utilisateur par (azure_tid, azure_oid). Une nouvelle compagnie s’onboarde par INSERT data (pas de re-déploiement de code).

L’identité est distribuée, l’accès est centralisé. Chaque compagnie a son tenant — ses comptes vivent là (Groupe Signalisation via SOFI ; BeancoLite demain dans le sien) — mais leurs rôles GS-Apps se gèrent tous dans la même Console UG. UG_CORPORATE est un chapeau sans tenant : on n’y connecte personne, on y élève au god-view par octroi explicite.

Registre multi-tenant — un tenant par compagnie, un chapeau au-dessusChaque compagnie a son tenant Microsoft (identité distribuée) : Groupe Signalisation via SOFI, BeancoLite à venir. Chaque tenant est relié par son azure_tid à une compagnie du registre ug.Companies (accès centralisé). Au-dessus, UG_CORPORATE est un chapeau sans tenant : on y accède au god-view par octroi explicite depuis une compagnie.TENANTS MICROSOFT — identité (distribuée)Tenant Groupe Signalisationcomptes via SOFITenant BeancoLiteà venirazure_tidazure_tidREGISTRE ug.Companies — accès (centralisé)Groupe SignalisationCompagnie BeancoLiteoctroi expliciteUG_CORPORATE — chapeauaucun tenant · god-view par octroi
L’identité est distribuée (un tenant Microsoft par compagnie), l’accès est centralisé (le registre ug.Companies). UG_CORPORATE est un chapeau sans tenant — on n’y « habite » pas, on y est élevé par octroi.

Une connexion réussie franchit trois portes successives. Les deux premières sont Entra (plan identité, domaine de l’admin tenant) ; la troisième est l’application (plan autorisation, BD ug). Les trois doivent passer.

Le parcours de connexion — trois portesUne personne franchit trois portes : authentification (Entra), assignation à l’application (Entra), autorisation applicative (code). Bloqué à la porte 2 si non assigné (AADSTS50105). Refusé à la porte 3 si tenant inconnu, ou écran vide si aucun rôle. Sinon accès accordé, scopé à la compagnie.Une personneemployé · terrain · B2B1 · Authentificationjeton Microsoft réel (/common)2 · Assignation à l’appEnterprise App « GS Apps »Pas assigné à l’appbloqué · AADSTS501053 · Autorisationtenant → compagnie → rôlesTenant inconnurefusé · n’entre pasAucun rôleentre · écran videAccès accordéscopé compagnie (+ god-view)Entra — identité (portes 1-2)App / BD ug — autorisation (porte 3)
Les trois portes d’une connexion : les deux premières sont tenues par Entra (identité, assignation), la troisième par l’application (rôles en BD). Les trois doivent passer.
# Porte Où Échec typique
1 Authentification Entra — jeton réel Microsoft, validé multi-tenant côté backend (JWKS /common) Compte inexistant / tenant non autorisé
2 Assignation à l’application Entra — Applications d’entreprise → GS Apps → Utilisateurs et groupes Pas assigné à l’app (code Microsoft AADSTS50105) : l’app est en « Attribution requise » → bloqué avant tout code
3 Autorisation applicative App / BD ug — tid ∈ allowlist ug.Companies.azure_tid, mapping tid → compagnie, resolveOrProvisionUser, puis rôles/grants (Système C) Tenant inconnu → refus fail-closed ; aucun rôle → accès vide

Identité ≠ assignation ≠ autorisation — trois gestes distincts. Inviter quelqu’un comme guest (identité, porte 1) ne l’assigne pas à l’app (porte 2) et ne lui donne aucun rôle (porte 3). Et le SSO n’a pas de lien magique : les courriels (invitation B2B Entra, invitation applicative) sont des notifications ; la personne se connecte simplement à l’URL avec son compte.

Trois gestes distincts — inviter, assigner, autoriserTrois gestes indépendants, aucun n entraîne les deux autres. Inviter quelqu un comme invité crée son identité (porte 1) mais ne l assigne pas à l application et ne lui donne aucun rôle. Assigner la personne à l application d entreprise dans Entra (porte 2) ne lui donne toujours aucun rôle. Donner un rôle dans la Console UG (porte 3) est le seul geste qui ouvre un accès réel. Les courriels d invitation ne sont que des notifications, pas des liens magiques.1 · Invitercrée l identité (guest)n assigne rien, n autorise rien2 · Assigner à l appEntra · app d entreprisetoujours aucun rôle3 · Autoriserun rôle · Console UG (BD ug)le seul qui ouvre un accèsaucun geste n entraîne les autres · les courriels d invitation sont de simples notifications, pas des liens magiques
Trois gestes indépendants. Un invité peut exister (1) et même être assigné (2) sans aucun accès tant qu on ne lui a pas donné de rôle (3).

La source unique des rôles est la table ug.UserCompanyBcRoles (Système C). Les groupes Azure AD comme source d’authZ sont rejetés (ADR-063 §2 / SPEC-1 D-C1) : la BD est la seule vérité.

  • Grain : (user, compagnie, bc_key, rôle). Le bc_key est un module (15 modules au catalogue, ADR-066) — vocabulaire MAJUSCULES post-nettoyage (ADM, pas ADMIN ; une contrainte CK le force).
  • Modèle : RBAC tenant-scoped + RLS (Row-Level Security). Pas de ReBAC (sur-ingénierie écartée).
  • Scope cross-compagnie : le Cas 6 du prédicat RLS (ug.UserCompanyAccess, ADR-064) autorise un user à voir plusieurs compagnies via un mapping aplati. Inerte tant qu’aucun seed.

isCorporateAdmin est une propriété de l’utilisateur résolu, dérivée de la donnée ug (appartenance/grant au niveau de la company-parent UG, écrite par la Console UG) — jamais d’un compte de service, jamais en dur (ADR-063 D-N2). Elle mappe au bypass Cas 2 du prédicat RLS. Le hardcode adminReadClaims:true historique a été retiré (CH3) : injectCompanyId() dérive désormais companyId / isCorporateAdmin du user résolu, fail-closed si absent.

ADR-070 fige un seul jeu de termes — à utiliser dans l’UI, la doc et le code neuf. Les valeurs d’enum internes restent inchangées :

Terme (humain) Sens Interne consoleAccessVia
Administrateur d’organisation god-view, toutes compagnies uca (octroi explicite, double-clé) · corporate legacy coupé §3.bis
Administrateur de compagnie admin scopé à sa compagnie delegated
Membre rôles par module, pas d’administration —

Module ≠ niveau. ADM est un module (clé bc_key, ADR-066) ; ADMIN est un niveau de rôle (UTILISATEUR < GESTIONNAIRE < ADMIN). On écrit « rôle Administrateur sur le module Admin », jamais « ADM/ADMIN ». Désigner un admin est non délégable (anti-escalade : un administrateur de compagnie ne crée jamais d’admin).

ADR-067 tranche la frontière d’administration : le corporate ne micro-gère pas les rôles de chaque compagnie ; il délègue.

L’escalier d’administration, et le passage à l’opérationnelDans le plan d’administration (Console UG), un administrateur d’organisation crée les compagnies et désigne l’administrateur de chaque compagnie, qui attribue les rôles de ses membres. Le membre ne gère rien. Dans le plan opérationnel, il utilise les apps (facturation, inventaire, flotte) selon ses rôles.Plan d’administration des accès — Console UGAdministrateur d’organisationcrée les compagnies · désigne les adminsdésigneAdministrateur de compagnieattribue les rôles de ses membresattribue les rôlesMembren’administre rienutilise les modulesPlan opérationnel — les appsFacturation · inventaire · flotte…utilise ses modules selon ses rôles
L’escalier de délégation : l’administrateur d’organisation désigne, l’administrateur de compagnie attribue, le membre utilise. Chaque étage est borné — jamais de re-délégation.
  • Étage 1 (corporate) : compagnies, REACH, désigne l’admin de chaque compagnie, et fixe l’entitlement modules (quels modules une compagnie a le droit d’activer — non délégable).
  • Étage 2 (admin délégué) : fait le finegrant — les rôles des users de sa compagnie, scopé à sa compagnie.
  • Invariants : zéro double administration (D3) ; cinq garde-fous pour l’administrateur de compagnie — sa compagnie seulement, les modules activés pour elle seulement, jamais au-dessus de son propre niveau, jamais le rôle Administrateur sur le module Admin, et la personne à qui il accorde un rôle est membre de la compagnie (D4, authorizeRoleWrites et findNonMemberTargets, apps/ug/server/routes/roles.ts) ; le corporate garde la vue god de supervision / bris de glace (D5).
  • « Voir comme » (ADR-067 §8.quater, 2026-09-30) : l’administrateur d’organisation (double-clé uca) regarde la Console UG comme l’administrateur d’une compagnie et y change les rôles, chaque changement signé à son nom. Le regard est un sélecteur, jamais une preuve : l’en-tête X-GS-Voir-Comme est revérifié par le serveur à chaque requête. Sous regard, D5 est suspendu — lecture de la seule compagnie regardée, écriture par les cinq garde-fous de D4, routes réservées à l’organisation refusées. Hors regard, D5 est intact.

Concrètement, la surface du finegrant délégué est la fiche compagnie scopée de la Console UG (CompanyDetailView en mode isDelegated, atterrissage sur l’onglet Membres), pas N écrans séparés. Le résolveur resolveConsoleAccess (@gs/auth-multi-tenant/console-access) rend un verdict 'corporate' | 'uca' | 'delegated' | null (fail-closed : god-view via la double-clé uca — le court-circuit corporate par appartenance a été retiré au §3.bis, valeur désormais inatteignable ; delegated exige ≥1 compagnie administrée). Sous regard « Voir comme », GET /api/me de la Console UG rend consoleAccessVia: 'delegated' et voirComme: { companyId } : une seconde source du verdict, que l’UI suit sans le re-dériver.

Le « superuser » de l’app = un corporate-admin. C’est un grant data ug (isCorporateAdmin), curé — on ne l’accorde qu’aux compétents. Deux portes le gardent : (1) identité dans un tenant autorisé (allowlist tid) + (2) le grant ug. Pas de groupe Entra parallèle comme source d’authZ (ce serait une 2e liste = double administration + contredit la source unique).

Les gestes concrets dans la Console UG (ug.groupesignalisation.ca) — donner un accès, désigner un administrateur de compagnie ou d’organisation, débloquer une connexion — vivent dans un guide dédié : Gérer les accès dans la Console UG. Cette page-ci reste la référence des concepts ; le guide est le pas-à-pas.

Chaque décision structurante suit un standard reconnu (Microsoft Well-Architected, patrons SaaS multi-tenant, RBAC/RLS) — pas une préférence locale. Le détail et les alternatives écartées vivent dans les ADR ; voici le « pourquoi » en clair :

Décision Pourquoi (le standard) ADR
Identité ≠ autorisation (deux plans) Base des systèmes matures : on change l’un sans toucher l’autre ; un externe entre sans devenir un compte maison. 061 / 063
Multi-tenant /common + allowlist tid, fail-closed Patron Microsoft documenté pour le SaaS multi-tenant. Onboarder une compagnie = une donnée, pas un déploiement. 055
RBAC tenant-scopé + RLS Défense en profondeur : même une requête fautive reste bornée par la base. 061 / 064
BD ug comme source unique d’authZ (pas les groupes Entra) Évite la double administration et le « group sprawl ». Choix assumé ; l’inverse existe, mais une seule vérité s’audite mieux. 063
Délégation à deux étages, non cascadable « Delegated administration » de manuel : le corporate distribue des périmètres, l’admin de compagnie joue dedans sans l’élargir. 067
God-view par octroi explicite, révocable, audité Moindre privilège : le pouvoir vient d’un octroi nommé, pas d’une appartenance. Retrait en un clic, tout est tracé. 070 §3.bis
PIM / MFA / Conditional Access / break-glass (infra Azure) Recommandation Microsoft Well-Architected pour les accès privilégiés. Une adresse UG ne donne aucun droit infra. 069
ADR + infra-as-code (bicep) Les décisions et l’infra sont écrites et versionnées, pas dans une tête. Reproductible, auditable. —

ADRs (dépôt GitHub — le détail des décisions et des alternatives écartées) :