Application des droits à travers toute l'API¶
Contexte¶
Le système de droits à travers le service Autorisations permet de vérifier simplement si une action est autorisée par un utilisateur. Cependant son implémentation est très complexe ce qui nous empêche de la faire évoluer.
Par conséquent lorsqu'on veut récupérer des listes de documents ou de dossiers on est obligé d'appliquer ces vérifications en mémoire. Ce processus est lent et inefficace puisqu'on est obligé de récupérer plus de données que nécessaire depuis la base de données.
Pour palier en partie à ce problème nous utilisons Elasticsearch (ES) pour les endpoints de recherche où les droits sont dénormalisés ce qui nous permet de faire une recherche rapide.
Cette situation est cependant problématique sur 2 aspects :
- on ne peut pas utiliser ES pour des listes exhaustives car on ne peut pas récupérer plus de 10k éléments depuis un index
- la logique de dénormalisation est décorélée du service
Autorisationsce qui complexifie la maintenance
Ce contexte fait qu'en l'état on ne peut pas adresser la problématique de lister des dizaines de milliers de documents/dossiers sur un même endpoint.
Nous devons donc trouver un moyen d'appliquer les droits directement en SQL.
Solution écartée¶
Une solution serait d'établir une requête SQL qui ferait toute les jointures nécessaires pour cibler les bons documents/dossiers.
Le problème de cette approche est qu'on couple le stockage de toutes les entités de l'application. Alors que toute l'application est conçue pour garder un découplage au niveau de stockage pour nous permettre une évolution simplifiée.
De plus cela revient à transposer la complexité actuelle du code dans la requête SQL, complexifiant encore plus cette partie de l'application.
L'autre problématique est qu'avec le stockage actuel des données cette requête SQL devrait faire des full table scan à chaque vérification (à cause de tableau en json). La performance ne serait donc pas suffisante sur des gros volumes de données, obligeant ainsi à revoir tout le stockage des données concernées.
Décision¶
L'application des droits dans ES est rapide car on les dénormalise sous forme de clés composites qui ont la forme :
{idGabaritDossier}-{idRole}{idGabaritDossier}-{idRole}-{idValeurFiltre}{idBannette}-{idRole}{idGabaritDocument}-{idRole}{idGabaritDossier}-{idRole}-{idValeurFiltre}-{idGabaritDocument}-{idRole}
Il n'y a pas le droit (lecture, modifier, etc..) dans la clé car on ne l'utilise que dans le cas de la lecture. Mais il serait simple de l'ajouter dans chaque clé pour couvrir tous les scénarii.
Au moment de la recherche on calcule ces même clés pour l'utilisateur connecté. Du moment qu'une des clés de l'utilisateur est présente dans le tableau de clés stocké dans ES alors l'utilisateur y a accès.
La solution est donc d'utiliser le même système en SQL en stockant dans chaque dossier/document la liste des clés par rapport à la liste des habilitations de leur gabarit. Pour requêter cela est aussi simple qu'un WHERE property IN (value1, value2, etc...) qui est exprimable via notre système de requêtage via des Specifications.
Ce système est partiellement applicable en temps réel. Au moment de la création ou autre actions (comme un déplacement ou reclassement) on peut recalculer à moindre coup le nouveau tableau de clés. En revanche lorsque les droits appliqués au plan de classement (via l'admin) évoluent alors il faut recalculer l'ensemble des tableaux de clés pour tous les documents/dossiers; ce traitement devra donc être asynchrone.
La partie asynchrone n'est pas problématique à l'usage de l'application puisque :
- c'est déjà le cas pour la recherche
- les listes de dossiers/documents ne sont de toutes façon pas utilisable sur les gros volumes
- le traitement asynchrone pour des petits volumes sera minime donc presque imperceptible par le client
Comme mentionné dans le Contexte l'implémentation actuelle est complexe car elle n'est pas la même entre ES et en mémoire. On a donc 2 implémentations qui ne sont pas localisées au même endroit dans l'application. Avec cette approche nous aurions la même logique applicable en mémoire, en SQL et dans ES.
Pour arriver à cette solution nous devons revoir comment est organisé le code. Au lieu d'hardcoder la logique dans des services comme Autorisations nous devons décoréler la déclaration de ce qu'on souhaite vérifier du moment où le code est vraiment exécuté (sur un modèle similaire aux Specifications).
La forme exacte de ces structures de données n'est pas encore définie car elle demande de faire un travail de recherche (alias commencer à refactorer le code pour faire émerger le design). Si on arrive à produire un système déclartif et composable alors le système sera plus simple à maintenir.
Conceptuellement c'est essayer d'arriver à faire la même transition des méthodes multiples sur les Repository vers les Specifications.
Le facteur limitant de ce système sera le nombre de rôles associés à des gabarits car au plus il y a de rôles au plus il y aura de clés à générer pour un document/dossier. Au delà d'une centaine de rôles pour un gabarit ou pour un utilisateur risque de devenir problématique.