From 3a2a3d541d0de5c1b8b7b35163c0a2c0c3aedc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Fri, 5 Nov 2021 16:31:20 +0100 Subject: [PATCH] forms: prevent autosave from overwriting session's data (#58208) --- tests/form_pages/test_all.py | 47 ++++++++++++++++++++++++++++++++++++ wcs/forms/root.py | 10 +++++--- wcs/sessions.py | 6 ++++- 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/tests/form_pages/test_all.py b/tests/form_pages/test_all.py index f0dfddb3..c192a94a 100644 --- a/tests/form_pages/test_all.py +++ b/tests/form_pages/test_all.py @@ -4376,6 +4376,53 @@ def test_form_autosave_with_parameterized_datasource(pub): assert formdef.data_class().select()[0].data['3_display'] == 'barbar' +def test_form_autosave_never_overwrite(mocker, pub, settings): + create_user(pub) + + formdef = create_formdef() + formdef.data_class().wipe() + + formdef.fields = [ + fields.PageField(id='0', label='1st page', type='page'), + fields.StringField(id='1', label='string1'), + fields.PageField(id='2', label='2nd page', type='page'), + fields.StringField(id='3', label='string2'), + ] + formdef.store() + + app = get_app(pub) + login(app, username='foo', password='foo') + + resp = app.get('/test/') + resp.form.set('f1', '1') + # go to the second page + resp = resp.form.submit('submit') + resp.form.set('f3', '1') + # autosave wrong data + autosave_data = dict(resp.form.submit_fields()) + autosave_data['f3'] = 'wtf!' + resp_autosave = app.post('/test/autosave', params=autosave_data) + assert resp_autosave.json == {'result': 'success'} + # check the draft is fucked + data = formdef.data_class().select()[0].data + assert data['3'] == 'wtf!' + # now finish submitting + resp = resp.form.submit('submit') # -> validation page + # autosave wrong data + # _ajax_form_token is just a form_token, so take the current one to + # simulate a rogue autosave from the second page + autosave_data['_ajax_form_token'] = resp.form['_form_id'].value + resp_autosave = app.post('/test/autosave', params=autosave_data) + assert resp_autosave.json == {'result': 'success'} + data = formdef.data_class().select()[0].data + assert data['3'] == 'wtf!' + # validate + resp = resp.form.submit('submit') # -> submit + + # great everything is still fine in the end + assert formdef.data_class().select()[0].data == {'1': '1', '3': '1'} + + def test_form_string_field_autocomplete(pub): formdef = create_formdef() formdef.fields = [fields.StringField(id='0', label='string', type='string', required=False)] diff --git a/wcs/forms/root.py b/wcs/forms/root.py index 28449a96..2275b187 100644 --- a/wcs/forms/root.py +++ b/wcs/forms/root.py @@ -1299,9 +1299,9 @@ class FormPage(Directory, FormTemplateMixin): def autosave(self): get_response().set_content_type('application/json') + get_request().ignore_session = True def result_error(reason): - get_request().ignore_session = True return json.dumps({'result': 'error', 'reason': reason}) ajax_form_token = get_request().form.get('_ajax_form_token') @@ -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 @@ -1378,9 +1379,12 @@ class FormPage(Directory, FormTemplateMixin): filled.store() if not filled.user_id: - get_session().mark_anonymous_formdata(filled) + if get_session().mark_anonymous_formdata(filled): + get_session().store() - data['draft_formdata_id'] = filled.id + if new_draft: + data['draft_formdata_id'] = filled.id + get_session().store() self.set_tracking_code(filled, data) get_logger().info('form %s - saving draft (id: %s)' % (self.formdef.name, filled.id)) diff --git a/wcs/sessions.py b/wcs/sessions.py index 0d8c6970..563ac5aa 100644 --- a/wcs/sessions.py +++ b/wcs/sessions.py @@ -59,7 +59,11 @@ class BasicSession(Session): def mark_anonymous_formdata(self, formdata): if not self.anonymous_formdata_keys: self.anonymous_formdata_keys = {} - self.anonymous_formdata_keys[formdata.get_object_key()] = True + key = formdata.get_object_key() + if key not in self.anonymous_formdata_keys: + self.anonymous_formdata_keys[key] = True + return True + return False def is_anonymous_submitter(self, formdata): if not self.anonymous_formdata_keys: -- 2.33.0