misc: add fields verification after tracking code (#59027)
This commit is contained in:
parent
b7a5464822
commit
fc8e3458a9
|
@ -178,10 +178,11 @@ formulaire.
|
|||
<code mime="application/json">
|
||||
{
|
||||
"name": "Newsletter",
|
||||
"only_allow_one": "false",
|
||||
"enable_tracking_codes": "true",
|
||||
"confirmation": "true",
|
||||
"discussion": "false",
|
||||
"only_allow_one": false,
|
||||
"enable_tracking_codes": true,
|
||||
"tracking_code_verify_fields": ["1"],
|
||||
"confirmation": true,
|
||||
"discussion": false,
|
||||
"fields": [
|
||||
{
|
||||
"label": "Nom",
|
||||
|
|
|
@ -266,6 +266,20 @@ def test_forms_edit_tracking_code(pub, formdef):
|
|||
resp = resp.forms[0].submit().follow()
|
||||
assert FormDef.get(1).drafts_lifespan == '5'
|
||||
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='VerifyString', type='string'),
|
||||
fields.DateField(id='2', label='VerifyDate', type='date'),
|
||||
fields.ItemField(id='3', label='CannotVerify', type='item'),
|
||||
]
|
||||
formdef.store()
|
||||
resp = resp.click('Tracking Code')
|
||||
assert '<option value="1">VerifyString</option>' in resp
|
||||
assert '<option value="2">VerifyDate</option>' in resp
|
||||
assert 'CannotVerify' not in resp
|
||||
resp.forms[0]['tracking_code_verify_fields$element0'].value = '1'
|
||||
resp = resp.forms[0].submit().follow()
|
||||
assert FormDef.get(1).tracking_code_verify_fields == ['1']
|
||||
|
||||
|
||||
def test_forms_edit_captcha(pub, formdef):
|
||||
create_superuser(pub)
|
||||
|
|
|
@ -452,6 +452,8 @@ def test_formdef_schema(pub, access):
|
|||
|
||||
formdef.category_id = cat.id
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.tracking_code_verify_fields = ['0']
|
||||
formdef.store()
|
||||
|
||||
with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
|
||||
|
@ -488,6 +490,7 @@ def test_formdef_schema(pub, access):
|
|||
# check schema
|
||||
assert set(resp.json.keys()) >= {
|
||||
'enable_tracking_codes',
|
||||
'tracking_code_verify_fields',
|
||||
'url_name',
|
||||
'description',
|
||||
'workflow',
|
||||
|
@ -506,6 +509,9 @@ def test_formdef_schema(pub, access):
|
|||
}
|
||||
assert resp.json['name'] == 'test'
|
||||
|
||||
assert resp.json['enable_tracking_codes'] is True
|
||||
assert resp.json['tracking_code_verify_fields'] == ['0']
|
||||
|
||||
# fields checks
|
||||
assert resp.json['fields'][0]['label'] == 'foobar'
|
||||
assert resp.json['fields'][0]['type'] == 'string'
|
||||
|
|
|
@ -1773,6 +1773,112 @@ def test_form_tracking_code(pub, nocache):
|
|||
resp = resp.follow()
|
||||
|
||||
|
||||
def test_form_tracking_code_verification(pub, nocache):
|
||||
formdef = create_formdef()
|
||||
formdef.fields = [
|
||||
fields.StringField(id='0', label='string1', required=False),
|
||||
fields.StringField(id='1', label='string2', required=False),
|
||||
]
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.tracking_code_verify_fields = ['0', '1']
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
formdef.data_class().wipe()
|
||||
assert '<h3>Tracking code</h3>' in resp.text
|
||||
resp.forms[0]['f0'] = 'foobar1'
|
||||
resp.forms[0]['f1'] = 'foobar2'
|
||||
resp = resp.forms[0].submit('submit')
|
||||
tracking_code = get_displayed_tracking_code(resp)
|
||||
assert tracking_code is not None
|
||||
|
||||
assert formdef.data_class().count() == 1
|
||||
assert formdef.data_class().select()[0].is_draft()
|
||||
assert formdef.data_class().select()[0].tracking_code == tracking_code
|
||||
assert formdef.data_class().select()[0].data['0'] == 'foobar1'
|
||||
assert formdef.data_class().select()[0].data['1'] == 'foobar2'
|
||||
formdata = formdef.data_class().select()[0]
|
||||
formdata_id = formdata.id
|
||||
|
||||
resp = get_app(pub).get('/')
|
||||
resp.forms[0]['code'] = tracking_code
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
|
||||
resp = resp.follow()
|
||||
assert 'Access rights verification' in resp
|
||||
resp.forms[0]['f0'] = 'foobar1'
|
||||
resp.forms[0]['f1'] = 'foobar2'
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert resp.location == 'http://example.net/test/%s/' % formdata_id
|
||||
resp = resp.follow()
|
||||
assert resp.location.startswith('http://example.net/test/?mt=')
|
||||
resp = resp.follow()
|
||||
|
||||
# check anonymous user can't get to it from the URL
|
||||
pub.session_manager.session_class.wipe()
|
||||
resp = get_app(pub).get('http://example.net/test/%s/' % formdata_id)
|
||||
assert resp.location.startswith('http://example.net/login')
|
||||
# or logged users that didn't enter the code:
|
||||
create_user(pub)
|
||||
login(get_app(pub), username='foo', password='foo').get(
|
||||
'http://example.net/test/%s/' % formdata_id, status=403
|
||||
)
|
||||
|
||||
# verification failure
|
||||
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
|
||||
assert 'Access rights verification' in resp
|
||||
resp.forms[0]['f0'] = 'foobar1' # ok
|
||||
resp.forms[0]['f1'] = 'barfoo2' # ko
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert 'Access denied: this content does match the form' in resp
|
||||
|
||||
# draft with an empty field: do not verify it
|
||||
formdata.data['0'] = None
|
||||
formdata.store()
|
||||
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
|
||||
assert 'Access rights verification' in resp
|
||||
assert 'f0' not in resp.forms[0].fields
|
||||
assert 'f1' in resp.forms[0].fields
|
||||
resp.forms[0]['f1'] = 'foobar2'
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert resp.location == 'http://example.net/test/%s/' % formdata_id
|
||||
resp = resp.follow()
|
||||
assert resp.location.startswith('http://example.net/test/?mt=')
|
||||
resp = resp.follow()
|
||||
|
||||
# empty draft: no verification
|
||||
formdata.data['1'] = None
|
||||
formdata.store()
|
||||
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
|
||||
assert resp.location == 'http://example.net/test/%s/' % formdata_id
|
||||
resp = resp.follow()
|
||||
assert resp.location.startswith('http://example.net/test/?mt=')
|
||||
|
||||
# not a draft: all validation fields are required
|
||||
formdata.status = 'wf-new'
|
||||
formdata.data['0'] = 'foobar1'
|
||||
formdata.store()
|
||||
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
|
||||
assert 'Access rights verification' in resp
|
||||
assert 'f0' in resp.forms[0].fields
|
||||
assert 'f1' in resp.forms[0].fields
|
||||
resp.forms[0]['f0'] = 'foobar1'
|
||||
resp.forms[0]['f1'] = ''
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert resp.location == 'http://example.net/test/%s/' % formdata_id
|
||||
resp = resp.follow()
|
||||
assert 'foobar1' in resp.text
|
||||
assert 'form_comment' in resp.text # user is treated as submitter
|
||||
|
||||
# verification failure
|
||||
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
|
||||
assert 'Access rights verification' in resp
|
||||
resp.forms[0]['f0'] = 'foobar1' # ok
|
||||
resp.forms[0]['f1'] = 'not empty' # ko
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert 'Access denied: this content does match the form.' in resp
|
||||
|
||||
|
||||
def test_form_tracking_code_rate_limit(pub, freezer):
|
||||
pub.load_site_options()
|
||||
if not pub.site_options.has_section('options'):
|
||||
|
|
|
@ -884,3 +884,21 @@ def test_import_formdef_root_node_error():
|
|||
excinfo.value.msg
|
||||
== 'Provided XML file is invalid, it starts with a <wrong_root_node> tag instead of <carddef>'
|
||||
)
|
||||
|
||||
|
||||
def test_tracking_code_attributes(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'Foo'
|
||||
formdef.url_name = 'foo'
|
||||
formdef.confirmation = True
|
||||
formdef.enable_tracking_codes = True
|
||||
for verify_fields in (['1', '2'], [], None):
|
||||
formdef.tracking_code_verify_fields = verify_fields
|
||||
f2 = assert_xml_import_export_works(formdef)
|
||||
assert f2.enable_tracking_codes == formdef.enable_tracking_codes
|
||||
assert f2.tracking_code_verify_fields == formdef.tracking_code_verify_fields
|
||||
assert f2.confirmation == formdef.confirmation
|
||||
f2 = assert_json_import_export_works(formdef)
|
||||
assert f2.enable_tracking_codes == formdef.enable_tracking_codes
|
||||
assert f2.tracking_code_verify_fields == formdef.tracking_code_verify_fields
|
||||
assert f2.confirmation == formdef.confirmation
|
||||
|
|
|
@ -298,6 +298,21 @@ class OptionsDirectory(Directory):
|
|||
title=_('Enable support for tracking codes'),
|
||||
value=self.formdef.enable_tracking_codes,
|
||||
)
|
||||
verify_fields = [(None, '---', None)]
|
||||
for field in self.formdef.fields:
|
||||
if field.type in ('string', 'date', 'email'):
|
||||
verify_fields.append((field.id, field.label, field.id))
|
||||
form.add(
|
||||
WidgetList,
|
||||
'tracking_code_verify_fields',
|
||||
title=_('Fields to check after entering the tracking code'),
|
||||
element_type=SingleSelectWidget,
|
||||
value=self.formdef.tracking_code_verify_fields,
|
||||
add_element_label=_('Add verification Field'),
|
||||
element_kwargs={'render_br': False, 'options': verify_fields},
|
||||
hint=_('Only text, date and email fields can be used'),
|
||||
)
|
||||
|
||||
widget = form.add(
|
||||
WcsExtraStringWidget,
|
||||
'drafts_lifespan',
|
||||
|
@ -474,6 +489,7 @@ class OptionsDirectory(Directory):
|
|||
'only_allow_one',
|
||||
'disabled',
|
||||
'enable_tracking_codes',
|
||||
'tracking_code_verify_fields',
|
||||
'always_advertise',
|
||||
'disabled_redirection',
|
||||
'publication_date',
|
||||
|
|
|
@ -147,6 +147,7 @@ class FormDef(StorableObject):
|
|||
disabled = False
|
||||
only_allow_one = False
|
||||
enable_tracking_codes = False
|
||||
tracking_code_verify_fields = None
|
||||
disabled_redirection = None
|
||||
always_advertise = False
|
||||
publication_date = None
|
||||
|
@ -1012,7 +1013,7 @@ class FormDef(StorableObject):
|
|||
if self.max_field_id is None and self.fields:
|
||||
self.max_field_id = max(lax_int(x.id) for x in self.fields)
|
||||
|
||||
more_attributes = []
|
||||
more_attributes = ['tracking_code_verify_fields']
|
||||
if self.max_field_id:
|
||||
more_attributes.append('max_field_id')
|
||||
|
||||
|
@ -1128,7 +1129,7 @@ class FormDef(StorableObject):
|
|||
formdef.workflow_id = w.id
|
||||
break
|
||||
|
||||
more_attributes = ['max_field_id']
|
||||
more_attributes = ['max_field_id', 'tracking_code_verify_fields']
|
||||
for attribute in cls.TEXT_ATTRIBUTES + cls.BOOLEAN_ATTRIBUTES + more_attributes:
|
||||
if attribute in value:
|
||||
setattr(formdef, attribute, value.get(attribute))
|
||||
|
@ -1209,6 +1210,11 @@ class FormDef(StorableObject):
|
|||
if self.max_field_id:
|
||||
ET.SubElement(root, 'max_field_id').text = str(self.max_field_id)
|
||||
|
||||
if self.tracking_code_verify_fields is not None:
|
||||
verify_fields = ET.SubElement(root, 'tracking_code_verify_fields')
|
||||
for field_id in self.tracking_code_verify_fields:
|
||||
ET.SubElement(verify_fields, 'field_id').text = str(field_id)
|
||||
|
||||
fields = ET.SubElement(root, 'fields')
|
||||
for field in self.fields or []:
|
||||
fields.append(field.export_to_xml(charset=charset, include_id=include_id))
|
||||
|
@ -1400,6 +1406,12 @@ class FormDef(StorableObject):
|
|||
else:
|
||||
formdef.max_field_id = max(lax_int(x.id) for x in formdef.fields)
|
||||
|
||||
if tree.find('tracking_code_verify_fields') is not None:
|
||||
formdef.tracking_code_verify_fields = [
|
||||
xml_node_text(verify_field_id)
|
||||
for verify_field_id in tree.findall('tracking_code_verify_fields/field_id')
|
||||
]
|
||||
|
||||
formdef.workflow_options = {}
|
||||
for option in tree.findall('options/option'):
|
||||
option_value = None
|
||||
|
|
|
@ -45,7 +45,7 @@ from wcs.roles import logged_users_role
|
|||
from wcs.variables import LazyFormDef
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowStatusItem
|
||||
|
||||
from ..qommon import _, emails, errors, get_cfg, misc, template
|
||||
from ..qommon import _, emails, errors, get_cfg, misc, ngettext, template
|
||||
from ..qommon.admin.emails import EmailsDirectory
|
||||
from ..qommon.form import CheckboxWidget, EmailWidget, Form, HiddenErrorWidget, HtmlWidget, StringWidget
|
||||
from ..qommon.template import TemplateError
|
||||
|
@ -181,6 +181,48 @@ class TrackingCodeDirectory(Directory):
|
|||
raise errors.TraversalError()
|
||||
if get_request().is_from_bot():
|
||||
raise errors.AccessForbiddenError()
|
||||
|
||||
verify_fields = []
|
||||
for field in formdata.formdef.fields:
|
||||
if field.id in (formdata.formdef.tracking_code_verify_fields or []):
|
||||
if formdata.status == 'draft' and not formdata.data.get(field.id):
|
||||
# a draft could be incomplete: do not test its empty values
|
||||
continue
|
||||
verify_fields.append(field)
|
||||
if verify_fields:
|
||||
form = Form()
|
||||
for field in verify_fields:
|
||||
widget = field.add_to_form(form)
|
||||
widget.field = field
|
||||
form.add_submit('submit', _('Verify'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
|
||||
if form.get_submit() == 'cancel':
|
||||
return redirect('/')
|
||||
|
||||
bad_content = False
|
||||
if form.is_submitted() and not form.has_errors():
|
||||
for field in verify_fields:
|
||||
value = formdata.data.get(field.id)
|
||||
verify_value = form.get_widget('f%s' % field.id).parse()
|
||||
if value != verify_value:
|
||||
# global error: we do not specify which field is in error, for security
|
||||
form.add_global_errors([_('Access denied: this content does match the form.')])
|
||||
bad_content = True
|
||||
break
|
||||
|
||||
if not form.is_submitted() or form.has_errors() or bad_content:
|
||||
html_top()
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s</h2>') % _('Access rights verification')
|
||||
r += htmltext('<p>%s</p>') % ngettext(
|
||||
'In order to be able to access the form, indicate the content of the following field.',
|
||||
'In order to be able to access the form, indicate the content of the following fields.',
|
||||
len(verify_fields),
|
||||
)
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
get_session().mark_anonymous_formdata(formdata)
|
||||
return redirect(formdata.get_url())
|
||||
|
||||
|
|
Loading…
Reference in New Issue