Contourner un piège des règles d'accès sur les colonnes Référence

Contexte

Document de formation avec quatre tables liées :

  • Clients (Adresse_mail, etc.)
  • Produits
  • Commandes (Client → Référence vers Clients)
  • Lignes de commande (Reference_commande → Référence vers Commandes, qui elle-même référence Client)

Objectif : que chaque client connecté (via un attribut utilisateur basé sur l’email) ne voie et ne modifie que ses propres commandes.

Attribut utilisateur configuré :

  • Nom : Client
  • Propriété d’appairage : user.Email
  • Table d’appairage : Clients
  • Colonne cible : Adresse_mail

Comportement observé

On cherche à paramétrer ces règles d’accès dans « Permissions avancées » (le panneau de gestion des Access Rules de Grist). Sur la table Commandes, toutes les formulations suivantes échouent silencieusement (aucune erreur affichée, mais la ligne n’est jamais considérée comme correspondant à la condition, même pour un client qui devrait légitimement y avoir accès) :

user.Client == rec.Client
user.Client.id == rec.Client.id
user.Email == rec.Client.Adresse_mail
rec.Client and user.Client.id == rec.Client.id

Résultat concret : avec une règle générique de refus (« Tous les autres ») active en dessous, l’utilisateur ne voit aucune commande, y compris celles qui lui appartiennent. Sans cette règle de refus, l’utilisateur retombe sur les règles par défaut du document (accès complet aux Editors), ce qui prouve que la règle spécifique ne matche jamais, dans un sens comme dans l’autre.

Preuve que les données sont pourtant identiques

Une colonne de diagnostic en formule déclenchée, testée en conditions réelles (un utilisateur avec droit U temporaire modifiant effectivement une ligne) :

return str(user.Client) + " | id=" + str(user.Client.id if user.Client else "None") + " || rec.Client=" + str($Client) + " | id=" + str($Client.id if $Client else "None")

Résultat affiché : Clients[14] | id=14 || rec.Client=Clients[14] | id=14

Les deux objets Référence sont donc rigoureusement identiques (même table, même id), mais la comparaison échoue quand même dans le contexte d’une condition de règle d’accès.

Point commun de tous les échecs

Toutes les formulations qui échouent déréférencent un champ à travers une colonne de Référence côté rec (rec.Client, rec.Client.id, rec.Client.Adresse_mail…). En revanche, le déréférencement côté user fonctionne normalement (user.Client.id, user.Email fonctionnent très bien isolément).

Confirmation par analogie : sur un autre document du même auteur (table de gestion de formateurs), une règle d’accès fonctionnelle et éprouvée en production suit exactement ce schéma :

$Offreur_txt == user.Inspecteur.Code_offreur_txt   # fonctionne (colonne texte plate côté rec)
user.Email.lower() == $Cree_par.lower()             # fonctionne (colonne texte plate côté rec)

Dans les deux cas, côté rec, c’est toujours une colonne déjà stockée en texte plat sur la ligne elle-même, jamais une valeur allant chercher plus loin à travers une Référence.

Solution de contournement (fonctionne, testée)

Ajouter une colonne « miroir » en formule classique (pas en règle d’accès) qui aplatit la valeur nécessaire directement sur la ligne :

# Sur Commandes, colonne "Client_email"
return $Client.Adresse_mail if $Client else None
# Sur Lignes de commande, colonne "Client_email"
return $Reference_commande.Client_email if $Reference_commande else None

La règle d’accès devient alors triviale et fonctionne immédiatement :

$Client_email == user.Email

Question

Est-ce un comportement connu et documenté (auquel cas une référence serait bienvenue, je n’ai rien trouvé de précis en cherchant), un bug à signaler, ou une limitation volontaire du bac à sable d’évaluation des règles d’accès (peut-être pour des raisons de performance ou de sécurité liées à l’évaluation en cascade des permissions sur la table référencée) ? Merci d’avance pour vos éclairages !

Bonjour, bienvenue sur le forum et merci d’avoir pris le temps de détailler autant votre problème !

Dans ce tuto vidéo de l’équipe GristLabs, on appaire l’attribut d’un utilisateur connecté avec la valeur d’une colonne de type référence, pour que l’utilisateur puisse voir les données le concernant, avec la formule suivante user.Employee.id == $Employee

Donc je vous recommande d’essayer la syntaxe user.Client.id == $Client (qui est en principe équivalent à user.Client.id == rec.Client).

Sinon, je pense à quelque chose de bête : est-ce par hasard votre colonne Client dans la table Commandes ne serait pas de type Référence multiple ? Car dans ce cas la comparaison == ne fonctionne pas et il faut utiliser une syntaxe in.

Bonjour @Enro et merci beaucoup pour votre réponse et pour le temps pris à chercher une piste !

Pour préciser un point : la colonne Client sur Commandes n’est pas en Référence multiple, c’est bien une Référence simple (vérifié avant même votre suggestion, via une colonne de diagnostic qui confirmait un objet Référence unique de part et d’autre de la comparaison).

J’ai testé votre suggestion user.Client.id == $Client (soit user.Client.id == rec.Client), et bonne nouvelle : ça fonctionne bien sur la table Commandes, contrairement à toutes les formulations que j’avais essayées jusque-là. Merci pour cette piste, elle m’a permis de mieux cerner le problème.

Malheureusement, ça ne s’est pas généralisé à mon cas d’usage complet. J’ai une deuxième table, Lignes de commande, qui n’a pas de colonne Client directe : l’appartenance ne peut s’y déduire qu’en passant par un deuxième niveau de Référence (Reference_commande puis Client), ou par une colonne formule qui aplatit cette valeur. J’ai testé plusieurs variantes sur cette table :

  • la chaîne à deux sauts directement (rec.Reference_commande.Client) : échoue, blocage y compris en lecture
  • une colonne formule intermédiaire comparée avec $Client_ref : la formule n’est pas encore calculée au moment de la création d’une ligne, ce qui bloque la création
  • la même chose via newRec au lieu de rec, pour tenir compte du calcul au moment de la création : bloque toujours
  • séparer la règle de création (C) de la règle de lecture/modification (R/U/D) : encore pire, ça a fait disparaître l’accès en lecture complètement

Donc pour résumer ce que je crois avoir compris : votre syntaxe fonctionne bien quand la comparaison se fait sur une Référence brute stockée directement sur la ligne (un seul niveau, aucun calcul), mais elle ne tient plus dès qu’il faut un deuxième niveau de déréférencement ou une valeur calculée, que ce soit via rec ou newRec. Ça recoupe et précise le comportement que je décrivais dans mon message initial plutôt que de le contredire.

Je reste donc sur la solution de contournement des colonnes miroir en texte plat, qui fonctionne de façon fiable dans tous les cas de figure (lecture, création, modification), même si elle est un peu plus lourde à maintenir. Encore merci pour votre aide, ça a été utile pour mieux circonscrire le problème !

1 « J'aime »

Il me semble en effet qu’un rec.Reference_commande.Client ne fonctionne pas dans les règles d’accès.

Le contournement est de rajouter, dans la table des lignes de commandes une colonne qui ramène l’adresse mail ou le client, pour pouvoir mettre la même règle d’accès que dans la table des commandes.