Aller au contenu

Multi-tenance

Ce que ça signifie pour l'utilisateur

Chaque société cliente de Jigi (un « tenant ») a l'impression d'utiliser une application qui lui est dédiée : ses employés, ses congés, ses factures, ses documents. En réalité, toutes les sociétés partagent la même instance de l'application et la même base de données — mais aucune donnée d'une société n'est jamais visible par une autre, sauf exception documentée ci-dessous pour les opérateurs de la plateforme elle-même.

Comment ça fonctionne

Chaque requête envoyée au backend doit porter un en-tête X-Tenant-ID (un UUID identifiant la société). Ce mécanisme est géré en trois temps :

  1. Résolution — un filtre Servlet (TenantFilter) lit l'en-tête X-Tenant-ID sur chaque requête HTTP entrante et le valide.
  2. Propagation — l'identifiant est stocké dans un ThreadLocal (app.jigi.tenant.context.TenantContext) pendant toute la durée du traitement de la requête, puis nettoyé (TenantContext.clear()) une fois la réponse envoyée — y compris en cas d'erreur, pour éviter toute fuite entre requêtes traitées par le même thread.
  3. Filtrage — chaque table métier porte une colonne tenant_id UUID NOT NULL, et chaque requête de dépôt de données (repository) filtre systématiquement sur ce tenant_id.
public final class TenantContext {
    private static final ThreadLocal<UUID> CONTEXT = new ThreadLocal<>();
    public static UUID getTenantId() { return CONTEXT.get(); }
    public static void setTenantId(UUID tenantId) { CONTEXT.set(tenantId); }
    public static void clear() { CONTEXT.remove(); }
}

L'exception : les opérateurs de la plateforme

Les comptes JIGI_OPERATOR et JIGI_ADMIN (voir Rôles & permissions) opèrent au niveau plateforme, pas au niveau d'un tenant particulier. Ils ont accès à une vue cross-tenant (liste de tous les tenants, catalogue d'abonnements, paiements, etc.) via l'application séparée admin-jigi, documentée dans le Guide Administrateur plateforme Jigi. Chacune de leurs actions sensibles est journalisée dans une table d'audit dédiée (subscription.operator_audit_log) — voir Opérateurs & audit.

Rôles globaux vs rôles par tenant

Le modèle de rôles de Jigi distingue :

  • des rôles système globaux (tenant_id IS NULL) : des modèles partagés par toutes les sociétés (ex. ORG_ADMIN, HR_MANAGER, MANAGER) ;
  • des rôles personnalisés propres à un tenant (tenant_id renseigné) : une société peut créer ses propres rôles (ex. ACCOUNTANT, CFO) avec ses propres combinaisons de permissions.

Ce détail est développé dans Rôles & permissions (RBAC).

Détails techniques
  • Table tenants (schéma public) : identifie chaque société cliente.
  • Toute entité métier porte tenant_id UUID NOT NULL avec un index sur cette colonne pour les performances.
  • TenantFilter s'exécute avant que la requête n'atteigne les contrôleurs — un X-Tenant-ID absent ou invalide est rejeté avant toute logique métier.
  • Les tables au sein du schéma workflow (voir Moteur de workflow) suivent la même convention tenant_id.
  • Les rôles globaux utilisent un index unique partiel (uq_roles_global_name ON roles (name) WHERE tenant_id IS NULL) car PostgreSQL ne considère pas deux valeurs NULL comme égales dans une contrainte UNIQUE classique.

FAQ

Un employé peut-il, par erreur de configuration, voir les données d'une autre société ? Non — l'isolation est appliquée au niveau des requêtes de données elles-mêmes (pas seulement dans l'interface), donc même un bug d'affichage ne peut pas exposer les données d'un autre tenant.

Que se passe-t-il si l'en-tête X-Tenant-ID est manquant ? La requête est rejetée avant d'atteindre la logique métier — aucune donnée n'est renvoyée.

Les opérateurs Jigi voient-ils le contenu métier détaillé d'un tenant (ex. le contenu des fiches de paie) ? Non, leur accès porte sur les informations de gestion de la plateforme (abonnement, paiement, statut du tenant), pas sur le contenu métier détaillé des modules RH/comptabilité d'un tenant, sauf action explicite et journalisée (ex. support ponctuel).