Résoudre le problème des cookies multiples pour le jeton CSRF
Le problème
Lors du passage de CubicWeb 3.38 à CubicWeb 4.8 et la mise à jour des cubes, une des composantes du projet utilisant cwclientlib
(en version 1.2.2) a eu CookieConflictError
pour le jeton CSRF. Grâce à la version 1.4.2 de cwclientlib
, il a été identifié que le problème venait qu'il y a plusieurs jetons CSRF pour le même domaine mais avec des chemins différents, l'un étant pour /
et l'autre pour /rqlio
(donc ce n'est pas le cube API qui est utilisé, mais l'ancienne méthode, à savoir le couple signedrequest
+ rqlcontroller
, ce qui est cohérent avec l'âge du projet et la très probable non-configuration de request_mech
).
__init__ 63 unhandled exception:
There are multiple cookies with name, 'csrf_token'
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/celery/app/trace.py", line 451, in trace_task
R = retval = fun(*args, **kwargs)
File "/usr/local/lib/python3.9/dist-packages/celery/app/trace.py", line 734, in __protected_call__
return self.run(*args, **kwargs)
File "/usr/local/lib/python3.9/dist-packages/projet_workers/forecasting.py", line 84, in calibrate
prepare_and_run_calibration(client, eid, directory, **opts)
File "/usr/local/lib/python3.9/dist-packages/projet_workers/forecasting.py", line 178, in prepare_and_run_calibration
opts = prepare_calibration(client, eid, directory, **opts)
File "/usr/local/lib/python3.9/dist-packages/projet_workers/forecasting.py", line 156, in prepare_calibration
opts['Use_Past_Report'] = prepare_previous_calibration(
File "/usr/local/lib/python3.9/dist-packages/projet_workers/forecasting.py", line 96, in prepare_previous_calibration
resp = client.rql(request removed
File "/usr/local/lib/python3.9/dist-packages/tenacity/__init__.py", line 324, in wrapped_f
return self(f, *args, **kw)
File "/usr/local/lib/python3.9/dist-packages/tenacity/__init__.py", line 404, in __call__
do = self.iter(retry_state=retry_state)
File "/usr/local/lib/python3.9/dist-packages/tenacity/__init__.py", line 349, in iter
return fut.result()
File "/usr/lib/python3.9/concurrent/futures/_base.py", line 433, in result
return self.__get_result()
File "/usr/lib/python3.9/concurrent/futures/_base.py", line 389, in __get_result
raise self._exception
File "/usr/local/lib/python3.9/dist-packages/tenacity/__init__.py", line 407, in __call__
result = fn(*args, **kwargs)
File "/usr/local/lib/python3.9/dist-packages/projet_workers/__init__.py", line 57, in wrapper
resp = func(*args, **kwargs)
File "/usr/local/lib/python3.9/dist-packages/projet_workers/__init__.py", line 99, in rql
return super(CWProxy, self).rql(*args, **kwargs)
File "/usr/local/lib/python3.9/dist-packages/cwclientlib/cwproxy.py", line 320, in rql
if self._csrf_token is None:
File "/usr/local/lib/python3.9/dist-packages/cwclientlib/cwproxy.py", line 165, in _csrf_token
return self._requests_session.cookies.get("csrf_token")
File "/usr/local/lib/python3.9/dist-packages/requests/cookies.py", line 202, in get
return self._find_no_duplicates(name, domain, path)
File "/usr/local/lib/python3.9/dist-packages/requests/cookies.py", line 405, in _find_no_duplicates
raise CookieConflictError(
requests.cookies.CookieConflictError: There are multiple cookies with name, 'csrf_token'
Versions testées
Versions de CubicWeb
- 4.8.0 : le problème est là.
- 4.7.1 : le problème est là.
- 4.6.4 : le problème est là.
- 4.5.2 : pas le problème.
- 4.4.0 : pas le problème.
- 4.3.0 : pas le problème.
- 3.38.16 et avant : pas le problème.
Version des cubes
Avec la 4.5.2, il y avait la même version pour les cubes que pour la 4.6.4; 4.7.1 et 4.8.0. Par conséquent, le problème ne semble venir de l'évolution d'un ou plusieurs cubes, mais il semble venir de CubicWeb(-core).
$ grep -ri --include=*.py route_prefix ~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/
$ grep -ri --include=*.py path ~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/
$ grep -ri --include=*.py csrf ~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: """RqlIOController with csrf desactivated for application/json because
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: require_csrf = False
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: """RqlIOController with csrf activated for cookie authenticated user
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: To have csrf deactivated with multipart/form-data, install
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: """RqlIOController with csrf desactivated for anonymous_user.
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: require_csrf = False
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: RqlIOController with CSRF check deactivated for a user authenticated by
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: require_csrf = False
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: require_csrf = False
$ grep -ri --include=*.py ~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: """RqlIOController with csrf activated for cookie authenticated user
~/.virtualenvs/projet/lib/python3.9/site-packages/cubicweb_rqlcontroller/views.py: & ~match_all_http_headers("Authorization") # no Authorization == cookie
Il semble donc de toute façon improbable que ce soit le cube rqlcontroller qui introduise de lui-même le problème.
En local
Avec CubicWeb 4.8.0 et cwclientlib 1.2.2, on n'a pas réussi à reproduire le problème en appelant d'abord rqlio
puis rql
et de même en sens inverse. Il n'y a toujours qu'un seul chemin et avec le bon chemin.
Particularité du projet
Le projet surcharge la vue HTTP rqlio
pour journaliser les requêtes RQL (voir cubicweb/cubes/rqlcontroller!51) et à priori faire aussi d'autres petites choses. Cette surcharge pourrait être à l'origine de l'émission d'un cookie CSRF. Cependant il faudrait néanmoins que cet éventuel émission implicite de cookie CSRF soit faite avec le bon chemin (path
).
Solutions envisagées
Il faudrait à priori idéalement qu'il n'y ait qu'un seul cookie pour le jeton CSRF, ce qui n'est pas de l'ordre cwclientlib
mais de la partie serveur. Une autre solution, qui ne s'oppose pas à la première et permettrait de gérer l'existant mais qui est moins élégante, est de tenter ces 2 chemins côté cwclientlib
et d'abord /rqlio
car c'est le plus spécifique.
Contournement
Utiliser cwclientlib
version 1.4.3.
Analyse
cwclientlib
Côté En-têtes HTTP différentes
Le projet n'utilise pas le cube API (la nouvelle interface serveur), mais le couple signedrequest
+ rqlcontroller
(l'ancienne interface serveur).
# def rql
headers = {
"Accept": "application/json",
"Date": date_header_value(),
"Origin": self._base_url,
"X-CSRF-Token": self._csrf_token,
}
# def rqlio : pas de CSRF (et d'origine) !
headers = {
"Accept": "application/json",
"Date": date_header_value(),
}
Côté serveur
Cube RQL controller
grep -ri --include=*.py csrf cubicweb_rqlcontroller/
ne renvoit rien qui laisse à penser qu'il génère un cookie CSRF, donc le problème se trouve à priori dans CubicWeb, à moins que ça ne soit la faute d'une surcharge du cube dans le projet.
CubicWeb
grep -ri --include=*.py csrf cubicweb
- Le seul endroit qui semble générait le cookie est la méthode
new_csrf_token
qui appellepyramid.csrf.CookieCSRFStoragePolicy.new_csrf_token
. -
CWCookieCSRFStoragePolicy(path=config.route_prefix)
pourrait être le problème, carconfig.route_prefix
pourrait avoir une valeur différente en fonction qu'il est appelé par CubicWeb(-core) ou par un cube. Cette modification, pensée comme un correctif, a été introduit dans CubicWeb 4.6.0. Cependant, en CubicWeb 4.6.2, le comportement a changé pourconfig.set_csrf_storage_policy(CWCookieCSRFStoragePolicy(path=urlparse(base_url).path))
(voir cubicweb#1017 (closed)).