Skip to content

Coupon - partie 3

Cette leçon aborde la question des droits d'accès et des permissions qui s'y rapportent. Nous continuons à travailler sur l'application "coupon" mise en place durant les leçons "03 - coupon1" et "04 - coupon2".

Introduction

L'application "Coupon" mise en place lors des leçons précédente ne gère aucun droit d'accès : tous les utilisateurs peuvent accéder à tous les coupons et tous les magasins, en créer, les modifier, les supprimer.

D'autre part, les dossiers sont toujours modifiables, quel que soit le statut. Nous allons commencer par mettre en place une gestion des permissions en fonction du statut du coupon.

Nomenclature

Les policies d'Ewt font intervenir 4 notions qu'il peut être nécessaire de clarifier:

1) Permissions : Les permissions indiquent si une donnée est disponible en lecture et/ou en écriture: les types de permissions possibles sont donc read ou write

2) Actions : Les actions font référence aux commandes envoyées par un client (un navigateur) pour agir sur un dossier ou sur le moteur lui-même. Le moteur supporte différentes actions et la liste est susceptible de s'accroître au fil des versions de Ewt. On peut par exemple citer les actions suivantes: addTuple, admin, arrange, close, create, clone, delete, delTuple, dummy, open, reset, save, script, setState, setLocale, setStyle,setView`.

3) Transitions : Les transitions sont des changements d'état d'un dossier

4) Operations : Les opérations sont des traitements propres à l'application, mais non interprétés par Ewt. Il s'agit d'une possibilité offerte à l'application pour apposer des policies à des processus qui lui sont spécifiques. On peut par exemple imaginer une opération "reindexCatalog" qui est un processus métier. On peut alors associer des policies à cette opération, bien qu'il ne s'agisse pas d'une opération du moteur.

Activation des policies

En premier lieu, nous devons activer le système de policies afin d'avoir une gestion de droits.

Qu'est-ce qu'une policy ?
Les policies sont largement décrites dans la documentation de référence. En bref, il s'agit de règles permettant de gérer les droits d'accès. Les policies peuvent être définies soit pour des groupes d'individus (on parle de policy d'identité) soit pour des modèles, des groupes ou des champs (on parle alors de policy de ressource).

Comment active-t-on le système de gestion des policies ?
Le système de gestion de policies s'active dès lors que des policies sont définies dans l'application et/ou que la configuration de l'application référence une policy. Nous allons effectuer ces deux opérations.

Mise en place d'une policy de base

Nous allons mettre en place une policy de base.

👉 Créez un répertoire policies dans le répertoire principal de l'application coupon et ajoutez-y un fichier main.xml ayant le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<policy name="main" type="identity" priority="-100">
  <description>
    Cette policy d'identité vérifie le rôle technique permettant d'accéder 
    à l'application. Comme il s'agit de la policy de base, on lui donne
    une priorité de -100, ce qu'il fait que n'importe quelle autre policy 
    primera sur celle-ci.
  </description>
  <appliesTo>
    <role>EWT</role>
  </appliesTo>
  <statement effect="allow">
    <permission>read</permission>
    <action>dummy,save,close</action>
  </statement>
</policy>

Vous pouvez également nommer le fichier main.policy si vous souhaitez lui donner une extension plus parlante.

Que fait cette policy ?
Cette policy accorde une permission de lecture seule aux utilisateurs possédant le rôle EWT. Elle autorise également l'usage des actions dummy et save.

Pour rappel, dans la leçon "01 - intro", nous avons configuré le serveur tomcat de sorte que le login user reçoive le rôle EWT (nous avons fait cela au niveau du fichier tomcat-users.xml dans la config de Tomcat). Il s'agit d'un rôle technique nécessaire pour accéder à l'application. Ce rôle est référencé au niveau du fichier web.xml d'Ewt en tant que contrainte d'authentification (élément <auth-constraint>), ce qui fait qu'il est attendu par le serveur d'application: si un utilisateur s'authentifie sur le serveur d'application mais ne possède pas ce rôle, il recevra une erreur 403.

