Les données fournies en BODY des calls API (PUT) sont prises en compte dès les hooks isXXXEnabled

Bonjour,

Version=5.1.0 BuiltOn=2021-09-01 18:40 (et probablement toutes les précédentes)

En cas de PUT sur une API rest mappée, les données fournies par le client sont déjà prises en compte dans les hooks isXXXEnabled (i.e. reportées dans les values de row ou accessibles via getFieldValue) . Ceci met en défaut nos règles d’activation des droits lorsqu’elles s’appuient sur les dernières valeurs validées lors du save précédent.

Exemple : Un attribut “recordOwner” est renseigné pour limiter la modification à “recordOwner”. Un client pourrait pousser une nouvelle valeur de “recordOwner” pour transgresser la règle.

Le contournement mis en place pour l’instant est de ne pas faire référence à getFieldValue(…) mais getFieldOldValue(…) dans les hooks isXXXEnabled pour garantir un comportement équivalent entre les cas d’usage UI et API.

Bonjour,

Je ne suis pas sur de bien comprendre le besoin.
A ma connaisance, l’API mappée est juste un wrapper pour traduire des noms d’attribut externes en nommages internes, mais pas les valeurs des champs eux-mêmes.

Dans Simplicité c’est l’habilitation du user (UI ou API) qui permet de spécifier ce genre de règle :

  • Par paramétrage le profil X est liée à une visibilité “Owner only” sur la fonction d’accès à l’objet,
  • ou via contrôle dans le preValidate en testant que le champ “created_by” = getGrant().getLogin()

Passer des droits au travers de l’enregistrement (via un user API unique), c’est par construction permettre à tout client de se donner des droits de mise à jour en connaissant les “bons” champs à renseigner ?

Bonjour François, merci pour ton retour rapide.

l’attribut “recordOwner” est juste un exemple. J’aurais pu citer un autre cas “responsibleDepartment” (fk) afférent à une direction de rattachement du user courant. Si le user courant est dans la direction concernée, alors il peut modifier le record et le hook isUpdateEnabled renvoie true sinon le hook doit renvoyer false.

Le problème sous-jacent est que si un client API fait un PUT sur la ressource et qu’il fourni comme valeur pour responsibleDepartment sa direction de rattachement, alors c’est sa valeur (fournie en PUT) qui est considérée dans le hook isUpdateEnabled alors que c’est la valeur précédemment enregistrée qui devrait être prise en compte (la dernière valeur fournie par un user qui a le droit de modifier le record).

ps: je n’ai pas testé les cas d’appel d’API natives Simplicité (je n’utilise que les appels d’API mappées) donc je ne sais pas si elles sont concernées.

Il faut donc comparer ce qui est envoyé avec l’enregistrement en base (il existe aussi des Visibilités de Fonction dans Simplicité qui gèrent aussi les membres d’un groupe ou d’une organisation/arbre de groupes).

Cette logique (ou la votre “maison”) ne peut s’appliquer qu’en cas de mise à jour. Dans une création, il n’y a rien base qui fasse foi / à comparer, le user est nécessairement celui qui crée ou appartient à son organisation qui va créer le record.

Oui, le problème se pose dans le cas de la mise à jour d’un record existant (via un PUT …uri…/rowId + valeurs proposées en mise à jour dans le BODY).

Si je compare les séquences des logiques de la UI de de l’API pour le cas où le user courant accède à un record dont sa direction n’est pas responsable:

  • via la UI, le user accède au formulaire, le hook isUpdateEnabled renvoie false, le user ne peut pas modifier
  • via l’API, le user PUT (en positionnant responsibleDepartment dans le BODY), le hook renverra true car c’est la nouvelle valeur qui est considérée dans le getFieldValue et la modification est acceptée

Actuellement, toutes nos implémentation des règles dans les hooks isUpdateEnabled s’appuient sur l’hypothèse que les getFieldValue(…) renvoient la valeur disponible en base avant édition hors le PUT a déjà édité le record (en mémoire) au moment de l’exécution de la règle…

