Coupon - partie 2¶
Cette leçon fait suite à la leçon "03 - coupon1" et nécessite que l'application développée en partie 1 soit fonctionnelle. Reportez-vous donc $ la première partie si vous ne l'avez pas encore fait.
Durant cette leçon, nous allons apporter différentes retouches à l'application pour étendre la description, améliorer l'interface de saisie ainsi que la recherche et mettre en place une validation de données.
Modèle "Magasin"¶
Notre description ne comporte actuellement qu'un seul modèle "coupon". Nous allons ajouter un nouveau modèle "magasin" et faire en sorte que l'on puisse référencer un magasin depuis un coupon.
👉 Éditez le fichier schema.xml
et ajoutez la définition de table suivante
en-dessous de la table Coupon
:
1 2 3 4 5 |
|
La nouvelle table est très simple et se passe de commentaire. Il s'agit à
présent de remplacer notre colonne Magasin
dans la table Coupon
par une
référence vers la nouvelle table. Le but recherché est d'avoir une liste
déroulante de magasins à sélectionner lorsque l'on crée un coupon.
Pour ce faire:
👉 Dans le même fichier au niveau de la table Coupon
, remplacez la ligne
<column name="Magasin" type="varchar(100)"/>
par
<column name="idMagasin" type="int" foreignReference="Magasin"/>
L'attribut foreignReference
indique quelle table, voire quel champ
référence notre champ. La valeur de l'attribut peut avoir deux formes:
TABLE.COLUMN
ou TABLE
. Dans le premier cas, la référence est explicite.
Dans la seconde, le moteur va automatiquement baser la référence sur la clé
primaire de la table indiquée, à condition que cette dernière possède une
clé primaire basée sur une seule colonne. Dans le cas où la table utilise
une clé primaire composée de plusieurs colonnes, la notation TABLE.COLUMN
est obligatoire. Dans notre exemple, la colonne idMagasin
sera une
référence vers la colonne idMagasin
de la table Magasin
.
La mise à jour de la description est également relativement aisée à comprendre.
👉 Éditez le fichier descript.xml
et ajoutez le modèle suivant à la suite du
modèle coupon
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
👉 Dans le même fichier au niveau du modèle coupon
, remplacez la ligne
<field name="nomMagasin" type="text" column="Magasin"/>
par
1 2 3 4 5 |
|
Ce champ apporte une nouveauté. Ce champ annonce un type "select". Le type est indique à la feuille de style qu'elle doit afficher une liste déroulante. Pour alimenter la liste déroulante, on s'appuie sur une requête SQL.
👉 Ouvrez l'application depuis un navigateur via l'URL
http://localhost:8080/ewt/web/coupon.
Cliquez sur Fichier > Reset
pour
forcer un rechargement du schéma et de la description.
👉 Cliquez sur Admin > Générer un canevas de langue
. Sélectionner les
nouvelles lignes du canevas, c'est-à-dire celles qui se trouvent à partir de
# model magasin
, collez-les dans le fichier i18n/descript_fr.properties
de l'application et ajoutez les libellés.
1 2 3 4 5 6 7 8 9 |
|
Dans l'exemple ci-dessus, nous avons décidé d'ajouter une emoji sur le modèle "Magasin". Vous pouvez adapter le libellé à votre guise.
👉 Remplacez la ligne ayant pour clé field.coupon.base.nomMagasin.label
par
field.coupon.base.idMagasin.label = Magasin`
👉 Revenez sur l'application, cliquez sur Admin > Reset
Notre nouveau modèle est prêt. Il reste à préparer la base de données.
👉 Cliquez sur Admin > Créer la base de données (alter)
La commande devrait générer passablement d'erreurs dans le log car certains requêtes vont chercher à recréer des éléments qui existent déjà. Vous pouvez ignorer ces erreurs.
Vérifions à présent que notre nouveau modèle fonctionne.
👉 Cliquez sur Fichier > Nouveau > 🛒 Magasin
. Vous pouvez saisir un nom de
magasin et une adresse internet.
👉 Créez un second magasin de la même façon.
Multi-dossiers
Si vous avez créé le nouveau dossier alors que le premier était encore ouvert, vous noterez que les deux dossiers sont ouverts en même temps.
En effet, par défaut Ewt garde ouvert les dossiers jusqu'à ce qu'ils soient explicitement fermés (par l'utilisateur ou par un script) ou que la session expire. C'est le fonctionnement par défaut du moteur.
Il est possible de demander au moteur de gérer les dossiers de façon
unitaire. Pour cela, il faut définir la propriété suivante dans le bloc
admin
:
<documentMode>single</documentMode>
Dans la version actuelle du moteur, la propriété peut prendre les
valeurs single
ou multi
. Le mode par défaut est le mode single
.
On notera au passage que la ligne d'entête de l'interface affiche une liste déroulante avec la mention "2 dossiers ouverts". Le menu déroulant permet de ré-afficher les dossiers (par exemple s'ils ne sont plus visibles à l'écran, ce qui arrive par exemple si on clique sur le nom d'application dans le coin supérieur gauche) ou de les fermer tous.
Voyons à présent ce qu'il en est du modèle coupon
.
👉 Fermez les dossiers de magasins et lancez une recherche. Ouvrez un
dossier du modèle coupon
.
On remarque que le nouveau champ "Magasin" affiche à présent une liste déroulante qui reprend les magasins créés précédemment.
Champ "Statut"¶
Nous allons à présent nous concentrer sur le champ "Statut". Ce champ est censé enregistrer le statut du coupon et indiquer s'il est disponible ou utilisé.
Type "switch"¶
Nous allons travailler plusieurs éléments sur ce champ. Commençons par sa forme. Le champ de type texte actuel n'est pas pratique et nous allons le remplacer par un autre type de contrôle.
👉 Éditez le fichier descript.xml
et modifiez le type du champ statut
de
text
à switch
.
👉 Revenez sur l'application et cliquez sur Fichier > Reset
. Le champ
devrait aborder une nouvelle forme:
Lorsque l'on crée un nouveau coupon, on aimerait que celui-ci soit immédiatement disponible. Par conséquent le statut par défaut devrait être 1 et non pas 0. Il est possible de demander à Ewt de renseigner une valeur par défaut.
Valeur par défaut¶
👉 Modifiez la description du champ Statut
pour lui donner la forme
ci-dessous. Effectuez un nouveau Reset
puis créer un nouveau coupon via le
menu Fichier
.
1 2 3 4 5 |
|
Le coupon devrait à présent avoir un statut "actif" dès la création.
Données binaires¶
On aimerait à présent pouvoir rattacher à notre coupon une copie numérique
du coupon. Pour cela, nous allons ajouter un champ de type blob
.
👉 Modifiez le fichier schema.xml
et ajoutez les descriptions de colonnes
suivantes dans la table Coupon
:
1 2 3 4 |
|
Ces colonnes permettront de sauvegarder le fichier, son nom, sa taille et
son mime-type. Le mime-type permet de décrire le type de fichier. Ainsi, un
fichier pdf sera décrit au moyen du mime-type application/pdf
, un fichier
jpg au moyen de image/jpeg
, etc. Une liste des mime-types principaux est
disponible sur https://developer.mozilla.org/fr/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types.
👉 Modifiez le fichier descript.xml
et ajoutez les lignes suivantes dans le
modèle coupon
:
1 2 3 4 5 6 7 |
|
Le champ fichier
présente plusieurs particularités:
- Il est du type
file
. Le moteur s'appuie sur ce type pour savoir que le champ est un champ binaire.
Il indique au moteur et à la feuille de style que le champ est de type binaire. Le moteur a besoin de savoir que le champ est binaire pour des questions d'optimisation. - Il contient des métadonnées. Les métadonnées sont des valeurs associées au
champ lui-même et stockées dans des colonnes indépendantes du champ; les
noms de ces métadonnées utilisent des préfixes
file:
, ce qui indique au moteur comment il peut interpréter ces métadonnées et les lier à l'objet "File" qu'il va se construire en mémoire.
👉 Ajoutez un libellé de champ dans le fichier i18n/descript_fr.properties
comme nous l'avons déjà fait auparavant
👉 Lancez à nouveau un "Reset" puis utilisez la fonction Admin > Créer la
base de données (alter)
pour forcer la création des champs en base de données.
L'interface du modèle "Coupon" affiche à présent un champ "Fichier" ayant la forme suivante:
Validations¶
La dernière notion que nous allons aborder dans cette leçon est la notion de validation. La validation consiste à spécifier des règles de contrôle des valeurs. Ces règles remplissent plusieurs objectifs : vérifier que les valeurs ont une forme convenable, vérifier que les valeurs sont valides et qu'elles sont sûres, c'est-à-dire qu'elles ne représentent pas un risque de sécurité.
Le moteur effectue un contrôle automatique du format de valeur après formatage. Par exemple qu'un champ lié à une colonne "integer" contient bien une valeur entière. Il est toutefois possible de spécifier des règles de validation personnalisées.
Contrôle (check
)¶
Nous allons mettre en place un contrôle qui vérifie qu'un libellé est bien saisi pour le champ "Nom" du modèle "Magasin". Il existe plusieurs façons de réaliser ce type de test. Nous allons passer en revue les différentes variantes possibles.
👉 Implémentez l'une des variantes ci-dessous dans votre application puis
effectuez un reset
pour prendre en compte la modification.
i) Règle "MANDATORY"¶
👉 Éditez le fichier descript.xml
et modifiez le champ nom
du modèle
magasin
ainsi:
1 2 3 |
|
Ici, on utilise une règle interne du moteur pour vérifier que le champ n'est pas vide.
ii) Expression régulière¶
👉 Éditez le fichier descript.xml
et modifiez le champ nom
du modèle
magasin
ainsi:
1 2 3 |
|
Ici, on utilise une expression régulière sur la valeur brute (source="raw"
).
La valeur brute est la valeur telle que reçue du formulaire html. Dans
certains cas, cette valeur brute peut différer de la valeur enregistrée en
base de données (que l'on appellera "valeur standardisée").
Prenons le cas d'une date. La valeur brute pourrait être "31.12.2023" dans le cas d'une locale "fr-CH" ou "12.31.2023" dans le cas d'une locale "en-US". Le moteur traite cette valeur brute en fonction de la locale et en produit une valeur standardisée au format iso 8601 "2023-12-31".
La validation s'effectuera donc sur la base d'une valeur différente selon la source indiquée:
raw
: valeur brutestd
: valeur standardisée
Pour les tests de valeur, il est recommandé de s'appuyer sur la valeur standardisée car les règles de validation n'ont pas à se soucier des différentes locales possibles.
Dans le cas ci-dessus, nous référençons la source raw
car le test de
validation que nous effectuons n'est pas influencé par la forme de la valeur.
iii) Script¶
👉 Éditez le fichier descript.xml
et modifiez le champ nom
du modèle
magasin
ainsi:
1 2 3 |
|
Ici, on utilise le moteur de script pour effectuer le test de validation. La
valeur brute est disponible au niveau de la variable data
. Le script doit
retourner la valeur true
pour indiquer que la validation réussi et false
pour indiquer qu'elle échoue.
Nettoyage (sanitize
)¶
Nous allons ajouter une règle de validation pour nettoyer le champ "Remarque" du modèle "Coupon".
👉 Éditez le fichier descript.xml
et modifiez le champ remarque
du modèle
coupon
ainsi:
1 2 3 |
|
Ici, on applique une règle de nettoyage sur la valeur pour lui enlever tous les éléments susceptibles de contenir une injection XSS, à l'exception de certains éléments (éléments de formatage, éléments de blocs, etc.)
Les règles d'autorisation correspondent aux sanitizers définis par la librairie Owasp: https://www.javadoc.io/doc/com.googlecode.owasp-java-html-sanitizer/owasp-java-html-sanitizer/20191001.1/org/owasp/html/Sanitizers.html
👉 Effectuez un reset pour prendre en compte la modification.
👉 Ouvrez ou créez une fiche "Coupon" puis essayez de saisir la valeur suivante dans le champ "Remarque":
hello <script>alert('xss')</script> world
👉 Cliquez sur "Enregistrer"
Vous pouvez alors constater que la partie <script>...</script>
de la
remarque a été retirée et qu'un avertissement a été émis par l'application:
Le libellé du message d'avertissement est un message prédéfini dans le moteur. Il est possible de le personnaliser.
👉 Modifiez la règle de validation pour lui ajouter un attribut
label="field.coupon.base.remarque.validation.warning"
👉 Éditez le fichier de langue i18n/descript_fr.properties
et ajoutez une
entrée pour la clé que nous avons référencée:
field.coupon.base.remarque.validation.warning = Vous ne pouvez pas intégrer \
d'élément html dans la remarque
👉 Effectuez un reset
puis essayez à nouveau de renseigner la valeur
contenant la partie <script>...</script>
dans le champ. Cliquez sur
"Enregistrer".
Vous noterez que la valeur a été nettoyée mais que cette fois l'avertissement correspond à celui que vous avez défini.
Use case de validation
Ce paragraphe est optionnel. Il approfondit le use case de validation de
type sanitize
.
Le contexte de validation sanitize
que nous avons mise en œuvre ici
n'est pas représentatif d'un cas réaliste, car nous avons appliqué une
règle de nettoyage html à un texte simple. Le but ici n'était pas tant
de démontrer le fonctionnement du sanitize en condition réelle, mais de
voir quel impact la règle de validation sanitize peut avoir sur une
valeur.
D'ailleurs l'utilisation du sanitize dans ce cas de figure représente un
risque de corruption de données. Saisissez par exemple la valeur hello <
world
dans la remarque puis cliquez sur "Enregistrer". Vous constaterez
que la valeur est changée en hello < world
. Cela vient du fait que le
sanitize a pour vocation de générer un code réutilisable en tant que html
et non pas en tant que texte.
La mise en place d'un use case plus réaliste demande plus d'efforts car le jeu de styles que nous utilisons est déjà construit pour éviter le plus possible le risque de Cross Site Scripting (XSS). En effet, pour qu'une injection XSS s'exprime, il faut que la valeur soit intégrée dans la page html au moyen d'une instruction XSL du genre
<xsl:value-of select="data" disable-output-escaping="yes"/>
or le jeu de style que nous utilisons n'en effectue aucun pour des données
provenant de champs (à l'exception du type de champ tinymce
, mais le
contrôle utilisé dans ce cas intègre son propre mécanisme de nettoyage).
Pour mettre en place un use case représentatif, il faut donc
construire un scénario qui provoque volontairement une faille permettant
l'injection XSS (ce qui n'est évidemment pas une bonne chose pour une
application). Pour ce faire, nous pouvons procéder ainsi:
👉 Créez un script _test.script
avec le contenu suivant:
$msg.info(#coupon[1].info.remarque, null, { disableOutputEscaping: "yes" });
👉 Adaptez le numéro de dossier (le "1" entre crochets dans l'exemple ci-dessus) au numéro de votre dossier. Le numéro de dossier apparaît à droite du libellé "Coupon", au-dessus des boutons "Enregistrer", "Fermer" et "Supprimer" de la fiche "Coupon".
Ce script reprend la valeur du champ "Remarque" du dossier coupon ayant l'identifiant "1" (que vous devrez adapter) dans un message de type "info", en indiquant explicitement de désactiver l'échappement. Ainsi, le texte de la remarque sera interprété comme du html.
👉 Désactivez la règle de validation (en la mettant en commentaire
dans le fichier descript.xml
), effectuez un reset
puis saisissez la
valeur suivante dans le champ "Remarque":
hello <script>alert('xss')</script> <b>world</b>
À l'enregistrement, le champ "Remarque" devrait afficher une valeur
inchangée. Cliquez à présent dans le menu "Fichier > Test", ce qui aura
pour effet d'exécuter le script _test.xml
que nous avons construit.
Un popup d'alerte avec le texte "xss" devrait s'afficher après
l'enregistrement. Cela signifie que l'application est à présent
sujette à l'injection XSS. Notez au passage que le mot "world" apparaît en
gras dans le message d'information.
👉 Réactivez alors la règle de validation, effectuez un reset
puis
cliquez à nouveau sur "Fichier > Test".
Le popup affichant "css" ne devrait plus apparaître car la valeur de
remarque a été nettoyée. Par contre le mot "world" devrait continuer de
s'afficher en gras si la règle FORMATTING
a bien été reprise dans la
règle de validation.
Cette illustration montre que le rôle du sanitize est de nettoyer un texte pour qu'il puisse être affiché de manière sure sous forme HTML.
Conclusion¶
Dans cette leçon, nous avons abordé différentes notions avancées de définitions de champs. Cela n'est de loin pas exhaustif et d'autres notions seront abordées dans les prochaines leçons.
La prochaine leçon sera orientée sur la gestion des permissions au moyen de policies. Cela nous permettra de mieux gérer l'affichage des champs en fonction des états et des droits d'accès.