Projet

Général

Profil

0001-misc-add-fields-verification-after-tracking-code-590.patch

Thomas Noël, 24 février 2022 00:53

Télécharger (15,7 ko)

Voir les différences:

Subject: [PATCH] misc: add fields verification after tracking code (#59027)

 help/fr/api-schema.page        |   9 +--
 tests/admin_pages/test_form.py |  14 +++++
 tests/api/test_formdef.py      |   6 ++
 tests/form_pages/test_all.py   | 106 +++++++++++++++++++++++++++++++++
 tests/test_formdef_import.py   |  18 ++++++
 wcs/admin/forms.py             |  16 +++++
 wcs/formdef.py                 |  16 ++++-
 wcs/forms/root.py              |  39 +++++++++++-
 8 files changed, 217 insertions(+), 7 deletions(-)
help/fr/api-schema.page
178 178
<code mime="application/json">
179 179
{
180 180
    "name": "Newsletter",
181
    "only_allow_one": "false",
182
    "enable_tracking_codes": "true",
183
    "confirmation": "true",
184
    "discussion": "false",
181
    "only_allow_one": false,
182
    "enable_tracking_codes": true,
183
    "tracking_code_verify_fields": ["1"],
184
    "confirmation": true,
185
    "discussion": false,
185 186
    "fields": [
186 187
        {
187 188
            "label": "Nom",
tests/admin_pages/test_form.py
245 245
    resp = resp.forms[0].submit().follow()
246 246
    assert FormDef.get(1).drafts_lifespan == '5'
247 247

  
248
    formdef.fields = [
249
        fields.StringField(id='1', label='VerifyString', type='string'),
250
        fields.DateField(id='2', label='VerifyDate', type='date'),
251
        fields.ItemField(id='3', label='CannotVerify', type='item'),
252
    ]
253
    formdef.store()
254
    resp = resp.click('Tracking Code')
255
    assert '<option value="1">VerifyString</option>' in resp
256
    assert '<option value="2">VerifyDate</option>' in resp
257
    assert 'CannotVerify' not in resp
258
    resp.forms[0]['tracking_code_verify_fields$element0'].value = '1'
259
    resp = resp.forms[0].submit().follow()
260
    assert FormDef.get(1).tracking_code_verify_fields == ['1']
261

  
248 262

  
249 263
def test_forms_edit_captcha(pub, formdef):
250 264
    create_superuser(pub)
tests/api/test_formdef.py
452 452

  
453 453
    formdef.category_id = cat.id
454 454
    formdef.workflow_id = workflow.id
455
    formdef.enable_tracking_codes = True
456
    formdef.tracking_code_verify_fields = ['0']
455 457
    formdef.store()
456 458

  
457 459
    with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
......
488 490
    # check schema
489 491
    assert set(resp.json.keys()) >= {
490 492
        'enable_tracking_codes',
493
        'tracking_code_verify_fields',
491 494
        'url_name',
492 495
        'description',
493 496
        'workflow',
......
506 509
    }
507 510
    assert resp.json['name'] == 'test'
508 511

  
512
    assert resp.json['enable_tracking_codes'] is True
513
    assert resp.json['tracking_code_verify_fields'] == ['0']
514

  
509 515
    # fields checks
510 516
    assert resp.json['fields'][0]['label'] == 'foobar'
511 517
    assert resp.json['fields'][0]['type'] == 'string'
tests/form_pages/test_all.py
1767 1767
    resp = resp.follow()
1768 1768

  
1769 1769

  
1770
def test_form_tracking_code_verification(pub, nocache):
1771
    formdef = create_formdef()
1772
    formdef.fields = [
1773
        fields.StringField(id='0', label='string1', required=False),
1774
        fields.StringField(id='1', label='string2', required=False),
1775
    ]
1776
    formdef.enable_tracking_codes = True
1777
    formdef.tracking_code_verify_fields = ['0', '1']
1778
    formdef.store()
1779

  
1780
    resp = get_app(pub).get('/test/')
1781
    formdef.data_class().wipe()
1782
    assert '<h3>Tracking code</h3>' in resp.text
1783
    resp.forms[0]['f0'] = 'foobar1'
1784
    resp.forms[0]['f1'] = 'foobar2'
1785
    resp = resp.forms[0].submit('submit')
1786
    tracking_code = get_displayed_tracking_code(resp)
1787
    assert tracking_code is not None
1788

  
1789
    assert formdef.data_class().count() == 1
1790
    assert formdef.data_class().select()[0].is_draft()
1791
    assert formdef.data_class().select()[0].tracking_code == tracking_code
1792
    assert formdef.data_class().select()[0].data['0'] == 'foobar1'
1793
    assert formdef.data_class().select()[0].data['1'] == 'foobar2'
1794
    formdata = formdef.data_class().select()[0]
1795
    formdata_id = formdata.id
1796

  
1797
    resp = get_app(pub).get('/')
1798
    resp.forms[0]['code'] = tracking_code
1799
    resp = resp.forms[0].submit()
1800
    assert resp.location == 'http://example.net/code/%s/load' % tracking_code
1801
    resp = resp.follow()
1802
    assert 'Access rights verification' in resp
1803
    resp.forms[0]['f0'] = 'foobar1'
1804
    resp.forms[0]['f1'] = 'foobar2'
1805
    resp = resp.forms[0].submit('submit')
1806
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1807
    resp = resp.follow()
1808
    assert resp.location.startswith('http://example.net/test/?mt=')
1809
    resp = resp.follow()
1810

  
1811
    # check anonymous user can't get to it from the URL
1812
    pub.session_manager.session_class.wipe()
1813
    resp = get_app(pub).get('http://example.net/test/%s/' % formdata_id)
1814
    assert resp.location.startswith('http://example.net/login')
1815
    # or logged users that didn't enter the code:
1816
    create_user(pub)
1817
    login(get_app(pub), username='foo', password='foo').get(
1818
        'http://example.net/test/%s/' % formdata_id, status=403
1819
    )
1820

  
1821
    # verification failure
1822
    resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
1823
    assert 'Access rights verification' in resp
1824
    resp.forms[0]['f0'] = 'foobar1'  # ok
1825
    resp.forms[0]['f1'] = 'barfoo2'  # ko
1826
    resp = resp.forms[0].submit('submit', status=403)
1827
    assert 'Access rights verification failed' in resp
1828

  
1829
    # draft with an empty field: do not verify it
1830
    formdata.data['0'] = None
1831
    formdata.store()
1832
    resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
1833
    assert 'Access rights verification' in resp
1834
    assert 'f0' not in resp.forms[0].fields
1835
    assert 'f1' in resp.forms[0].fields
1836
    resp.forms[0]['f1'] = 'foobar2'
1837
    resp = resp.forms[0].submit('submit')
1838
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1839
    resp = resp.follow()
1840
    assert resp.location.startswith('http://example.net/test/?mt=')
1841
    resp = resp.follow()
1842

  
1843
    # empty draft: no verification
1844
    formdata.data['1'] = None
1845
    formdata.store()
1846
    resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
1847
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1848
    resp = resp.follow()
1849
    assert resp.location.startswith('http://example.net/test/?mt=')
1850

  
1851
    # not a draft: all validation fields are required
1852
    formdata.status = 'wf-new'
1853
    formdata.data['0'] = 'foobar1'
1854
    formdata.store()
1855
    resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
1856
    assert 'Access rights verification' in resp
1857
    assert 'f0' in resp.forms[0].fields
1858
    assert 'f1' in resp.forms[0].fields
1859
    resp.forms[0]['f0'] = 'foobar1'
1860
    resp.forms[0]['f1'] = ''
1861
    resp = resp.forms[0].submit('submit')
1862
    assert resp.location == 'http://example.net/test/%s/' % formdata_id
1863
    resp = resp.follow()
1864
    assert 'foobar1' in resp.text
1865
    assert 'form_comment' in resp.text  # user is treated as submitter
1866

  
1867
    # verification failure
1868
    resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
1869
    assert 'Access rights verification' in resp
1870
    resp.forms[0]['f0'] = 'foobar1'  # ok
1871
    resp.forms[0]['f1'] = 'not empty'  # ko
1872
    resp = resp.forms[0].submit('submit', status=403)
1873
    assert 'Access rights verification failed' in resp
1874

  
1875

  
1770 1876
def test_form_tracking_code_rate_limit(pub, freezer):
1771 1877
    pub.load_site_options()
1772 1878
    if not pub.site_options.has_section('options'):
tests/test_formdef_import.py
867 867
        'Unknown field types: foobaz; '
868 868
        'Unknown fields blocks: foobar, foobaz'
869 869
    )
870

  
871

  
872
def test_tracking_code_attributes(pub):
873
    formdef = FormDef()
874
    formdef.name = 'Foo'
875
    formdef.url_name = 'foo'
876
    formdef.confirmation = True
877
    formdef.enable_tracking_codes = True
878
    for verify_fields in (['1', '2'], [], None):
879
        formdef.tracking_code_verify_fields = verify_fields
880
        f2 = assert_xml_import_export_works(formdef)
881
        assert f2.enable_tracking_codes == formdef.enable_tracking_codes
882
        assert f2.tracking_code_verify_fields == formdef.tracking_code_verify_fields
883
        assert f2.confirmation == formdef.confirmation
884
        f2 = assert_json_import_export_works(formdef)
885
        assert f2.enable_tracking_codes == formdef.enable_tracking_codes
886
        assert f2.tracking_code_verify_fields == formdef.tracking_code_verify_fields
887
        assert f2.confirmation == formdef.confirmation
wcs/admin/forms.py
266 266
            title=_('Enable support for tracking codes'),
267 267
            value=self.formdef.enable_tracking_codes,
268 268
        )
269
        verify_fields = [(None, '---', None)]
270
        for field in self.formdef.fields:
271
            if field.type in ('string', 'date', 'email'):
272
                verify_fields.append((field.id, field.label, field.id))
273
        form.add(
274
            WidgetList,
275
            'tracking_code_verify_fields',
276
            title=_('Fields to check after entering the tracking code'),
277
            element_type=SingleSelectWidget,
278
            value=self.formdef.tracking_code_verify_fields,
279
            add_element_label=_('Add verification Field'),
280
            element_kwargs={'render_br': False, 'options': verify_fields},
281
            hint=_('Only text, date and email fields can be used'),
282
        )
283

  
269 284
        widget = form.add(
270 285
            WcsExtraStringWidget,
271 286
            'drafts_lifespan',
......
442 457
                'only_allow_one',
443 458
                'disabled',
444 459
                'enable_tracking_codes',
460
                'tracking_code_verify_fields',
445 461
                'always_advertise',
446 462
                'disabled_redirection',
447 463
                'publication_date',
wcs/formdef.py
147 147
    disabled = False
148 148
    only_allow_one = False
149 149
    enable_tracking_codes = False
150
    tracking_code_verify_fields = None
150 151
    disabled_redirection = None
151 152
    always_advertise = False
152 153
    publication_date = None
......
1008 1009
        if self.max_field_id is None and self.fields:
1009 1010
            self.max_field_id = max(lax_int(x.id) for x in self.fields)
1010 1011

  
1011
        more_attributes = []
1012
        more_attributes = ['tracking_code_verify_fields']
1012 1013
        if self.max_field_id:
1013 1014
            more_attributes.append('max_field_id')
1014 1015

  
......
1124 1125
                    formdef.workflow_id = w.id
1125 1126
                    break
1126 1127

  
1127
        more_attributes = ['max_field_id']
1128
        more_attributes = ['max_field_id', 'tracking_code_verify_fields']
1128 1129
        for attribute in cls.TEXT_ATTRIBUTES + cls.BOOLEAN_ATTRIBUTES + more_attributes:
1129 1130
            if attribute in value:
1130 1131
                setattr(formdef, attribute, value.get(attribute))
......
1205 1206
        if self.max_field_id:
1206 1207
            ET.SubElement(root, 'max_field_id').text = str(self.max_field_id)
1207 1208

  
1209
        if self.tracking_code_verify_fields is not None:
1210
            verify_fields = ET.SubElement(root, 'tracking_code_verify_fields')
1211
            for field_id in self.tracking_code_verify_fields:
1212
                ET.SubElement(verify_fields, 'field_id').text = str(field_id)
1213

  
1208 1214
        fields = ET.SubElement(root, 'fields')
1209 1215
        for field in self.fields or []:
1210 1216
            fields.append(field.export_to_xml(charset=charset, include_id=include_id))
......
1393 1399
            else:
1394 1400
                formdef.max_field_id = max(lax_int(x.id) for x in formdef.fields)
1395 1401

  
1402
        if tree.find('tracking_code_verify_fields') is not None:
1403
            formdef.tracking_code_verify_fields = [
1404
                xml_node_text(verify_field_id)
1405
                for verify_field_id in tree.findall('tracking_code_verify_fields/field_id')
1406
            ]
1407

  
1396 1408
        formdef.workflow_options = {}
1397 1409
        for option in tree.findall('options/option'):
1398 1410
            option_value = None
wcs/forms/root.py
45 45
from wcs.variables import LazyFormDef
46 46
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowStatusItem
47 47

  
48
from ..qommon import _, emails, errors, get_cfg, misc, template
48
from ..qommon import _, emails, errors, get_cfg, misc, ngettext, template
49 49
from ..qommon.admin.emails import EmailsDirectory
50 50
from ..qommon.form import CheckboxWidget, EmailWidget, Form, HiddenErrorWidget, HtmlWidget, StringWidget
51 51
from ..qommon.template import TemplateError
......
181 181
            raise errors.TraversalError()
182 182
        if get_request().is_from_bot():
183 183
            raise errors.AccessForbiddenError()
184

  
185
        verify_fields = []
186
        for field in formdata.formdef.fields:
187
            if field.id in (formdata.formdef.tracking_code_verify_fields or []):
188
                if formdata.status == 'draft' and not formdata.data.get(field.id):
189
                    # a draft could be incomplete: do not test its empty values
190
                    continue
191
                verify_fields.append(field)
192
        if verify_fields:
193
            form = Form()
194
            for field in verify_fields:
195
                widget = field.add_to_form(form)
196
                widget.field = field
197
            form.add_submit('submit', _('Verify'))
198
            form.add_submit('cancel', _('Cancel'))
199

  
200
            if form.get_submit() == 'cancel':
201
                return redirect('/')
202

  
203
            if form.is_submitted() and not form.has_errors():
204
                for field in verify_fields:
205
                    value = formdata.data.get(field.id)
206
                    verify_value = form.get_widget('f%s' % field.id).parse()
207
                    if value != verify_value:
208
                        raise errors.AccessForbiddenError(_('Access rights verification failed'))
209
            else:
210
                html_top()
211
                r = TemplateIO(html=True)
212
                r += htmltext('<h2>%s</h2>') % _('Access rights verification')
213
                r += htmltext('<p>%s</p>') % ngettext(
214
                    'In order to be able to access the form, indicate the content of the following field.',
215
                    'In order to be able to access the form, indicate the content of the following fields.',
216
                    len(verify_fields),
217
                )
218
                r += form.render()
219
                return r.getvalue()
220

  
184 221
        get_session().mark_anonymous_formdata(formdata)
185 222
        return redirect(formdata.get_url())
186 223

  
187
-