ps: désolé, n’étant pas un expert de la sémantique des verbes HTTP, je formule les choses de manière un peu simpliste (POST pour créer, PUT pour modifier)

Exact, donc on parle bien d’une mise à jour désolé comme tu parlais de isXXXEnable j’étais un peu perdu (quand on fournit le row_id = 0 dans un POST, Simplicité sait traduire la requête en PUT = création).

La logique universelle (UI, API…) dans Simplicité est que :

  • les valeurs en base sont remontées via les getFieldOldValue("field"),
  • et les nouvelles valeurs (reçues) via des getFieldValue("field"),
  • hasChanged = si les 2 sont égales, le champ n’a pas été modifié

Si le PUT correspond bien à un record accessible en mise à jour, Simplicité est sensé faire un “select” pour peupler les 2 (values + oldValues), puis valoriser par dessus les champs demandés dans la requête (uniquement certaines values fournies).

  • PUT / isUpdateEnable = il faut bien tester la getOldValue pour accéder à la valeur en base avant mise à jour = c’est bien la bonne approche
  • POST / isCreateEnable = pas de old values

Ca a toujours été fait comme cela depuis la V1 :wink:

Extrait de code de l’API PUT mappée qui respecte bien la logique des old values :

obj.resetFilters();
if (!obj.select(rowId)) // set values + old-values if exists
{
	return notFound("No " + objName + " record found for row ID " + rowId);
}

DualHashBidiMap<String, String> fields = m_fieldNames.get(objName);
setValues(obj, fields, params); // populate some mapped values

if (!obj.isUpdateEnable(obj.getValues())) // hook isUpdateEnable
{
	return forbidden("Row ID " + rowId + " of object " + objName + " not granted for update");
}

try
{
	new BusinessObjectTool(obj).validateAndUpdate();
}
...

OK merci beaucoup pour la piqure de rappel, nous allons passer le mot et revoir l’implémentation des hooks concernés… :sweat:

Juste pour info, les exemples fournis dans la doc éditeur Simplicité® documentation/01-core/businessobject-code-hooks / isUpdateEnable / isDeleteEnable illustrent une implémentation avec row[getFieldIndex(…)] qui contiendra la nouvelle valeur (idem que getFieldValue) :

@Override
public boolean isUpdateEnable(String[] row) {
    // In this example update is allowed if a specified field has a true value  
    return Tool.isTrue(row[getFieldIndex("objField1")]);
}
@Override
public boolean isDeleteEnable(String[] row) {
    // In this example we apply the same rule as for update
    return isUpdateEnable(row);
}

→ si le client fait un PUT en poussant objField1=true… il passe…

Cela dit, nous remettons l’ouvrage sur le métier :diving_mask:

Oui historiquement les valeurs courantes étaient passées en paramètre pour l’édition en liste multi-lignes = où l’instance d’objet n’a pas de tuple en mémoire dans un contexte de liste (mais la liste paginée en mémoire getCurrentList).

On a dû laisser la syntaxe pour compatibilité ascendante, mais utiliser le record “row” ou getFieldValue dans ces hooks est devenu la même chose à partir de la V3 : pour faire “comme si” on faisait une édition de chaque ligne dans le bon context de mise à jour : appeler le initUpdate, isUpdateEnable… avec des accesseurs value/old-values qui fonctionnent même dans des contraintes.

Merci beaucoup pour ces éclairages (et pour l’historique).

La consigne est passée : Dans les hooks isUpdate|DeleteEnable, utiliser getFieldOldValue qui contient les dernières valeurs validées à la place de getFIeldValue (ou row qui contient les mêmes valeurs) qui pourra contenir des valeurs proposées à la mise à jour.

This topic was automatically closed 60 minutes after the last reply. New replies are no longer allowed.