Skip to content

Problèmes connus

Ce chapitre revient sur certains problèmes identifiés et pour lesquelles il n'existe pas de solution directement intégrée à Ewt. Des axes de solution sont proposés lorsque cela est possible.

Duplications de sessions

Lorsqu'on duplique un onglet sur lequel on est connecté à une application Ewt, cela ouvre une nouvelle fenêtre qui partage la même session et les mêmes cookies. Le moteur n'est alors pas capable de faire la distinction entre les requêtes provenant de l'un ou l'autre des deux onglets. Le fait que deux onglets distincts (ou plus) génèrent des requêtes pour une même session Ewt peut entraîner au mieux des blocages de policies ou, au pire, des écrasements de valeurs ou des comportements imprévisibles. Il n'est donc pas souhaitable que cette situation se produise.

En réalité il n'est pas possible d'empêcher un utilisateur de dupliquer un onglet et il n'est pas possible pour le moteur de lier une session à un onglet car il n'existe pas de standard commun aux différents navigateurs qui permette d'identifier un onglet. Chaque navigateur propose des plugins spécifiques, mais il n'existe pas de spécification standardisée à l'heure où ces lignes sont écrites.

Pour limiter le problème, il est donc recommandé de mettre en place une sécurité au niveau du navigateur lui-même. Cela peut se faire en JavaScript à l'aide de BroadcastChannel:

Le service BroadcastChannel permet d'envoyer et de recevoir des messages au sein des différents onglets, des différentes fenêtres, iframe ou workers qui portent sur une même session (la portée est en réalité un peu différente de cela, mais cela correspond à notre besoin).

Une solution est donc d'utiliser cette interface pour mettre en place une communication entre les onglets qui partagent une même session. L'idée est d'envoyer un message invalidate via le channel lors d'un submit afin d'indiquer à tous les autres onglets qu'ils doivent s'invalider. Chaque onglet décidera alors du comportement à adopter (afficher un bandeau d'avertissement à l'écran, fermer tous les dossiers ouverts, forcer un retour à la page d'accueil au moyen d'une requête GET, etc.)

Ci-dessous, nous donnons un exemple de solution qui va dans ce sens.

 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
34
35
36
37
38
39
40
const ewt = {
    // broadcast channel utilisé pour synchroniser les onglets pointant
    // sur une même session
    channel: undefined,

    init: function() {
        // initialisaiton du channel (on utilise le nom de session
        // comme nom de channel) et enregistrement de la méthode
        // qui gérera les messages
        this.channel = new BroadcastChannel(appSettings.session);
        this.channel.addEventListener('message', ewt.onMessage);
    },

    onMessage: function(ev) {
        if (ev?.data) {
            if (ev.data.message == 'invalidate') {
                console.log("Received `invalidate` message");
                window.open(appSettings.requestUrl, "_self");
            }
        }
    },

    // effectue un submit standard sur /web
    submit: function(action, params, context) {
        let command = { action: action, params: params };

        document.getElementById('ewt_command').value = JSON.stringify(command);
        document.getElementById('ewt_context').value = context;

        if (this.channel) {
            // envoi message d'invalidation aux autres onglets liés
            // à la session
            this.channel.postMessage({ message: 'invalidate' });
        }

        document.getElementById('form').submit();
    }
}

document.addEventListener('DOMContentLoaded', ewt.init);

Ici la méthode onMessage force un retour à la page d'accueil de l'application au moyen de window.open. Cette action offre deux avantages:

  1. Elle force l'utilisateur à quitter tout dossier ouvert qui pourrait être modifié depuis un autre onglet
  2. Elle force la création d'une session propre à l'onglet, ce qui permet d'éviter les conflits de sessions sur les requêtes à venir.

On pourrait améliorer ce comportement par exemple en annonçant à l'utilisateur que la page va se fermer.

Erreurs liées au reverse proxy

Certaines erreurs peuvent apparaître lorsque l'application est déployée derrière un reverse proxy, du fait que certains éléments d'en-tête ne sont pas correctement véhiculés, notamment les cookies de session.

cas 1 : le serveur tomcat attend des données chiffrées mais reçoit des données en clair

Symptomes: Le problème peut s'exprimer de différentes manières.

  • le navigateur affiche le message suivant lors de l'authentification

    Les informations saisies vont être transmises en clair (sans 
    chiffrement). Elles peuvent donc éventuellement être interceptées 
    et lues logs de leur acheminement.  
    Voulez-vous vraiment transmettre ces informations ?
    
  • la trace réseau montre que certaines requêtes transitent via une URL en http

