0001-general-allow-marking-form-as-required-a-strong-auth.patch
tests/test_admin_pages.py | ||
---|---|---|
477 | 477 |
assert data_class.get(formdata1.id).status == 'wf-finished' |
478 | 478 |
assert data_class.get(formdata2.id).status == 'draft' |
479 | 479 | |
480 |
def test_form_submitter_roles(pub): |
|
481 |
create_superuser(pub) |
|
482 |
role = create_role() |
|
483 | ||
484 |
FormDef.wipe() |
|
485 |
formdef = FormDef() |
|
486 |
formdef.name = 'form title' |
|
487 |
formdef.fields = [] |
|
488 |
formdef.store() |
|
489 | ||
490 |
app = login(get_app(pub)) |
|
491 |
resp = app.get('/backoffice/forms/1/') |
|
492 |
resp = resp.click(href=re.compile('^roles$')) |
|
493 |
resp.form['roles$element0'] = 'logged-users' |
|
494 |
assert not 'required_authentication_levels' in resp.body |
|
495 |
resp = resp.form.submit() |
|
496 |
assert FormDef.get(formdef.id).roles == ['logged-users'] |
|
497 | ||
498 |
# add auth levels support |
|
499 |
if not pub.site_options.has_section('options'): |
|
500 |
pub.site_options.add_section('options') |
|
501 |
pub.site_options.set('options', 'auth-levels', 'fedict') |
|
502 |
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: |
|
503 |
pub.site_options.write(fd) |
|
504 | ||
505 |
app = login(get_app(pub)) |
|
506 |
resp = app.get('/backoffice/forms/1/') |
|
507 |
resp = resp.click(href=re.compile('^roles$')) |
|
508 |
assert 'required_authentication_levels' in resp.body |
|
509 |
resp.form['required_authentication_levels$element0'].checked = True |
|
510 |
resp = resp.form.submit() |
|
511 |
assert FormDef.get(formdef.id).required_authentication_levels == ['fedict'] |
|
512 | ||
480 | 513 |
def test_form_workflow_role(pub): |
481 | 514 |
create_superuser(pub) |
482 | 515 |
role = create_role() |
tests/test_api.py | ||
---|---|---|
335 | 335 |
assert resp.json[0]['authentication_required'] |
336 | 336 |
assert resp.json == resp2.json == resp3.json == resp4.json |
337 | 337 | |
338 |
formdef.required_authentication_levels = ['fedict'] |
|
339 |
formdef.store() |
|
340 |
resp = get_app(pub).get('/api/formdefs/') |
|
341 |
assert resp.json[0]['required_authentication_levels'] == ['fedict'] |
|
342 | ||
338 | 343 |
def test_formdef_list_redirection(pub): |
339 | 344 |
FormDef.wipe() |
340 | 345 |
formdef = FormDef() |
tests/test_form_pages.py | ||
---|---|---|
325 | 325 |
login(get_app(pub), username='foo', password='foo').get('/test/', status=200) |
326 | 326 | |
327 | 327 |
# check special "logged users" role |
328 |
formdef.roles = [logged_users_role()] |
|
328 |
formdef.roles = [logged_users_role().id]
|
|
329 | 329 |
formdef.store() |
330 | 330 |
user = create_user(pub) |
331 |
login(get_app(pub), username='foo', password='foo').get('/test/', status=403)
|
|
331 |
login(get_app(pub), username='foo', password='foo').get('/test/', status=200)
|
|
332 | 332 |
resp = get_app(pub).get('/test/', status=302) # redirect to login |
333 | 333 | |
334 | 334 |
# check "receiver" can also access the formdef |
... | ... | |
341 | 341 |
user.store() |
342 | 342 |
login(get_app(pub), username='foo', password='foo').get('/test/', status=200) |
343 | 343 | |
344 |
def test_form_access_auth_level(pub): |
|
345 |
user = create_user(pub) |
|
346 | ||
347 |
if not pub.site_options.has_section('options'): |
|
348 |
pub.site_options.add_section('options') |
|
349 |
pub.site_options.set('options', 'auth-levels', 'fedict') |
|
350 |
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: |
|
351 |
pub.site_options.write(fd) |
|
352 | ||
353 |
formdef = create_formdef() |
|
354 |
get_app(pub).get('/test/', status=200) |
|
355 | ||
356 |
formdef.required_authentication_levels = ['fedict'] |
|
357 |
formdef.roles = [logged_users_role().id] |
|
358 |
formdef.store() |
|
359 | ||
360 |
# an unlogged user will get a redirect to login |
|
361 |
resp = get_app(pub).get('/test/', status=302) |
|
362 |
assert '/login' in resp.location |
|
363 | ||
364 |
# a user logged in with a simple username/password tuple will get a page |
|
365 |
# to relogin with a stronger auth |
|
366 |
app = login(get_app(pub), username='foo', password='foo') |
|
367 |
resp = app.get('/test/') |
|
368 |
assert 'You need a stronger authentication level to fill this form.' in resp.body |
|
369 | ||
370 |
for session in pub.session_manager.values(): |
|
371 |
session.saml_authn_context = 'urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI' |
|
372 |
session.store() |
|
373 |
resp = app.get('/test/') |
|
374 |
assert 'You need a stronger authentication level to fill this form.' not in resp.body |
|
375 |
assert resp.form |
|
376 | ||
344 | 377 |
def test_form_submit(pub): |
345 | 378 |
formdef = create_formdef() |
346 | 379 |
formdef.data_class().wipe() |
tests/test_formdef_import.py | ||
---|---|---|
408 | 408 |
formdef.backoffice_submission_roles = [role.id] |
409 | 409 |
fd2 = assert_xml_import_export_works(formdef, include_id=True) |
410 | 410 |
assert fd2.backoffice_submission_roles == formdef.backoffice_submission_roles |
411 | ||
412 |
def test_required_authentication_levels(): |
|
413 |
formdef = FormDef() |
|
414 |
formdef.name = 'foo' |
|
415 |
formdef.fields = [] |
|
416 |
formdef.required_authentication_levels = ['fedict'] |
|
417 |
fd2 = assert_xml_import_export_works(formdef, include_id=True) |
|
418 |
assert fd2.required_authentication_levels == formdef.required_authentication_levels |
wcs/admin/forms.py | ||
---|---|---|
613 | 613 |
'render_br': False, |
614 | 614 |
'options': options |
615 | 615 |
}) |
616 |
auth_levels = get_publisher().get_supported_authentication_levels() |
|
617 |
if attribute == 'roles' and auth_levels: |
|
618 |
form.add(CheckboxesWidget, 'required_authentication_levels', |
|
619 |
title=_('Required authentication levels'), |
|
620 |
value=self.formdef.required_authentication_levels, |
|
621 |
options=auth_levels.items()) |
|
616 | 622 |
form.add_submit('submit', _('Submit')) |
617 | 623 |
form.add_submit('cancel', _('Cancel')) |
618 | 624 |
if form.get_widget('cancel').parse(): |
... | ... | |
630 | 636 |
else: |
631 | 637 |
roles = form.get_widget('roles').parse() or [] |
632 | 638 |
setattr(self.formdef, attribute, [x for x in roles if x]) |
639 |
if form.get_widget('required_authentication_levels'): |
|
640 |
self.formdef.required_authentication_levels = form.get_widget( |
|
641 |
'required_authentication_levels').parse() |
|
633 | 642 |
self.formdef.store() |
634 | 643 |
return redirect('.') |
635 | 644 |
wcs/api.py | ||
---|---|---|
291 | 291 |
'description': formdef.description or '', |
292 | 292 |
'keywords': formdef.keywords_list, |
293 | 293 |
'authentication_required': authentication_required} |
294 |
if formdef.required_authentication_levels: |
|
295 |
formdict['required_authentication_levels'] = formdef.required_authentication_levels |
|
294 | 296 | |
295 | 297 |
formdict['redirection'] = bool(formdef.is_disabled() and |
296 | 298 |
formdef.disabled_redirection) |
wcs/backoffice/submission.py | ||
---|---|---|
85 | 85 |
def html_top(self, *args, **kwargs): |
86 | 86 |
return html_top('submission', *args, **kwargs) |
87 | 87 | |
88 |
def check_authentication_level(self): |
|
89 |
pass |
|
90 | ||
88 | 91 |
def check_role(self): |
89 | 92 |
if self.edit_mode: |
90 | 93 |
return True |
wcs/formdef.py | ||
---|---|---|
73 | 73 |
workflow_options = None |
74 | 74 |
workflow_roles = None |
75 | 75 |
roles = None |
76 |
required_authentication_levels = None |
|
76 | 77 |
backoffice_submission_roles = None |
77 | 78 |
discussion = False |
78 | 79 |
confirmation = True |
... | ... | |
566 | 567 |
if self.workflow_options: |
567 | 568 |
root['options'] = self.workflow_options |
568 | 569 | |
570 |
if self.required_authentication_levels: |
|
571 |
root['required_authentication_levels'] = self.required_authentication_levels[:] |
|
572 | ||
569 | 573 |
return json.dumps(root, indent=indent, cls=misc.JSONEncoder) |
570 | 574 | |
571 | 575 |
@classmethod |
... | ... | |
650 | 654 |
if value.get('geolocations'): |
651 | 655 |
formdef.geolocations = value.get('geolocations') |
652 | 656 | |
657 |
if value.get('required_authentication_levels'): |
|
658 |
formdef.required_authentication_levels = [str(x) for x in |
|
659 |
value.get('required_authentication_levels')] |
|
660 | ||
653 | 661 |
return formdef |
654 | 662 | |
655 | 663 |
def export_to_xml(self, include_id=False): |
... | ... | |
759 | 767 |
element.attrib['key'] = geoloc_key |
760 | 768 |
element.text = unicode(geoloc_label, charset) |
761 | 769 | |
770 |
if self.required_authentication_levels: |
|
771 |
element = ET.SubElement(root, 'required_authentication_levels') |
|
772 |
for auth_level in self.required_authentication_levels: |
|
773 |
ET.SubElement(element, 'method').text = unicode(auth_level) |
|
774 | ||
762 | 775 |
return root |
763 | 776 | |
764 | 777 |
@classmethod |
... | ... | |
946 | 959 |
geoloc_value = child.text.encode(charset) |
947 | 960 |
formdef.geolocations[geoloc_key] = geoloc_value |
948 | 961 | |
962 |
if tree.find('required_authentication_levels') is not None: |
|
963 |
node = tree.find('required_authentication_levels') |
|
964 |
formdef.required_authentication_levels = [] |
|
965 |
for child in node.getchildren(): |
|
966 |
formdef.required_authentication_levels.append(str(child.text)) |
|
967 | ||
949 | 968 |
return formdef |
950 | 969 | |
951 | 970 |
def get_detailed_email_form(self, formdata, url): |
wcs/forms/root.py | ||
---|---|---|
459 | 459 |
(tracking code and steps).''' |
460 | 460 |
r = TemplateIO(html=True) |
461 | 461 |
r += htmltext('<div id="side">') |
462 |
if self.formdef.enable_tracking_codes: |
|
462 |
if self.formdef.enable_tracking_codes and data:
|
|
463 | 463 |
r += self.tracking_code_box(data, magictoken) |
464 | 464 |
r += self.step(step_no, page_no, log_detail, data=data) |
465 | 465 |
r += htmltext('</div> <!-- #side -->') |
... | ... | |
524 | 524 |
def create_view_form(self, *args, **kwargs): |
525 | 525 |
return self.formdef.create_view_form(*args, **kwargs) |
526 | 526 | |
527 |
def check_authentication_level(self): |
|
528 |
if not self.formdef.required_authentication_levels: |
|
529 |
return |
|
530 |
if get_session().get_authentication_level() in self.formdef.required_authentication_levels: |
|
531 |
return |
|
532 | ||
533 |
self.html_top(self.formdef.name) |
|
534 |
r = TemplateIO(html=True) |
|
535 |
r += self.form_side(step_no=0, page_no=0) |
|
536 |
auth_levels = get_publisher().get_supported_authentication_levels() |
|
537 |
r += htmltext('<div class="errornotice">') |
|
538 |
r += htmltext('<p>%s</p>') % _('You need a stronger authentication level to fill this form.') |
|
539 |
r += htmltext('</div>') |
|
540 |
root_url = get_publisher().get_root_url() |
|
541 |
for auth_level in self.formdef.required_authentication_levels: |
|
542 |
r += htmltext('<p><a class="button" href="%slogin/?forceAuthn=true">%s</a></p>') % ( |
|
543 |
root_url, _('Login with %s') % auth_levels[auth_level]) |
|
544 |
return r.getvalue() |
|
545 | ||
527 | 546 |
def _q_index(self, log_detail=None): |
528 | 547 |
self.check_role() |
548 |
authentication_level_check_result = self.check_authentication_level() |
|
549 |
if authentication_level_check_result: |
|
550 |
return authentication_level_check_result |
|
551 | ||
529 | 552 |
if self.check_disabled(): |
530 | 553 |
return redirect(self.check_disabled()) |
531 | 554 |
wcs/qommon/publisher.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU General Public License |
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import collections |
|
17 | 18 |
import cPickle |
18 | 19 |
import ConfigParser |
19 | 20 |
import imp |
... | ... | |
972 | 973 |
default_position = '50.84;4.36' |
973 | 974 |
return default_position |
974 | 975 | |
976 |
def get_supported_authentication_levels(self): |
|
977 |
levels = collections.OrderedDict() |
|
978 |
labels = { |
|
979 |
'fedict': _('Belgian eID'), |
|
980 |
'franceconnect': _('FranceConnect'), |
|
981 |
} |
|
982 |
if self.get_site_option('auth-levels'): |
|
983 |
for level in self.get_site_option('auth-levels').split(','): |
|
984 |
level = level.strip() |
|
985 |
levels[level] = labels[level] |
|
986 |
return levels |
|
987 | ||
988 |
def get_authentication_level_saml_contexts(self, level): |
|
989 |
return { |
|
990 |
'fedict': [ |
|
991 |
# custom context, provided by authentic fedict plugin: |
|
992 |
'urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI', |
|
993 |
# native fedict contexts: |
|
994 |
'urn:be:fedict:iam:fas:citizen:eid', |
|
995 |
'urn:be:fedict:iam:fas:citizen:token', |
|
996 |
'urn:be:fedict:iam:fas:enterprise:eid', |
|
997 |
'urn:be:fedict:iam:fas:enterprise:token'], |
|
998 |
'franceconnect': [ |
|
999 |
'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport', |
|
1000 |
] |
|
1001 |
}[level] |
|
1002 | ||
975 | 1003 |
def get_substitution_variables(self): |
976 | 1004 |
import misc |
977 | 1005 |
d = { |
wcs/qommon/sessions.py | ||
---|---|---|
171 | 171 |
def get_user_object(self): |
172 | 172 |
return self.get_user() |
173 | 173 | |
174 |
def get_authentication_level(self): |
|
175 |
for level in get_publisher().get_supported_authentication_levels(): |
|
176 |
contexts = get_publisher().get_authentication_level_saml_contexts(level) |
|
177 |
if self.saml_authn_context in contexts: |
|
178 |
return level |
|
179 |
return None |
|
180 | ||
174 | 181 |
def add_tempfile(self, upload): |
175 | 182 |
from wcs.qommon.form import PicklableUpload |
176 | 183 |
token = randbytes(8) |
177 |
- |