Projet

Général

Profil

Development #58208

ne pas modifier les données en session dans autosave

Ajouté par Benjamin Dauvergne il y a plus de 2 ans. Mis à jour il y a plus de 2 ans.

Statut:
Fermé
Priorité:
Normal
Assigné à:
Version cible:
-
Début:
26 octobre 2021
Echéance:
% réalisé:

0%

Temps estimé:
Patch proposed:
Oui
Planning:
Non

Description

Cf. #58161

En cas de forte charge on a visiblement des autosave et des soumissions classiques qui se croisent pour une même page, normalement ça doit bien se passer avec le autosave qui passe soit avant, soit qui est refusé parce que _ajax_form_token n'est plus valide, mais dans de rare cas on peut avoir un autosave qui se finit après la soumission de la page correspondante. Associé à #58207 ça peut amener à avoir des données normalement invalides dans formdata.data (champ requis à None).

Une solution serait d'attendre la fin d'un autosave en cours quand le bouton submit est cliqué, puis des les bloquer ensuite.


Fichiers

0002-forms-prevent-autosave-from-overwriting-magictoken-s.patch (1,19 ko) 0002-forms-prevent-autosave-from-overwriting-magictoken-s.patch Benjamin Dauvergne, 26 octobre 2021 22:40
0001-formdef-do-not-load-data-from-item-s-widgets-with-er.patch (4,41 ko) 0001-formdef-do-not-load-data-from-item-s-widgets-with-er.patch Benjamin Dauvergne, 26 octobre 2021 22:40
0002-forms-prevent-autosave-from-overwriting-magictoken-s.patch (1,19 ko) 0002-forms-prevent-autosave-from-overwriting-magictoken-s.patch Benjamin Dauvergne, 27 octobre 2021 09:11
0001-formdef-do-not-load-data-from-item-s-widgets-with-er.patch (4,41 ko) 0001-formdef-do-not-load-data-from-item-s-widgets-with-er.patch Benjamin Dauvergne, 27 octobre 2021 09:11
0003-js-prevent-autosave-while-user-is-active-58208.patch (1,57 ko) 0003-js-prevent-autosave-while-user-is-active-58208.patch Benjamin Dauvergne, 27 octobre 2021 09:11
0002-forms-prevent-autosave-from-overwriting-magictoken-s.patch (1,19 ko) 0002-forms-prevent-autosave-from-overwriting-magictoken-s.patch Benjamin Dauvergne, 27 octobre 2021 09:20
0001-formdef-do-not-load-data-from-item-s-widgets-with-er.patch (4,41 ko) 0001-formdef-do-not-load-data-from-item-s-widgets-with-er.patch Benjamin Dauvergne, 27 octobre 2021 09:20
0003-js-prevent-autosave-while-user-is-active-58208.patch (1,99 ko) 0003-js-prevent-autosave-while-user-is-active-58208.patch Benjamin Dauvergne, 27 octobre 2021 09:20
0002-forms-prevent-autosave-from-overwriting-magictoken-s.patch (1,19 ko) 0002-forms-prevent-autosave-from-overwriting-magictoken-s.patch Benjamin Dauvergne, 27 octobre 2021 09:54
0001-formdef-do-not-load-data-from-item-s-widgets-with-er.patch (4,41 ko) 0001-formdef-do-not-load-data-from-item-s-widgets-with-er.patch Benjamin Dauvergne, 27 octobre 2021 09:54
0003-js-prevent-autosave-while-user-is-active-58208.patch (2,23 ko) 0003-js-prevent-autosave-while-user-is-active-58208.patch Benjamin Dauvergne, 27 octobre 2021 09:54
0001-forms-prevent-autosave-from-overwriting-magictoken-s.patch (1,19 ko) 0001-forms-prevent-autosave-from-overwriting-magictoken-s.patch Benjamin Dauvergne, 28 octobre 2021 18:55
0001-forms-prevent-autosave-from-overwriting-session-s-da.patch (1,88 ko) 0001-forms-prevent-autosave-from-overwriting-session-s-da.patch Benjamin Dauvergne, 28 octobre 2021 21:50
0001-forms-prevent-autosave-from-overwriting-session-s-da.patch (4,06 ko) 0001-forms-prevent-autosave-from-overwriting-session-s-da.patch Benjamin Dauvergne, 28 octobre 2021 22:20
0001-forms-prevent-autosave-from-overwriting-session-s-da.patch (4,01 ko) 0001-forms-prevent-autosave-from-overwriting-session-s-da.patch Benjamin Dauvergne, 08 novembre 2021 12:34
0001-forms-prevent-autosave-from-overwriting-session-s-da.patch (4,85 ko) 0001-forms-prevent-autosave-from-overwriting-session-s-da.patch Benjamin Dauvergne, 23 novembre 2021 11:21
0001-forms-prevent-autosave-from-overwriting-session-s-da.patch (4,86 ko) 0001-forms-prevent-autosave-from-overwriting-session-s-da.patch Benjamin Dauvergne, 23 décembre 2021 09:06

Révisions associées

Révision a6162541 (diff)
Ajouté par Frédéric Péters il y a plus de 2 ans

forms: prevent autosave from overwriting session's data (#58208)

Historique

#2

Mis à jour par Benjamin Dauvergne il y a plus de 2 ans

  • Assigné à mis à Benjamin Dauvergne
#3

Mis à jour par Frédéric Péters il y a plus de 2 ans

Peut-être partiel mais il y a déjà un bout qui tente de prévenir ça, dans test_form_autosave on peut lire :

    # make sure autosave() doesn't destroy data that would have been submitted
    # in the meantime
#4

Mis à jour par Frédéric Péters il y a plus de 2 ans

Le moment particulier étant j'imagine caché dans :

