Anomalie #23977
Expiration des cookies dans logoutAction (anciennement : Boucle de redirection sur edispatcher)
100%
Description
Certains utilisateurs nous font remonter une erreur "Boucle de redirection" sur edispatcher
Cela semble lié aux cookies (s'ils détruisent les cookies, le problème n'existe plus)
Posh fonctionne, xdesktop fonctionne (par contre les ressources ne se chargent pas dans xdesktop, ce qui semble logique vu que edispatcher a un problème)
A priori cela ne se produit que sous Firefox (pas sous chrome) et uniquement à l'extérieur (pas du rectorat)
Mais ces derniers éléments restent à confirmer car il est possible que le cookie en question ne soit présent que sur la configuration "habituelle" (donc Chrome ne plante pas car il n'est pas utilisé d'habitude).
Pour l'instant le cas est trop sporadique pour en tirer des conclusions fermes (3 cas, peut-être 4)
Dans les logs d'erreur php , j'ai 2 lignes qui reviennent en permanence :
"session_destroy(): Trying to destroy uninitialized session in /var/www/html/edispatcher/include/cas.php on line 42, referer: https://esterel.ac-nice.fr/edispatcher/ng/logout"
et
The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,' in Unknown on line 0, referer: https://esterel.ac-nice.fr/edispatcher/ng/hub?tmpl=nice/nicetfav&refresh
Impossible pour l'instant de dire si cela est lié au problème
Associated revisions
Fixes #23977 : Boucle de redirection dans edispatcher
Ref #23977 : Boucle de redirection dans edispatcher
ref #23977 : Boucle de redirection sur edispatcher , correction sur vidage cookie lors du logout
- ref #23977 : check correct Session ID
Fixes #23977 : Expiration des cookies dans logoutAction
History
#1 Updated by Renaud Dussol almost 5 years ago
Après une journée d'investigation, voici mon analyse :
un vieux cookie dispatcher est présent sur le poste
on demande une connexion à edispatcher
edispatcher voit que la session existe mais qu'elle n'est plus bonne (vieux cookie), il demande donc un ticket à eolesso
eolesso fait l'authentification, crée un ticket, l'envoie au client
mais edispatcher réutilise à nouveau le vieux cookie > là on entre dans la boucle
comme la session liée à ce cookie n'est pas bonne, du coup il redemande la vérification à eolesso
eolesso répond à nouveau "cet utilisateur est ok"
edispatcher reprend la main mais voit encore que la session est pas bonne
> on est dans la boucle de redirectionSi on détruit le cookie edispatcher, cela remarche
Le pb se situe au moment ou eolesso répond : edispatcher devrait alors détruire le cookie existant et ne pas rejouer le vieux cookie
Les logs d'erreur d'apache signalés auparavant ("The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-,") m'ont incité à faire un dump de cet id dans un fichier texte: il se trouve que quand cette erreur apparait, l'id en question est une chaîne vide (le tableau de session est vide également)
J'ai pu le reproduire sur un serveur de test en vidant la value (correspondant à l'ID) du cookie. Lorsque je rappelais ma page edispatcher ensuite, j'étais dans la boucle de redirection
Cependant cela s'est produit aussi sur le poste d'un collègue mais la value ne son cookie n'était pas vide. Je n'avais malheureusement pas encore fait le dump à ce moment-là
Dans les logs d'apache du serveur de prod l'erreur "The session id is too long or contains illegal characters, valid characters are a-z, A-Z, 0-9 and '-" apparait relativement souvent
Dans mon dump, lorsque l'erreur apparaît, il est toujours vide
Je soupçonne que même si côté client l'ID n'est pas vide, le cookie n'est plus reconnu côté serveur car le garbage collector de PHP est passé et à supprimé la référence à cet id, donc côté serveur cet ID n'existe plus et il le considère comme vide
En cherchant un peu, j'ai vu qu'il y avait un moyen relativement simple de supprimer ce problème : il faudrait passer la variable de configuration session.use_strict_mode à 1 dans php.ini
http://php.net/manual/en/session.configuration.php#ini.session.use-strict-mode
Cette variable n'est visiblement pas initialisée par défaut sur les serveurs envole et malheureusement par défaut elle est à 0
Elle force le serveur à refuser les cookies non-initialisés côté serveur, et à régénérer un cookie si le cookie envoyé par le client est invalide
Je ne sais pas si cela peut avoir des conséquences sur le reste mais en tout cas j'ai fait un test sur notre serveur de test, et cela marche parfaitement
Dans php.ini :
session.use_strict_mode = 1
Si je vide la value du cookie edispatcher et que je refreshe la page, un nouvel id est régénéré et ma page s'affiche désormais correctement, je n'ai plus la boucle
Le problème est que pour modifier cela, on touche au php.ini qui n'est plus du ressort du dev de dispatcher
Je propose donc d'ajouter une ligne ini_set() dans le fichier include/inc.php :
Juste avant la partie session (après le comment)
// Gestion de l'authentification
ini_set('session.use_strict_mode', '1');
if (session_status() == PHP_SESSION_NONE) {
J'ai fait le test et j'ai le même comportement qu'en modifiant le php.ini : je n'ai plus la boucle si je vide la value du cookie
#2 Updated by Renaud Dussol almost 5 years ago
- % Done changed from 0 to 50
#3 Updated by Renaud Dussol almost 5 years ago
- Status changed from Nouveau to Résolu
- % Done changed from 50 to 100
Appliqué par commit 0d889c5f7903cec1fdd1bdde50e5b7ea50aa0d9d.
#4 Updated by Renaud Dussol almost 5 years ago
- Status changed from Résolu to À valider
- % Done changed from 100 to 50
Le problème vient en fait de la fonction clearCookie située dans le template de logout
Le strict_mode ne sert pas à résoudre ce problème
En l'état actuel des choses, le strict mode peut poser problème
C'est pourquoi je retire la ligne ajoutée
Néanmoins il serait intéressant de voir comment on peut implémenter ce strict mode car ne pas l'utiliser représente un risque de sécurité
Le problème est que cela concerne envole dans son ensemble et non edispatcher seulement. Il faudrait que cela soit résolu au niveau d'apache.
#5 Updated by Renaud Dussol almost 5 years ago
Concernant le problème de boucle proprement dit, je pense avoir identifié le pb :
Dans le template de logout (logout.html.twig, appelé par l'url ng/logout), une fonction JS clearCookie, ayant pour objectif de faire expirer les cookies, n'a pas l'effet attendu :
1) la fonction Date qui sert à initialiser la date d'expiration du cookie ne marche pas et le cookie généré n'a pas de date d'expiration
2) en outre le cookie ainsi créé n'a pas de value, donc pas d'ID
Ce qui fait que lorsqu'un utilisateur a cliqué sur logout, il se retrouve avec un cookie vide, qui est envoyé au serveur à la prochaine reconnexion. Le serveur ne sait comment gérer ce cookie, car il ne peut pas enregistrer de fichier de session avec un nom vide. (D'ou les nombreuses erreurs dans les logs apache "Illegal character"....). La session n'est pas créée et du coup le ticket eolesso est constamment demandé, d'où la boucle.
3) enfin dernière remarque : le domaine du cookie généré est assorti d'un "." initial (.estereL.ac-nice.fr chez nous). Apparemment c'est un fonctionnement normal de javascript (si on veut avoir le domaine exact, il faut en fait laisser cet attribut vide et ne pas lui envoyer document.domain). Cela dit, cette erreur permet en fait de ne pas avoir la boucle dans la majorité des cas, puisque du coup le cookie vide ne remplace pas le cookie edispatcher (ce qui était son rôle au départ) mais est ajouté. A la reconnexion suivante, le client envoie donc 2 cookies edispatcher, un pour esterel.ac-nice.fr (celui de la session précédente, qui a bien un ID) et un pour .esterel.ac-nice.fr (celui créé par la fonction clearCookie). Le serveur remonte bien une erreur pour le cookie vide, mais parvient à créer la session pour l'autre cookie.
Comment régler le problème ?
Il y a plusieurs possibilités :
1) Technologie : javascript ou PHP ?
A priori je serais plus PHP si on arrive à le faire correctement. Le server-side me plait toujours plus que le client-side... Le seul avantage du JS est que la fonction peut être facilement modifiée avec le système de template. Le JS peut être une première solution, qui permettra d'aller vite en attendant une solution PHP qui viendra forcément d'un nouveau paquet. EN cas de php, il faudra le mettre dans la fonction logoutaction()
2) Contenu du cookie :
a) mettre une bonne date (en JS il faut tester, mais le pb semble venir du fait que ce n'est pas au format UTC, voir toUTCString())
b) affecter un ID au cookie :
- soit celui de l'ID du cookie de la session, voir si on peut le récupérer en JS
- soit un contenu statique, par exemple "logout"), ou un mélange d'infos statiques et dynamiques ("logout_IDdesession_date_heure")
c) vérifier si appeler le Cas:logout() permet de supprimer proprement le cookie edispatcher, on peut utiliser cela
3) En plus de cela, affecter une durée de validité au cookie à la connexion, avec un session_set_cookie_params(secondes). Cela permettrait que les personnes qui ne cliquent pas sur déconnexion soient bien déloguées au bout d'un moment. Firefox est souvent paramétré pour conserver les cookies, même après fermeture, et j'ai vu beaucoup trop de gens qui ne cliquaient pas sur "se déconnecter" mais fermaient simplement l'onglet ou la page du PIA (et parfois ne quittaient même pas le navigateur car d'autres fenetres étaient ouvertes)
#6 Updated by Renaud Dussol almost 5 years ago
Proposition pour le javascript :
- j'utilise l'attribut max-age plutôt que expires (plus facile à gérer, voir si cela fonctionne sur tous les navigateurs...) et je mets la durée de validité du cookie à une seconde (je n'ai pas testé la valeur 0)
- dans la value, plutôt que de mettre du vide, je met le nom du cookie suivi de "_logout" (ex : edispatcher_logout)
- plus de domaine, il est automatiquement bien renseigné
- les 2 alert sont pour voir les cookies se modifier (et disparaître) au fur et à mesure, ils seront bien sûr à virer en production
function clearCookie(name, path){
alert(document.cookie);
var path = path || "/";
var c_value = name+"_logout; max-age=1; path="+path;
document.cookie = name + "=" + c_value;
alert(document.cookie);
};
#7 Updated by Christophe LEON almost 5 years ago
Merci encore pour ces investigations
Pour le expires et max-age.
expires fixe la date max et max-age le temps restant le cookie a vivre
A priori y a que IE qui ne supportent pas max-age
Dans un premier temps ont peut effectivement tester en js
Je suis d'accord il est nécessaire de le faire coté serveur.
L'action logout côté controlleur devrait appeler le CasLogout , je testerais chez nous
#8 Updated by Renaud Dussol almost 5 years ago
- File logout.html.twig added
- % Done changed from 50 to 70
J'ai modifié le template
Les cookies sont bien supprimés grâce à ce système désormais
Aucun cookie vide n'est envoyé
J'en ai profité pour faire un echo des "services" déconnectés (cela permet de vérifier si les cookies ont bien été supprimés)
J'ai aussi viré l'héritage du layout.html.twig, qui selon moi chargeait trop de chose sinutiles dans une page de logout (on perd un peu de mise en forme cela dit)
A la fin, au lieu d'un iframe vers le logout d'eolesso, je fais un redirect (c'est mieux chez nous, car nous avons mis une url de logout automatique sur eolesso)
Pour info je joins mon template en PJ
Je vais m'attaquer à la partie PHP
NB : afin de virer les cookies vide qui traînaient sur les clients, j'ai été obligé de faire une détection dans le include/inc.php (juste après le session_start) et de faire un setcookie pour lui mettre une date d'expiration à n+1 seconde
C'est parce que comme le nouveau système au logout détruit proprement les "bons" cookies, pour les utilisateurs chez qui il y avait le mauvais cookie, il ne reste plus que celui-là, et là ça fait des boucles en pagaille !!!
Je fais un log juste avant pour traquer tous les cookies vides qui restent encore
Ceci est temporaire, je pense qu'au bout d'une ou deux semaines je pourrai le virer
Je le fais pour les 3 cookies concernés : edispatcher, mxdesktop et PHPSESSID
if ($_COOKIE[session_name()] === '' ) {
file_put_contents('/tmp/logsession.txt', '\n'.date("Y-m-d H:i:s")." --- EDISPATCHER : COOKIE VIDE\n", FILE_APPEND);
setcookie("edispatcher", time()."edispatcher_erase_old_empty_cookies".session_id(), time()+1, "/", ".esterel.ac-nice.fr");
}
if ($_COOKIE['mxdesktop'] === '' ) {
file_put_contents('/tmp/logsession.txt', '\n'.date("Y-m-d H:i:s")." --- MXDESKTOP --- COOKIE VIDE\n", FILE_APPEND);
setcookie("mxdesktop", time()."mxdesktop_erase_old_empty_cookies".session_id(), time()+1, "/", ".esterel.ac-nice.fr");
}
if ($_COOKIE['PHPSESSID'] === '' ) {
file_put_contents('/tmp/logsession.txt', '\n'.date("Y-m-d H:i:s")." --- PHPSESSID --- COOKIE VIDE\n", FILE_APPEND);
setcookie("PHPSESSID", time()."PHPSESSID_erase_old_empty_cookies".session_id(), time()+1, "/", ".esterel.ac-nice.fr");
}
#9 Updated by Renaud Dussol almost 5 years ago
Je pense qu'il serait intéressant d'inclure dans le logout la destruction des cookies d'autres applications (wordpress, grr, etc...)
Le pb est qu'il est difficile de le lister en dur dans le code
J'ai imaginé une solution radicale : détruire tous les cookies liés à la session... Est-ce que ça ne risque pas d'avoir des effets de bords ?
Si ça n'en a pas, ce serait le plus simple et le plus efficace
foreach ($_COOKIE as $cookie=>$value) {
setcookie($cookie, $cookie.'logout', time()+1, '/') ;
}
Sinon, se construire à part un tableau des cookies que l'on veut supprimer
genre config/local/cookiestodestroy.inc.php
et dans le code :
foreach ($_COOKIE as $cookie=>$value) {
if (in_array($cookie, $cookiestodestroy)) {
setcookie($cookie, $cookie.'logout', time()+1, '/') ;
}
Mais cela obligerait l'admin à connaître les cookies qu'il souhaite supprimer... (on pourrait toutefois ajouter à la condition "|| $cookie === "edispatcher" || $cookie === "mxdesktop")
Je ne sais pas : y a-t-il des cookies envole qu'il faut conserver sur un poste client ? Si ce n'est pas le cas, autant tout supprimer...
#10 Updated by Renaud Dussol almost 5 years ago
ou alors plutôt que sur la condition faire un array_push($cookiestodestroy, "edispatcher", "mxdesktop");
#11 Updated by Renaud Dussol almost 5 years ago
- Subject changed from Boucle de redirection sur edispatcher to Expiration des cookies dans logoutAction (anciennement : Boucle de redirection sur edispatcher)
Voici les cookies que je récupère lors d'une connexion à envole (tableau $_COOKIE)
Connexion "simple" , c'est à dire uniquement la page d'accueil donc avec posh et edispatcher
myhomepage : q045jujb1257kg8fj10h25mg84
nbconn : 1
showmenu : 0
edispatcher : ag9nhqshp38uf5uk3mpt22fum6
_pk_id_1_0fdb : aa4408a5046446d4.1528793218.1.1528793218.1528793218.
_pk_ses_1_0fdb : *
mxdesktop : ST-eolesso-ihdinac-nicefr-7a6f262f7b4a405c7d1fe98855778f6d58577fc8c76a3a6b25852ed2
PHPSESSID : 8gonsk0itqpsqsqhkce5sqig32
Cela pose-t-il problème selon toi d'expirer tous ces cookies ? Je ne sais pas pourquoi il y en a autant... je présume que la plupart sont issus de posh, car quand je fais une connexion directe à edispatcher, je n'ai que edispatcher et mxdesktop
#12 Updated by Christophe LEON almost 5 years ago
Salut,
Je pense que si le serveur mutualise d'autres applis ( hors Envole ) cela peut poser problème effectivement
Peut être avoir une entrée dans gen_config, ou un paramètre géré par appManager, précisant les cookies que l'on souhaite supprimer lors du logout
nbconn, showmenu c'est généré par posh il me semble
_pk_id_1_0fdb et _pk_ses_1_0fdb c'est du piwik, les supprimer fausserait peut être le tracking
Christophe
#13 Updated by Renaud Dussol almost 5 years ago
Voici le code que je propose :
include_once('/var/www/html/edispatcher/include/cookies.php');
$cookies2destroy[] = "edispatcher";
$cookies2destroy[] = "mxdesktop";
//print_r($cookies2destroy);
$cookies = array_keys($_COOKIE);
$badcookies = array_diff($cookies2destroy,$cookies);
if ($badcookies) {
echo "<script>console.log('ATTENTION : DES COOKIES A SUPPRIMER DU FICHIER include/cookies.php NE SONT PAS REELEMENT PRESENTS :');";
foreach ($badcookies as $badcookie) {
echo "console.log('- COOKIE : ".$badcookie."');";
}
echo "</script>";
}
$cookies2reallydestroy = array_intersect($cookies2destroy,$cookies);
foreach ($cookies2reallydestroy as $c) {
// echo "COOKIE A DETRUIRE : ".$c."<br>";
setcookie($c, time().$c.'logout', time()+1, '/');
}
On confontre une liste préexistante de cookies qu'on veut supprimer au tableau des cookies existants
On fait un petit warning dans la console si des cookies de la liste n'existent pas
Et on supprime ceux qui sont dans la liste et qui sont réellement présents
Bien sur on ajoute automatiquement edispatcher et mxdesktop à la liste, puisque ceux-là, on est surs qu'il faut les virer
La seule question qui reste : est-ce qu'on fait un système identique à la variable gen_config edispatcher_logouts ou bien on laisse un fichier à part (comme j'ai fait pour l'instant)
Et est-ce qu'on laisse coexister les 2 modes (iframes ET cookies) ou on n'en laisse plus qu'un seul
on pourrait utiliser le tableau _LOGOUTS pour avoir les valeurs des cookies mais dans ce cas il ne faurdrait plus qu'il y ait des urls dedans...
Ou alors on laisse le choix mais dans ce cas il faut bien documenter ce parametre...
#14 Updated by Renaud Dussol almost 5 years ago
Je pense qu'il serait bon, à la création des 2 cookies "edispatcher" et "mxdesktop", de leur assigner une durée de validité
Avec session_set_cookie_params() ou bien avec setcookie()
Il faut quand même en discuter car cela peut avoir un impact
#15 Updated by Renaud Dussol almost 5 years ago
Deux autres petites choses qui me chiffonent :
- Au logout, un cookie "valide" edispatcher est toujours immédiatement recréé, je ne comprends pas pourquoi
- Il semble que le test "IS VALID SESSION" de inc.php renvoie toujours false, donc on rentre toujours dans ce if et le session_start semble être exécuté un grand nombre de fois...
#16 Updated by Renaud Dussol almost 5 years ago
- Status changed from À valider to Résolu
- % Done changed from 70 to 100
Appliqué par commit 9b5ddd0d9d3a5189e9f0358b78a8187c937be61a.
#17 Updated by Arnaud FORNEROT over 4 years ago
- Target version set to Envole 5.10
#18 Updated by Arnaud FORNEROT over 4 years ago
- Status changed from Résolu to Fermé