Comme le serveur Apache Httpd effectue une redirection vers un port HTTP, le serveur Tomcat ne voit pas qu'elle provient en réalité d'une connexion sécurisée. Il faut donc demander à Apache Httpd d'indiquer cela au niveau de l'entête HTTP.

Éditez le fichier de configuration ssl présent dans /etc/apache/sites-enabled.

Ajoutez les lignes suivantes dans votre configuration:

ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"

Variante possible

ProxyPreserveHost On
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}
RequestHeader set "X-Forwarded-SSL" expr=%{HTTPS}

Au niveau de la config Tomcat, éditez le fichier server.xml et ajoutez la valve suivante à la fin du bloc /Server/Service/Engine/Host:

<Valve className="org.apache.catalina.valves.RemoteIpValve"
       remoteIpHeader="x-forwarded-for"
       remoteIpProxiesHeader="x-forwarded-by"
       protocolHeader="x-forwarded-proto"
       portHeader="x-forwarded-port"/>

Redémarrez ensuite Tomcat et Httpd:

sudo systemctl restart apache2 tomcat

Il se peut que ce dernier signale une erreur au redémarrage, en mentionnant une erreur de syntaxe avec l'entrée RequestHeader. Si tel est le cas, lancez la commande suivante pour installer le module mod_header:

sudo a2enmod headers

Il se peut que les cookies soient perdus lors du traitement de la requête par Httpd, du fait qu'il porte sur un domaine et/ou un contexte qui diffère entre l'interne et l'externe.

Éditez le fichier de configuration ssl présent dans /etc/apache/sites-enabled.

Ajoutez les lignes suivantes:

ProxyPassReverseCookiePath /internalContext /externalContext
ProxyPassReverseCookieDomain localhost example.com

Sauvegardez les modifications et redémarrez le serveur Apache Httpd.

Message d'avertissement "Unable to get fieldset for `...`"

Illustrons le cas avec un exemple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<field name="idAutorisation" type="label" column="IdAutorisation">
    <options mode="sql">
        <value>select IdAutorisation, Resume from Autorisation where IdAutorisation=?</value>
        <text:param>#base.idAutorisation</text:param>
    </options>
    <properties>
        <property name="function">
            <text:function icon="verified" position="left" class="btn btn-primary"
                           i18n:title="field.facture.base.idAutorisation.button.autorisation.title">
                ewt.submit('open', { modelName: 'autorisation', docId: '${data:base.idAutorisation}' } )
            </text:function>
            <text:function icon="id_card" position="left" class="btn btn-primary"
                           i18n:title="field.facture.base.idAutorisation.button.client.title">
                ewt.submit('open', { modelName: 'client', docId: '${data:autorisation[${data:facture.base.idAutorisation}].base.idClient}' } )
            </text:function>
        </property>
    </properties>
</field>

Le champ ci-dessus est un champ en lecture seule qui référence une autorisation. L'autorisation en question est liée à une fiche client, et on profite de ce lien pour afficher deux boutons sur la gauche du champ: le premier référence l'autorisation et le second référence le client.

À l'exécution, les messages d'avertissement suivants s'affichent dans le log:

1
2
3
WARN - Unable to get fieldset for `base.idAutorisation`: no current resolution context
WARN - Unable to get fieldset for `facture.base.idAutorisation`: no current resolution context
WARN - Unable to get fieldset for `autorisation[${data:facture.base.idAutorisation}].base.idClient`: no current resolution context

L'avertissement est d'autant plus étrange que les boutons fonctionnent parfaitement et contiennent des identifiants de dossiers valides.

Le problème vient du fait que la property ne contient aucune indication de destination. Le moteur applique donc la destination par défaut. Dans le contexte d'un champ, il s'agit de descript-full,document. Or le moteur n'est pas capable de résoudre les références de contexte lorsqu'il doit générer les éléments pour la partie descript de l'arbre de sortie, car à ce moment il n'est pas dans le contexte d'un dossier.

La solution pour éviter l'avertissement est donc de spécifier explicitement la destination en changeant la ligne 7 ainsi:

<property name="function" instr:destination="document">

L'insertion multiple ne fonctionne pas avec $sql.insert

Ce problème est documenté au niveau de la méthode $sql.insert. La solution rapide consiste à utiliser $sql.update dans ce cas.