L'action dummy est une action fictive qui n'effectue aucun traitement. Elle n'en est pas pour autant inutile. En effet, lorsque l'on effectue une requête GET sur le moteur, c'est-à-dire lorsque l'on accède à une application au moyen d'une URL, le moteur vérifie que le client est autorisé à effectuer au moins une action, quelle qu'elle soit. En accordant un droit sur dummy, on remplit cette condition sans que cela n'ait d'impact métier sur une application, du fait que dummy ne fait rien.

L'action save est une action qui traite un formulaire html et close est une action qui ferme un dossier ouvert. On accorde le droit sur ces actions car elles sont nécessaires à la navigation dans une application, même lorsque celle-ci est en lecture seule.

👉 Effectuez un reset de l'application.

Si vous avez lancé la commande reset depuis la page d'accueil de l'application, vous ne devriez pas remarquer grand chose. Par contre si vous avez lancé la commande depuis un dossier, vous remarquerez que tous les champs du dossier sont passés en lecture seule. Seule l'action "Fermer" reste disponible (étant donné qu'elle est autorisée par la policy main). L'action "Enregistrer" a disparu malgré le fait que l'action save est autorisée. Cela vient du fait que la policy n'accorde pas la permission write.

img-05-01.png

👉 Depuis la page d'accueil, lancez une recherche en cliquant sur le bouton "Rechercher". Un message d'erreur signalant que l'opération n'est pas permise devrait apparaître.

img-05-02.png

Élément <appliesTo>

Au début de ce chapitre, nous avons évoqué les policies d'identité et les policies de ressource. La policy main mise en place ici est une policy d'identité. Les policies d'identité contiennent une balise <appliesTo> dans laquelle on énumère les références de sujets auxquels la policy doit s'appliquer. Nous avons référencé le rôle EWT. Les policies d'identité sont automatiquement évaluées du moment que le sujet connecté correspond à l'une des conditions <appliesTo> de la policy.

Dans le cas des policies de ressources il en va différemment. Une policy de ressource doit être référencée par une ressource. Prenons le cas de la policy main définie en tant que policy de ressource:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<policy name="main" type="resource" priority="-100">
  <description>
    Cette policy de ressource autorise un accès minimaliste sur
    l'application à tout utilisateur possédant le rôle technique.
    Comme il s'agit de la policy de base, on lui donne une priorité  
    de -100, ce qu'il fait que n'importe quelle autre policy primera 
    sur celle-ci.
  </description>
  <statement effect="allow">
    <subject>
      <role>EWT</role>
    </subject>
    <action>dummy,save,close</action>
    <permission>read</permission>
  </statement>
</policy>

La policy de ressource n'intègre pas d'élément <appliesTo>. Par conséquent, comment faire si nous souhaitons appliquer cette policy à l'application ? Il faut pour cela ajouter une référence au niveau du fichier de configuration. Cela se fait en ajoutant la section suivante dans le fichier config.xml:

1
2
3
<policies>
  <policy name="main"/>
</policies>

Mise en place d'une policy d'identité

Nous avons mis en place une policy de base qui a eu pour effet de fortement restreindre les droits de l'utilisateur "user" sur l'application. Nous allons à présent étendre les droits de ce dernier au moyen de policies supplémentaires.

Commençons par attribuer un rôle métier à l'utilisateur "user".

👉 Éditez le fichier tomcat-users.xml (présent dans C:\apps\apache-tomcat-9.0.76\conf\ si vous utilisez un environnement similaire à celui mis en place dans la leçon "01 - intro").

👉 Ajoutez les rôles métiers suivants:

<role rolename="coupon-admin"/>
<role rolename="coupon-user"/>

Ici, nous avons créé deux rôles : le rôle coupon-admin donnera accès aux fonctions d'administration tandis que le rôle coupon-user donnera accès aux fonctionnalités standard.

👉 Attribuez ces rôles à l'utilisateur "user" en modifiant la ligne correspondant à ce "user" de la façon suivante:

<user username="user" password="1234" roles="EWT,coupon-admin,coupon-user"/>

Pour tester les différentes combinaisons de rôles, nous allons ajouter deux utilisateurs supplémentaires:

👉 Ajoutez les comptes suivants:

<user username="superuser" password="1234" roles="EWT,coupon-user,coupon-admin"/>
<user username="admin" password="1234" roles="EWT,coupon-admin"/>

