0001-misc-add-fields-verification-after-tracking-code-590.patch
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 | ||
---|---|---|
266 | 266 |
resp = resp.forms[0].submit().follow() |
267 | 267 |
assert FormDef.get(1).drafts_lifespan == '5' |
268 | 268 | |
269 |
formdef.fields = [ |
|
270 |
fields.StringField(id='1', label='VerifyString', type='string'), |
|
271 |
fields.DateField(id='2', label='VerifyDate', type='date'), |
|
272 |
fields.ItemField(id='3', label='CannotVerify', type='item'), |
|
273 |
] |
|
274 |
formdef.store() |
|
275 |
resp = resp.click('Tracking Code') |
|
276 |
assert '<option value="1">VerifyString</option>' in resp |
|
277 |
assert '<option value="2">VerifyDate</option>' in resp |
|
278 |
assert 'CannotVerify' not in resp |
|
279 |
resp.forms[0]['tracking_code_verify_fields$element0'].value = '1' |
|
280 |
resp = resp.forms[0].submit().follow() |
|
281 |
assert FormDef.get(1).tracking_code_verify_fields == ['1'] |
|
282 | ||
269 | 283 | |
270 | 284 |
def test_forms_edit_captcha(pub, formdef): |
271 | 285 |
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 | ||
---|---|---|
1773 | 1773 |
resp = resp.follow() |
1774 | 1774 | |
1775 | 1775 | |
1776 |
def test_form_tracking_code_verification(pub, nocache): |
|
1777 |
formdef = create_formdef() |
|
1778 |
formdef.fields = [ |
|
1779 |
fields.StringField(id='0', label='string1', required=False), |
|
1780 |
fields.StringField(id='1', label='string2', required=False), |
|
1781 |
] |
|
1782 |
formdef.enable_tracking_codes = True |
|
1783 |
formdef.tracking_code_verify_fields = ['0', '1'] |
|
1784 |
formdef.store() |
|
1785 | ||
1786 |
resp = get_app(pub).get('/test/') |
|
1787 |
formdef.data_class().wipe() |
|
1788 |
assert '<h3>Tracking code</h3>' in resp.text |
|
1789 |
resp.forms[0]['f0'] = 'foobar1' |
|
1790 |
resp.forms[0]['f1'] = 'foobar2' |
|
1791 |
resp = resp.forms[0].submit('submit') |
|
1792 |
tracking_code = get_displayed_tracking_code(resp) |
|
1793 |
assert tracking_code is not None |
|
1794 | ||
1795 |
assert formdef.data_class().count() == 1 |
|
1796 |
assert formdef.data_class().select()[0].is_draft() |
|
1797 |
assert formdef.data_class().select()[0].tracking_code == tracking_code |
|
1798 |
assert formdef.data_class().select()[0].data['0'] == 'foobar1' |
|
1799 |
assert formdef.data_class().select()[0].data['1'] == 'foobar2' |
|
1800 |
formdata = formdef.data_class().select()[0] |
|
1801 |
formdata_id = formdata.id |
|
1802 | ||
1803 |
resp = get_app(pub).get('/') |
|
1804 |
resp.forms[0]['code'] = tracking_code |
|
1805 |
resp = resp.forms[0].submit() |
|
1806 |
assert resp.location == 'http://example.net/code/%s/load' % tracking_code |
|
1807 |
resp = resp.follow() |
|
1808 |
assert 'Access rights verification' in resp |
|
1809 |
resp.forms[0]['f0'] = 'foobar1' |
|
1810 |
resp.forms[0]['f1'] = 'foobar2' |
|
1811 |
resp = resp.forms[0].submit('submit') |
|
1812 |
assert resp.location == 'http://example.net/test/%s/' % formdata_id |
|
1813 |
resp = resp.follow() |
|
1814 |
assert resp.location.startswith('http://example.net/test/?mt=') |
|
1815 |
resp = resp.follow() |
|
1816 | ||
1817 |
# check anonymous user can't get to it from the URL |
|
1818 |
pub.session_manager.session_class.wipe() |
|
1819 |
resp = get_app(pub).get('http://example.net/test/%s/' % formdata_id) |
|
1820 |
assert resp.location.startswith('http://example.net/login') |
|
1821 |
# or logged users that didn't enter the code: |
|
1822 |
create_user(pub) |
|
1823 |
login(get_app(pub), username='foo', password='foo').get( |
|
1824 |
'http://example.net/test/%s/' % formdata_id, status=403 |
|
1825 |
) |
|
1826 | ||
1827 |
# verification failure |
|
1828 |
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code) |
|
1829 |
assert 'Access rights verification' in resp |
|
1830 |
resp.forms[0]['f0'] = 'foobar1' # ok |
|
1831 |
resp.forms[0]['f1'] = 'barfoo2' # ko |
|
1832 |
resp = resp.forms[0].submit('submit') |
|
1833 |
assert 'Access denied: this content does match the form' in resp |
|
1834 | ||
1835 |
# draft with an empty field: do not verify it |
|
1836 |
formdata.data['0'] = None |
|
1837 |
formdata.store() |
|
1838 |
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code) |
|
1839 |
assert 'Access rights verification' in resp |
|
1840 |
assert 'f0' not in resp.forms[0].fields |
|
1841 |
assert 'f1' in resp.forms[0].fields |
|
1842 |
resp.forms[0]['f1'] = 'foobar2' |
|
1843 |
resp = resp.forms[0].submit('submit') |
|
1844 |
assert resp.location == 'http://example.net/test/%s/' % formdata_id |
|
1845 |
resp = resp.follow() |
|
1846 |
assert resp.location.startswith('http://example.net/test/?mt=') |
|
1847 |
resp = resp.follow() |
|
1848 | ||
1849 |
# empty draft: no verification |
|
1850 |
formdata.data['1'] = None |
|
1851 |
formdata.store() |
|
1852 |
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code) |
|
1853 |
assert resp.location == 'http://example.net/test/%s/' % formdata_id |
|
1854 |
resp = resp.follow() |
|
1855 |
assert resp.location.startswith('http://example.net/test/?mt=') |
|
1856 | ||
1857 |
# not a draft: all validation fields are required |
|
1858 |
formdata.status = 'wf-new' |
|
1859 |
formdata.data['0'] = 'foobar1' |
|
1860 |
formdata.store() |
|
1861 |
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code) |
|
1862 |
assert 'Access rights verification' in resp |
|
1863 |
assert 'f0' in resp.forms[0].fields |
|
1864 |
assert 'f1' in resp.forms[0].fields |
|
1865 |
resp.forms[0]['f0'] = 'foobar1' |
|
1866 |
resp.forms[0]['f1'] = '' |
|
1867 |
resp = resp.forms[0].submit('submit') |
|
1868 |
assert resp.location == 'http://example.net/test/%s/' % formdata_id |
|
1869 |
resp = resp.follow() |
|
1870 |
assert 'foobar1' in resp.text |
|
1871 |
assert 'form_comment' in resp.text # user is treated as submitter |
|
1872 | ||
1873 |
# verification failure |
|
1874 |
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code) |
|
1875 |
assert 'Access rights verification' in resp |
|
1876 |
resp.forms[0]['f0'] = 'foobar1' # ok |
|
1877 |
resp.forms[0]['f1'] = 'not empty' # ko |
|
1878 |
resp = resp.forms[0].submit('submit') |
|
1879 |
assert 'Access denied: this content does match the form.' in resp |
|
1880 | ||
1881 | ||
1776 | 1882 |
def test_form_tracking_code_rate_limit(pub, freezer): |
1777 | 1883 |
pub.load_site_options() |
1778 | 1884 |
if not pub.site_options.has_section('options'): |
tests/test_formdef_import.py | ||
---|---|---|
884 | 884 |
excinfo.value.msg |
885 | 885 |
== 'Provided XML file is invalid, it starts with a <wrong_root_node> tag instead of <carddef>' |
886 | 886 |
) |
887 | ||
888 | ||
889 |
def test_tracking_code_attributes(pub): |
|
890 |
formdef = FormDef() |
|
891 |
formdef.name = 'Foo' |
|
892 |
formdef.url_name = 'foo' |
|
893 |
formdef.confirmation = True |
|
894 |
formdef.enable_tracking_codes = True |
|
895 |
for verify_fields in (['1', '2'], [], None): |
|
896 |
formdef.tracking_code_verify_fields = verify_fields |
|
897 |
f2 = assert_xml_import_export_works(formdef) |
|
898 |
assert f2.enable_tracking_codes == formdef.enable_tracking_codes |
|
899 |
assert f2.tracking_code_verify_fields == formdef.tracking_code_verify_fields |
|
900 |
assert f2.confirmation == formdef.confirmation |
|
901 |
f2 = assert_json_import_export_works(formdef) |
|
902 |
assert f2.enable_tracking_codes == formdef.enable_tracking_codes |
|
903 |
assert f2.tracking_code_verify_fields == formdef.tracking_code_verify_fields |
|
904 |
assert f2.confirmation == formdef.confirmation |
wcs/admin/forms.py | ||
---|---|---|
298 | 298 |
title=_('Enable support for tracking codes'), |
299 | 299 |
value=self.formdef.enable_tracking_codes, |
300 | 300 |
) |
301 |
verify_fields = [(None, '---', None)] |
|
302 |
for field in self.formdef.fields: |
|
303 |
if field.type in ('string', 'date', 'email'): |
|
304 |
verify_fields.append((field.id, field.label, field.id)) |
|
305 |
form.add( |
|
306 |
WidgetList, |
|
307 |
'tracking_code_verify_fields', |
|
308 |
title=_('Fields to check after entering the tracking code'), |
|
309 |
element_type=SingleSelectWidget, |
|
310 |
value=self.formdef.tracking_code_verify_fields, |
|
311 |
add_element_label=_('Add verification Field'), |
|
312 |
element_kwargs={'render_br': False, 'options': verify_fields}, |
|
313 |
hint=_('Only text, date and email fields can be used'), |
|
314 |
) |
|
315 | ||
301 | 316 |
widget = form.add( |
302 | 317 |
WcsExtraStringWidget, |
303 | 318 |
'drafts_lifespan', |
... | ... | |
474 | 489 |
'only_allow_one', |
475 | 490 |
'disabled', |
476 | 491 |
'enable_tracking_codes', |
492 |
'tracking_code_verify_fields', |
|
477 | 493 |
'always_advertise', |
478 | 494 |
'disabled_redirection', |
479 | 495 |
'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 |
... | ... | |
1012 | 1013 |
if self.max_field_id is None and self.fields: |
1013 | 1014 |
self.max_field_id = max(lax_int(x.id) for x in self.fields) |
1014 | 1015 | |
1015 |
more_attributes = [] |
|
1016 |
more_attributes = ['tracking_code_verify_fields']
|
|
1016 | 1017 |
if self.max_field_id: |
1017 | 1018 |
more_attributes.append('max_field_id') |
1018 | 1019 | |
... | ... | |
1128 | 1129 |
formdef.workflow_id = w.id |
1129 | 1130 |
break |
1130 | 1131 | |
1131 |
more_attributes = ['max_field_id'] |
|
1132 |
more_attributes = ['max_field_id', 'tracking_code_verify_fields']
|
|
1132 | 1133 |
for attribute in cls.TEXT_ATTRIBUTES + cls.BOOLEAN_ATTRIBUTES + more_attributes: |
1133 | 1134 |
if attribute in value: |
1134 | 1135 |
setattr(formdef, attribute, value.get(attribute)) |
... | ... | |
1209 | 1210 |
if self.max_field_id: |
1210 | 1211 |
ET.SubElement(root, 'max_field_id').text = str(self.max_field_id) |
1211 | 1212 | |
1213 |
if self.tracking_code_verify_fields is not None: |
|
1214 |
verify_fields = ET.SubElement(root, 'tracking_code_verify_fields') |
|
1215 |
for field_id in self.tracking_code_verify_fields: |
|
1216 |
ET.SubElement(verify_fields, 'field_id').text = str(field_id) |
|
1217 | ||
1212 | 1218 |
fields = ET.SubElement(root, 'fields') |
1213 | 1219 |
for field in self.fields or []: |
1214 | 1220 |
fields.append(field.export_to_xml(charset=charset, include_id=include_id)) |
... | ... | |
1400 | 1406 |
else: |
1401 | 1407 |
formdef.max_field_id = max(lax_int(x.id) for x in formdef.fields) |
1402 | 1408 | |
1409 |
if tree.find('tracking_code_verify_fields') is not None: |
|
1410 |
formdef.tracking_code_verify_fields = [ |
|
1411 |
xml_node_text(verify_field_id) |
|
1412 |
for verify_field_id in tree.findall('tracking_code_verify_fields/field_id') |
|
1413 |
] |
|
1414 | ||
1403 | 1415 |
formdef.workflow_options = {} |
1404 | 1416 |
for option in tree.findall('options/option'): |
1405 | 1417 |
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 |
bad_content = False |
|
204 |
if form.is_submitted() and not form.has_errors(): |
|
205 |
for field in verify_fields: |
|
206 |
value = formdata.data.get(field.id) |
|
207 |
verify_value = form.get_widget('f%s' % field.id).parse() |
|
208 |
if value != verify_value: |
|
209 |
# global error: we do not specify which field is in error, for security |
|
210 |
form.add_global_errors([_('Access denied: this content does match the form.')]) |
|
211 |
bad_content = True |
|
212 |
break |
|
213 | ||
214 |
if not form.is_submitted() or form.has_errors() or bad_content: |
|
215 |
html_top() |
|
216 |
r = TemplateIO(html=True) |
|
217 |
r += htmltext('<h2>%s</h2>') % _('Access rights verification') |
|
218 |
r += htmltext('<p>%s</p>') % ngettext( |
|
219 |
'In order to be able to access the form, indicate the content of the following field.', |
|
220 |
'In order to be able to access the form, indicate the content of the following fields.', |
|
221 |
len(verify_fields), |
|
222 |
) |
|
223 |
r += form.render() |
|
224 |
return r.getvalue() |
|
225 | ||
184 | 226 |
get_session().mark_anonymous_formdata(formdata) |
185 | 227 |
return redirect(formdata.get_url()) |
186 | 228 | |
187 |
- |