mais dans de rare cas

et ça serait :

        # reload session to make sure _ajax_form_token is still valid
        session = get_session_manager().get(get_session().id)
        if not session:
            return result_error('cannot get ajax form token (lost session)')
        if not session.has_form_token(get_request().form.get('_ajax_form_token')):
            return result_error('obsolete ajax form token (late check)')

# ici on pense avoir vérifié juste à temps, au plus proche, que le _ajax_form_token était ok
# mais raté, pile maintenant il ne l'est plus.
        try:
            self.save_draft(form_data, page_no)
#5

Mis à jour par Benjamin Dauvergne il y a plus de 2 ans

Ceinture et bretelle.

#6

Mis à jour par Benjamin Dauvergne il y a plus de 2 ans

Frédéric Péters a écrit :

Le moment particulier étant j'imagine caché dans :

mais dans de rare cas

et ça serait :

[...]

Oui, mais je pense vraiment que ça ne sert à rien d'essayer de faire plus compliqué coté serveur, on y arrivera pas, on écrit de toute façon en session sans verrou ni aucune protection contre les accès concurrents; c'est peine perdue. Par contre on peut se protéger efficacement coté front, en évitant de lancer plusieurs requêtes en même dans la même page (AJAX + </submit>), ça on peut l'empêcher facilement.


En même temps que j'écrivais tout ça j'ai pensé qu'on pourrait aussi éviter qu'autosave modifie les données en session, ce qui corrigerait il me semble les deux tickets (#58028 et #58027) en même temps :

diff --git a/wcs/forms/root.py b/wcs/forms/root.py
index 28449a96..8fe03c7a 100644
--- a/wcs/forms/root.py
+++ b/wcs/forms/root.py
@@ -1324,14 +1324,18 @@ class FormPage(Directory, FormTemplateMixin):
         session = get_session()
         if not session:
             return result_error('missing session')

         self.feed_current_data(magictoken)

         form_data = session.get_by_magictoken(magictoken, {})
+        # prevent autosave to write into session concurrently with user's
+        # submits, only do it when initializing the draft formdata.
+        if form_data.get('draft_formdata_id'):
+            form_data = form_data.copy()
         if not form_data:
             return result_error('missing data')

         try:
             page = self.pages[page_no]
         except IndexError:
             # XXX: this should not happen but if pages use conditionals based

L'idée étant qu'une fois qu'on a établi le lien entre les données en session et le draft via la donnée spéciale "draft_formdata_id" on ne modifie plus les données en session dans autosave(), on travaille uniquement sur une copie. Normalement après la première page ou le premier autosave on sait qu'on aura plus de concurrence entre submit() et autosave, c'est toujours les données en session qui gagnent, on peut avoir un draft qui diffère de l'état actuel de la session, mais ça ne durera pas jusqu'à la soumission finale, puisqu'à chaque soumission on mettra à jour le draft avec les bonnes données validées.

PS: j'avais écrit tout ça avant de travailler sur la solution proposée plus haut mais j'ai oublié de poster.

#14

Mis à jour par Benjamin Dauvergne il y a plus de 2 ans

Ça n'est clairement pas suffisant, même si les données ne sont pas modifiées elles sont anciennes et viendront écraser les données plus récentes puisque la session est systématiquement écrasée à chaque requête réussie et je remarque maintenant le traitement particulier fait quand la requête autosave échoue via get_request().ignore_session = True.

J'adopte donc le même comportement vis à vis de la session si draft_formdata_id n'est pas modifié.

#16

Mis à jour par Benjamin Dauvergne il y a plus de 2 ans

  • Sujet changé de formulaire: un autosave peut se terminer après la soumission à ne pas modifier les données en session dans autosave
#17

Mis à jour par Frédéric Péters il y a plus de 2 ans

Pourquoi ne pas juste systématiquement poser "request.ignore_session = True" et enregistrer la session de manière explicite au seul moment où elle change, cette partie qui serait de l'ordre de :

@@ -1360,6 +1360,7 @@ class FormPage(Directory, FormTemplateMixin):

     def save_draft(self, data, page_no=None):
         filled = self.get_current_draft() or self.formdef.data_class()()
+        new_draft = bool(filled.id is None)
         if filled.id and filled.status != 'draft':
             raise SubmittedDraftException()
         filled.data = data
@@ -1380,7 +1381,10 @@ class FormPage(Directory, FormTemplateMixin):
             if not filled.user_id:
                 get_session().mark_anonymous_formdata(filled)

-        data['draft_formdata_id'] = filled.id
+        if new_draft:
+            data['draft_formdata_id'] = filled.id
+            get_session().store()
+

?

#19

Mis à jour par Frédéric Péters il y a plus de 2 ans

Il y a un autre moment à gérer, quand il est fait get_session().mark_anonymous_formdata(filled) il faut enregistrer la session. (j'ai poussé une nouvelle branche qui ajoute ça).

#20

Mis à jour par Benjamin Dauvergne il y a plus de 2 ans

  • Statut changé de Solution proposée à En cours
#23

Mis à jour par Frédéric Péters il y a plus de 2 ans

  • Statut changé de Solution proposée à Résolu (à déployer)

Validé/poussé,

commit a616254174960353e3da8afb3ae6f9d50edd0253
Author: Frédéric Péters <fpeters@entrouvert.com>
Date:   Fri Nov 5 16:31:20 2021 +0100

    forms: prevent autosave from overwriting session's data (#58208)
#24

Mis à jour par Frédéric Péters il y a plus de 2 ans

  • Statut changé de Résolu (à déployer) à Solution déployée
#26

Mis à jour par Transition automatique il y a environ 2 ans

Automatic expiration

Formats disponibles : Atom PDF