À la fin des modifications, votre fichier tomcat-users.xml devrait avoir la forme suivante (les commentaires ont été retirés):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
  <role rolename="EWT"/>
  <role rolename="coupon-user"/>
  <role rolename="coupon-admin"/>
  <user username="user" password="1234" roles="EWT,coupon-user"/>
  <user username="superuser" password="1234" roles="EWT,coupon-user,coupon-admin"/>
  <user username="admin" password="1234" roles="EWT,coupon-admin"/>
</tomcat-users>

Nous allons à présent définir les policies qui accordent les droits en fonction des nouveaux rôles.

👉 Créez un fichier admin.xml dans le dossier policies de l'application et insérez-y le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<policy name="admin" type="identity">
  <description>
    Cette policy accorde un droit sur les fonctions d'administration
  </description>
  <appliesTo>
    <role>coupon-admin</role>
  </appliesTo>
  <statement effect="allow">
    <action>admin,reset</action>
  </statement>
</policy>

👉 Créez un fichier user.xml dans le dossier policies de l'application et insérez-y le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<policy name="user" type="identity">
  <description>
    Cette policy accorde un droit sur toutes les fonctions, à l'exception
    des fonctions d'administration
  </description>
  <appliesTo>
     <role>coupon-user</role>
  </appliesTo>
  <statement effect="allow">
    <description>
      On accorde un droit sur toutes les actions, à l'exception de
      reset et admin
    </description>
    <permission>read,write</permission>
    <action>addTuple</action>
    <action>arrange</action>
    <action>close</action>
    <action>create</action>
    <action>clone</action>
    <action>delete</action>
    <action>delTuple</action>
    <action>dummy</action>
    <action>open</action>
    <action>reset</action>
    <action>save</action>
    <action>script</action>
    <action>setLocale</action>
    <action>setState</action>
    <action>setStyle</action>
    <action>setView</action>
  </statement>
</policy>

On remarquera au passage la différence de syntaxe utilisée pour énumérer les actions entre les deux policies. Ewt autorise d'énumérer les actions (et les permissions) en les plaçant dans une liste séparée par une virgule, en les énumérant dans des balises <action> séparées ou en faisant un mix des deux formes. Vous pouvez choisir la forme qui vous convient le mieux.

Construction alternative

Ce paragraphe est optionnel. Nous y discutons d'une façon alternative de construire la policy user. Nous abordons quelques notions utiles concernant les policies, mais nous aurons l'occasion d'y revenir plus tard.

Dans le cas du rôle coupon-user nous avons décidé d'énumérer toutes les actions autorisées. On aurait également pu construire la policy différemment, en accordant les droits sur toutes les actions, puis en retirant les actions admin et reset ensuite. La policy aurait pu être définie ainsi:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<policy name="user" type="identity" version="1.0">
  <description>
    Cette policy accorde un droit sur toutes les fonctions, à 
    l'exception des fonctions d'administration
  </description>
  <appliesTo>
    <role>coupon-user</role>
  </appliesTo>
  <statement effect="allow">
    <description>
      On accorde un droit sur toutes les actions...
    </description>
    <permission>read,write</permission>
    <action>*</action>
  </statement>
  <statement effect="deny">
    <description>
      ... mais on le retire sur admin et reset
    </description>
    <permission>read,write</permission>
    <action>admin,reset</action>
  </statement>
</policy>

Attention, cette façon de procéder pour gérer les droits entraîne au moins deux conséquences:

  1. Une adaptation de la policy admin est nécessaire
  2. Une référence de version doit être ajoutée à la policy user

a) Adaptation de la policy admin
La policy admin doit être modifiée ainsi :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="utf-8"?>
<policy name="admin" type="identity" priority="1">
  <description>
    Cette policy accorde un droit sur les fonctions d'administration
  </description>
  <appliesTo>
    <role>coupon-admin</role>
  </appliesTo>
  <statement effect="allow">
    <action>admin,reset</action>
  </statement>
</policy>

