Menu
Démarrer un projet

Architecture

Appliquer les permissions multi-organisations dans l'API

L'isolation entre organisations est facile à décrire et facile à rater d'une façon qui reste invisible jusqu'au jour où un client voit une ligne appartenant à quelqu'un d'autre.

Publié le

Tout produit multi-organisations est à une requête d'un incident. Pas un incident spectaculaire : aucune alarme ne sonne quand une liste contient quatre lignes là où il en fallait trois. Le client voit un nom qu'il reconnaît, ou un enregistrement qu'il ne devrait pas avoir, et la confiance est perdue avant que quiconque ait lu un journal.

L'appartenance n'est pas une colonne ajoutée plus tard

La version tentante ajoute un identifiant d'organisation aux tables qui en ont visiblement besoin et laisse l'application filtrer dessus. Cela tient jusqu'à la première jointure qui oublie, au premier rapport écrit dans l'urgence, à la première tâche de fond qui s'exécute au nom de personne.

La version qui tient traite l'organisation comme une partie du chemin d'accès plutôt que comme un attribut. Chaque lecture part de l'organisation et descend. Une requête incapable de nommer son organisation ne s'exécute pas. La contrainte est agaçante le premier jour et porteuse le quatre centième.

Trois questions auxquelles chaque requête doit répondre

Avant qu'une requête atteigne la moindre donnée, trois réponses doivent déjà exister.

  • Dans quelle organisation cette requête agit-elle ? Pas celle à laquelle l'utilisateur appartient d'habitude, celle que cette requête nomme.
  • Quel rôle l'appelant détient-il dans cette organisation ? Les rôles sont propres à une organisation, donc une personne peut en administrer une et seulement lire une autre.
  • L'enregistrement visé est-il bien dans cette organisation ? Le contrôle porte sur la ligne autant que sur la collection, car l'identifiant est arrivé du client.

Où placer le contrôle

À la frontière de l'API, avant la couche de données, et jamais uniquement dans l'interface.

Un bouton masqué n'est pas une permission. C'est un indice de permission, rendu par du code qui s'exécute sur une machine que personne ne maîtrise, dans un navigateur où les outils de développement sont à une touche. L'interface doit lire l'état des permissions et le refléter. Elle ne doit jamais le décider.

Placer le contrôle à la frontière rend aussi les tests bon marché. Une règle qui vit dans un composant exige un navigateur et une page rendue. La même règle derrière un point d'API exige une requête.

Tester les règles, pas les écrans

Les tests qui comptent sont courts et ennuyeux : pour chaque rôle et chaque point d'API, vérifier ce qui se passe quand l'enregistrement appartient à une autre organisation. La réponse attendue est presque toujours la même, ce qui rend la suite facile à garder complète.

Deux habitudes la maintiennent honnête. Écrire d'abord le cas négatif, car un test qui ne démontre que le chemin heureux ne démontre rien sur l'isolation. Et faire des tests de permissions ceux qui bloquent une fusion, pour qu'une fuite casse le build plutôt que d'atteindre la production puis un client.

La migration que personne n'anticipe

Un jour, un client demandera à être scindé en deux, ou deux clients fusionneront, ou un utilisateur aura besoin d'accéder à une seconde organisation. Si l'appartenance est une colonne, ces trois cas sont des réparations de données au tableur, en retenant sa respiration.

Si l'appartenance est un enregistrement à part entière, avec un rôle attaché au couple utilisateur et organisation, ces trois cas sont des écritures ordinaires. Ce modèle coûte une table de plus au départ et n'a jamais été ce que l'on regrette.

Toutes les notes

Dites-nous ce que vous construisez.

Écrivez en anglais, en français, en roumain ou en arabe. Chaque message reçoit une réponse écrite sous un jour ouvré.