La différence se situe au niveau de la balise <policy> : nous avons ajouté un attribut priority="1". Pourquoi ? Dans Ewt, les refus priment sur les autorisations. Or, nous avons ici une policy admin qui accorde un droit sur les actions admin et reset et dans le même temps une policy user qui la refuse. Le refus l'emporte et par conséquent les actions d'administration ne sont utilisables par personne.

L'attribut priority permet de faire primer la policy admin sur la policy user. La policy admin déclare une priorité de 1, alors que la policy user n'en déclare aucune, ce qui revient à avoir une priorité
de 0. Une priorité plus haute l'emporte sur une priorité inférieure.

b) Verrouillage de version
Dans la policy user ci-dessus, nous avons intégré une petite différence qui qui a son importance du point de vue de la sécurité. L'élément racine <policy> a été complété avec un attribut version="1.0". Cet attribut indique que l'on souhaite appliquer le modèle de policy "1.0" dans ce cas. Il s'agit d'un garde-fou pour éviter que l'entrée <action>*</action> ne soit mal interprétée.

Explication
Comme indiqué au début de cette remarque, la policy ci-dessus accorde un droit sur toutes les actions dans un premier temps, puis retire le droit pour les actions admin et reset. L'interprétation de "toutes les actions" peut varier en fonction de la version du moteur Ewt. Imaginons en effet qu'une version future du moteur ajoute une nouvelle action avancée. La policy ci-dessus accorderait de facto cette nouvelle action. Cela pourrait représenter un risque de sécurité pour l'application.

Le fait de mentionner que la policy applique la version "1.0" du moteur de policy fait que la référence wildcard <action>*</action> représente "toutes les actions connues de la version 1.0 du moteur de policy". Cela évite qu'une action ajoutée dans la future version 1.1 ne soit prise en compte. La documentation de référence relative aux policies sera maintenue au fil des évolutions et indiquera à quelle version chaque action est associée.

Conclusion
La méthode de définition par ajout/retrait de droits entraînent des complications et des risques, raison pour laquelle elle n'est pas recommandée. La solution consistant à procéder uniquement par ajout de droit est donc à privilégier lorsque cela est possible.

Ces modifications étant faites, il faut à présent redémarrer le serveur d'application Tomcat : cela permettra de recharger la configuration de tomcat-users.xml et effectuera dans le même temps un reset de l'application.

👉 Stoppez l'instance Tomcat puis démarrez-la à nouveau

À présent, le compte "user" devrait à nouveau pouvoir lancer des recherches, créer et modifier des dossiers. Par contre il n'est pas autorisé à lancer des reset ou à accéder aux fonctions d'administration.

Le compte "admin" quant à lui devrait à l'inverse être autorisé sur les actions d'administration et sur le reset, mais ne devrait pas pouvoir créer ou modifier de dossiers, ni lancer de recherche.

Enfin, le compte "superuser" devrait disposer de toutes les fonctionnalités.

Permission dépendant du statut

L'idée est de garder les champs en lecture/écriture tant qu'un coupon est actif et de rendre ces champs uniquement lisibles lorsque le coupon est inactif, à l'exception du champ "Statut" qui devra rester modifiable.

👉 Créez un fichier statut.xml dans le dossier policies de l'application et ajoutez-y le contenu suivant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<policy name="statut" type="resource" priority="1">
  <description>
    Cette policy gère les permissions en fonction du statut
    d'un dossier
  </description>
  <statement effect="deny" requireQualifiedContext="true">
    <description>
      Ce statement retire le droit d'écriture sur un dossier dont
      le statut est 0. Il retire également les droits sur les
      actions susceptibles de modifier des valeurs sur un dossier.
    </description>
    <subject>
      <role>*</role>
    </subject>
    <permission>write</permission>
    <action>addTuple,delTuple,create,delete,setState</action>
    <condition>return (#base.statut == 0);</condition>
  </statement>
</policy>

Cette policy de ressource s'applique à tous les rôles utilisateur. Elle retire le droit d'écriture et bloque certaines actions lorsque le champ statut d'un dossier a la valeur 0. La condition sur la valeur du champ est écrite en script. Nous reviendrons plus tard sur le langage de script, mais la syntaxe de la condition ci-dessus est assez simple à comprendre.

Cette policy de ressource a une priorité de 1 (cf. attribut priority="1"). La priorité permet de classer les policies par niveaux. Cela permet de passer outre la règle voulant qu'une policy de type deny prime sur une policy de type allow. Ce point est développé à l'aide d'un exemple dans le bloc de remarque 2 plus haut dans cette leçon. Nous y revenons également plus bas.

Au niveau du statement, nous avons spécifié un attribut requireQualifiedContext="true". Il s'agit d'une optimisation et d'un garde-fou pour éviter des erreurs inattendues. Il indique au moteur que le statement n'est à traiter que lorsque la policy est évaluée dans un contexte pleinement qualifié. L'idée est d'éviter d'évaluer le statement hors dossier, car dans ce cas la résolution de #base.statut ne pourrait pas se faire et générerait une erreur.

Nous devons indiquer au moteur qu'il doit appliquer cette policy à tout le modèle "Coupon".

👉 Éditez le fichier descript.xml et modifiez la définition du modèle coupon de façon à lui ajouter le bloc <policies> comme ci-dessous:

1
2
3
4
5
6
7
<model name="coupon" maingroup="base">
  <policies>
    <policy name="statut"/>
  </policies>
  <groups>
    <group name="base" table="Coupon" type="single" mainfield="idCoupon">
    ...

👉 Effectuez un reset de l'application au moyen d'un utilisateur disposant du rôle coupon-admin (donc admin ou superuser)

Une fois cette policy en place, tous les champs du dossier passent en lecture seule lorsque le statut est à 0, y compris le champ "Statut" lui-même. Ça peut être un problème si le changement de statut a été effectué par erreur. On va donc faire en sorte que le champ "Statut" reste modifiable dans tous les cas.

Il y a plusieurs façons de le faire. Dans cette leçon, nous proposons la solution suivante, mais elle n'est de loin pas la seule possible.

👉 Éditez le fichier descript.xml et modifiez le champ statut pour lui donner la description suivante:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<field name="statut" type="switch" column="Statut">
  <default mode="text">
    <value>1</value>
  </default>
  <policies>
    <policy name="fieldStatut" type="resource" priority="2">
      <statement effect="allow" requireQualifiedContext="true">
        <subject>
          <role>*</role>
        </subject>
        <permission>write</permission>
        <action>addTuple,delTuple,create,delete,setState</action>
        <condition>return (#base.statut == 0);</condition>
      </statement>
    </policy>
  </policies>
  <properties>
    <attribute name="onchange">if (this.checked) doSubmit('save')</attribute>
  </properties>
</field>

En premier lieu, nous avons ajouté un élément policies contenant une description de policy "inline". En effet, Ewt permet de référencer par son nom une policy définie dans le dossier policies de l'application comme nous l'avons fait pour la policy statut, mais également directement dans la description du champ. Ici nous optons pour cette variante car la policy que nous définissons ne concerne que le champ "Statut" lui-même.

La structure de la policy en soi ne change pas par rapport à une policy définie via un fichier. Ici nous indiquons une priorité 2 via l'attribut priority="2". Cela est nécessaire pour que le allow que nous définissons ici prime sur le deny de la policy statut créée plus tôt.

Nous spécifions également un bloc <properties> dans lequel nous déclarons un <attribute>. Les properties sont des sortes de fourre-tout. Elles permettent à l'utilisateur d'y spécifier n'importe quel type de données. Ces données seront reprises dans l'arbre de sortie et pourront être interprétées par la feuille de style. L'élément <attribute> n'est donc pas un mot-clé reconnu par Ewt. Le moteur ne fait que de transmettre cet élément dans l'arbre de sortie et c'est la feuille de style qui l'exploite à sa guise. Dans le cas présent, la feuille de style reprend l'attribut en question en tant qu'attribut de champ. Cela permet de forcer un rafraîchissement de l'écran lorsque l'on change le statut de 0 à 1. Comme en mode "lecture seule" l'interface n'affiche pas de bouton "Enregistrer", le code javascript utilisé ici facilite l'enregistrement du changement de statut. Une alternative aurait pu consister à rajouter un bouton "Rafraîchir" dans l'interface, mais cela nécessite d'intervenir au niveau des styles et ce n'est pas l'objet de la présente leçon.

img-05-03.gif