689 lines
26 KiB
Python
689 lines
26 KiB
Python
# authentic2 - versatile identity manager
|
|
# Copyright (C) 2010-2022 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from django import VERSION as DJ_VERSION
|
|
from django.utils.html import escape
|
|
from webtest import Upload
|
|
|
|
from authentic2.a2_rbac.models import Role
|
|
from authentic2.apps.authenticators.models import AddRoleAction, BaseAuthenticator, LoginPasswordAuthenticator
|
|
from authentic2.models import Attribute
|
|
from authentic2_auth_fc.models import FcAuthenticator
|
|
from authentic2_auth_oidc.models import OIDCProvider
|
|
from authentic2_auth_saml.models import SAMLAuthenticator, SetAttributeAction
|
|
|
|
from .utils import assert_event, login, logout, request_select2
|
|
|
|
|
|
def test_authenticators_authorization(app, simple_user, simple_role, admin, superuser):
|
|
simple_user.roles.add(simple_role.get_admin_role()) # grant user access to /manage/
|
|
|
|
resp = login(app, simple_user, path='/manage/')
|
|
assert 'Authenticators' not in resp.text
|
|
app.get('/manage/authenticators/', status=403)
|
|
|
|
role = Role.objects.get(name='Manager of authenticators')
|
|
simple_user.roles.add(role)
|
|
|
|
resp = app.get('/manage/')
|
|
resp = resp.click('Authentication frontends')
|
|
assert 'Authenticators' in resp.text
|
|
|
|
logout(app)
|
|
resp = login(app, admin, path='/manage/')
|
|
assert 'Authentication frontends' in resp.text
|
|
|
|
resp = resp.click('Authentication frontends')
|
|
assert 'Authenticators' in resp.text
|
|
|
|
logout(app)
|
|
resp = login(app, superuser, path='/manage/')
|
|
assert 'Authentication frontends' in resp.text
|
|
|
|
resp = resp.click('Authentication frontends')
|
|
assert 'Authenticators' in resp.text
|
|
|
|
|
|
def test_authenticators_password(app, superuser_or_admin, settings):
|
|
resp = login(app, superuser_or_admin, path='/manage/authenticators/')
|
|
# Password authenticator already exists
|
|
assert 'Password' in resp.text
|
|
authenticator = LoginPasswordAuthenticator.objects.get()
|
|
|
|
resp = resp.click('Configure')
|
|
assert 'Show condition: None' in resp.text
|
|
# cannot delete password authenticator
|
|
assert 'Delete' not in resp.text
|
|
assert 'configuration is not complete' not in resp.text
|
|
app.get('/manage/authenticators/%s/delete/' % authenticator.pk, status=403)
|
|
|
|
resp = resp.click('Edit')
|
|
assert list(resp.form.fields) == [
|
|
'csrfmiddlewaretoken',
|
|
'show_condition',
|
|
'button_description',
|
|
'registration_open',
|
|
'min_password_strength',
|
|
'password_min_length',
|
|
'remember_me',
|
|
'include_ou_selector',
|
|
'password_regex',
|
|
'password_regex_error_msg',
|
|
'login_exponential_retry_timeout_duration',
|
|
'login_exponential_retry_timeout_factor',
|
|
'login_exponential_retry_timeout_max_duration',
|
|
'login_exponential_retry_timeout_min_duration',
|
|
'emails_ip_ratelimit',
|
|
'sms_ip_ratelimit',
|
|
'emails_address_ratelimit',
|
|
'sms_number_ratelimit',
|
|
None,
|
|
]
|
|
|
|
resp.form['show_condition'] = '}'
|
|
resp = resp.form.submit()
|
|
assert 'could not parse expression: unmatched' in resp.text
|
|
|
|
resp.form['show_condition'] = "'backoffice' in login_hint or remote_addr == '1.2.3.4'"
|
|
resp = resp.form.submit().follow()
|
|
assert 'Click "Edit" to change configuration.' not in resp.text
|
|
if DJ_VERSION[0] <= 2:
|
|
assert (
|
|
'Show condition: 'backoffice' in login_hint or remote_addr == '1.2.3.4''
|
|
in resp.text
|
|
)
|
|
else:
|
|
# html-rendered quote characters change in django 3 onwards…
|
|
assert (
|
|
'Show condition: 'backoffice' in login_hint or remote_addr == '1.2.3.4''
|
|
in resp.text
|
|
)
|
|
assert_event('authenticator.edit', user=superuser_or_admin, session=app.session)
|
|
|
|
resp = resp.click('Edit')
|
|
resp.form['show_condition'] = "remote_addr in dnsbl('ddns.entrouvert.org')"
|
|
resp = resp.form.submit().follow()
|
|
assert 'dnsbl' in resp.text
|
|
|
|
# password authenticator cannot be disabled
|
|
assert 'Disable' not in resp.text
|
|
app.get('/manage/authenticators/%s/toggle/' % authenticator.pk, status=403)
|
|
|
|
resp = resp.click('Journal of edits')
|
|
assert resp.text.count('edit (show_condition)') == 2
|
|
|
|
# cannot add another password authenticator
|
|
resp = app.get('/manage/authenticators/add/')
|
|
assert 'Password' not in resp.text
|
|
|
|
# phone authn management feature flag is activated
|
|
settings.A2_ALLOW_PHONE_AUTHN_MANAGEMENT = True
|
|
|
|
phone1 = Attribute.objects.create(
|
|
name='another_phone',
|
|
kind='phone_number',
|
|
label='Another phone',
|
|
)
|
|
phone2 = Attribute.objects.create(
|
|
name='yet_another_phone',
|
|
kind='fr_phone_number',
|
|
label='Yet another phone',
|
|
)
|
|
|
|
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
assert 'Time (in seconds, between 60 and 3600) after which SMS codes expire. Default is 180' in resp.text
|
|
|
|
settings.SMS_CODE_DURATION = 240
|
|
|
|
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
assert resp.form['sms_code_duration'].value == ''
|
|
|
|
resp.form['accept_email_authentication'] = False
|
|
resp.form['accept_phone_authentication'] = True
|
|
resp.form['sms_code_duration'] = '1200'
|
|
assert 'Time (in seconds, between 60 and 3600) after which SMS codes expire. Default is 240' in resp.text
|
|
assert resp.form['phone_identifier_field'].options == [
|
|
(str(phone1.id), False, 'Another phone'),
|
|
(str(phone2.id), False, 'Yet another phone'),
|
|
]
|
|
resp.form['phone_identifier_field'] = phone2.id
|
|
resp.form.submit()
|
|
|
|
authenticator.refresh_from_db()
|
|
assert authenticator.accept_email_authentication is False
|
|
assert authenticator.accept_phone_authentication is True
|
|
assert authenticator.phone_identifier_field == phone2
|
|
assert authenticator.sms_code_duration == 1200
|
|
|
|
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
resp.form['sms_code_duration'] = '4200' # too high
|
|
resp = resp.form.submit()
|
|
assert resp.pyquery('.error')[0].text_content().strip() == (
|
|
'Ensure that this value is lower than 3600, or leave blank for default value.'
|
|
)
|
|
authenticator.refresh_from_db()
|
|
assert authenticator.sms_code_duration == 1200
|
|
|
|
resp.form['sms_code_duration'] = '42' # too low
|
|
resp = resp.form.submit()
|
|
assert resp.pyquery('.error')[0].text_content().strip() == (
|
|
'Ensure that this value is higher than 60, or leave blank for default value.'
|
|
)
|
|
authenticator.refresh_from_db()
|
|
assert authenticator.sms_code_duration == 1200
|
|
|
|
resp.form['sms_code_duration'] = '2442' # new valid value
|
|
resp = resp.form.submit()
|
|
assert resp.location == f'/manage/authenticators/{authenticator.pk}/detail/'
|
|
resp = resp.follow()
|
|
assert not resp.pyquery('.error')
|
|
authenticator.refresh_from_db()
|
|
assert authenticator.sms_code_duration == 2442
|
|
|
|
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
resp.form.set('sms_code_duration', '')
|
|
resp = resp.form.submit()
|
|
authenticator.refresh_from_db()
|
|
assert authenticator.sms_code_duration is None
|
|
|
|
|
|
@pytest.mark.freeze_time('2022-04-19 14:00')
|
|
def test_authenticators_password_export(app, superuser):
|
|
resp = login(app, superuser, path='/manage/authenticators/')
|
|
assert LoginPasswordAuthenticator.objects.count() == 1
|
|
|
|
resp = resp.click('Configure')
|
|
resp = resp.click('Export')
|
|
assert resp.headers['content-type'] == 'application/json'
|
|
assert (
|
|
resp.headers['content-disposition']
|
|
== 'attachment; filename="export_password_authenticator_20220419.json"'
|
|
)
|
|
|
|
authenticator_json = json.loads(resp.text)
|
|
assert authenticator_json == {
|
|
'authenticator_type': 'authenticators.loginpasswordauthenticator',
|
|
'name': '',
|
|
'slug': 'password-authenticator',
|
|
'show_condition': '',
|
|
'button_description': '',
|
|
'button_label': 'Login',
|
|
'registration_open': True,
|
|
'remember_me': None,
|
|
'include_ou_selector': False,
|
|
'min_password_strength': None,
|
|
'password_min_length': 8,
|
|
'password_regex': '',
|
|
'password_regex_error_msg': '',
|
|
'login_exponential_retry_timeout_duration': 1,
|
|
'login_exponential_retry_timeout_factor': 1.8,
|
|
'login_exponential_retry_timeout_max_duration': 3600,
|
|
'login_exponential_retry_timeout_min_duration': 10,
|
|
'emails_ip_ratelimit': '10/h',
|
|
'sms_ip_ratelimit': '10/h',
|
|
'emails_address_ratelimit': '3/d',
|
|
'sms_number_ratelimit': '10/h',
|
|
'ou': None,
|
|
'related_objects': [],
|
|
'accept_email_authentication': True,
|
|
'accept_phone_authentication': False,
|
|
'sms_code_duration': None,
|
|
}
|
|
|
|
resp = app.get('/manage/authenticators/')
|
|
resp = resp.click('Import')
|
|
authenticator_json['button_description'] = 'test'
|
|
resp.form['authenticator_json'] = Upload(
|
|
'export.json', json.dumps(authenticator_json).encode(), 'application/json'
|
|
)
|
|
resp = resp.form.submit()
|
|
assert LoginPasswordAuthenticator.objects.count() == 1
|
|
assert LoginPasswordAuthenticator.objects.get().button_description == 'test'
|
|
|
|
|
|
def test_authenticators_fc(app, superuser):
|
|
resp = login(app, superuser, path='/manage/authenticators/')
|
|
|
|
resp = resp.click('Add new authenticator')
|
|
resp.form['authenticator'] = 'fc'
|
|
resp = resp.form.submit()
|
|
assert '/edit/' in resp.location
|
|
|
|
provider = FcAuthenticator.objects.get()
|
|
assert provider.order == -1
|
|
|
|
resp = app.get(provider.get_absolute_url())
|
|
assert 'extra-actions-menu-opener' in resp.text
|
|
assert 'Platform: Integration' in resp.text
|
|
assert 'Scopes: profile (profile), email (email)' in resp.text
|
|
assert 'Client ID' not in resp.text
|
|
assert 'Client Secret' not in resp.text
|
|
|
|
resp = resp.click('Edit')
|
|
assert list(resp.form.fields) == [
|
|
'csrfmiddlewaretoken',
|
|
'show_condition',
|
|
'platform',
|
|
'client_id',
|
|
'client_secret',
|
|
'scopes',
|
|
'link_by_email',
|
|
None,
|
|
]
|
|
assert 'phone' not in resp.pyquery('#id_scopes').html()
|
|
assert 'address' not in resp.pyquery('#id_scopes').html()
|
|
|
|
resp.form['platform'] = 'prod'
|
|
resp.form['client_id'] = '211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6k'
|
|
resp.form['client_secret'] = '211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6d'
|
|
resp.form['scopes'] = ['given_name', 'birthdate']
|
|
resp = resp.form.submit().follow()
|
|
|
|
assert 'Platform: Production' in resp.text
|
|
assert 'Scopes: given name (given_name), birthdate (birthdate)' in resp.text
|
|
assert 'Client ID: 211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6k' in resp.text
|
|
assert 'Client Secret: 211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6d' in resp.text
|
|
|
|
resp = app.get('/manage/authenticators/')
|
|
assert 'FranceConnect' in resp.text
|
|
assert 'class="section disabled"' in resp.text
|
|
|
|
resp = resp.click('Configure', index=1)
|
|
resp = resp.click('Enable').follow()
|
|
assert 'Authenticator has been enabled.' in resp.text
|
|
|
|
resp = app.get('/manage/authenticators/')
|
|
assert 'class="section disabled"' not in resp.text
|
|
|
|
provider.refresh_from_db()
|
|
provider.scopes.extend(['phone', 'address']) # deprecated scopes
|
|
provider.save()
|
|
|
|
resp = app.get(provider.get_absolute_url())
|
|
resp = resp.click('Edit')
|
|
resp.form.submit().follow()
|
|
provider.refresh_from_db()
|
|
assert 'phone' not in provider.scopes
|
|
assert 'address' not in provider.scopes
|
|
|
|
|
|
def test_authenticators_saml(app, superuser, ou1, ou2):
|
|
resp = login(app, superuser, path='/manage/authenticators/')
|
|
|
|
resp = resp.click('Add new authenticator')
|
|
resp.form['name'] = 'Test'
|
|
resp.form['authenticator'] = 'saml'
|
|
resp = resp.form.submit()
|
|
|
|
authenticator = SAMLAuthenticator.objects.filter(slug='test').get()
|
|
resp = app.get(authenticator.get_absolute_url())
|
|
assert 'Create user if their username does not already exists: Yes' in resp.text
|
|
assert 'Metadata file path' not in resp.text
|
|
|
|
assert 'Enable' not in resp.text
|
|
assert 'configuration is not complete' in resp.text
|
|
|
|
resp = resp.click('Edit')
|
|
assert resp.pyquery('button#tab-general').attr('class') == 'pk-tabs--button-marker'
|
|
assert not resp.pyquery('button#tab-advanced').attr('class')
|
|
assert 'ou' in resp.form.fields
|
|
|
|
resp = resp.form.submit()
|
|
assert 'One of the metadata fields must be filled.' in resp.text
|
|
|
|
resp.form['metadata_url'] = 'https://example.com/metadata.xml'
|
|
resp = resp.form.submit().follow()
|
|
assert 'Metadata URL: https://example.com/metadata.xml' in resp.text
|
|
|
|
resp = resp.click('Enable').follow()
|
|
assert 'Authenticator has been enabled.' in resp.text
|
|
|
|
resp = resp.click('Edit')
|
|
resp.form['attribute_mapping'] = '[{"attribute": "email", "saml_attribute": "mail", "mandatory": false}]'
|
|
resp = resp.form.submit().follow()
|
|
|
|
authenticator.refresh_from_db()
|
|
assert authenticator.attribute_mapping == [
|
|
{'attribute': 'email', 'saml_attribute': 'mail', 'mandatory': False}
|
|
]
|
|
|
|
resp = resp.click('Edit')
|
|
assert resp.pyquery('button#tab-advanced').attr('class') == 'pk-tabs--button-marker'
|
|
|
|
resp = app.get(authenticator.get_absolute_url())
|
|
resp = resp.click('Journal of edits')
|
|
assert 'edit (metadata_url)' in resp.text
|
|
|
|
|
|
def test_authenticators_saml_hide_metadata_url_advanced_fields(app, superuser, ou1, ou2):
|
|
authenticator = SAMLAuthenticator.objects.create(slug='idp1')
|
|
|
|
resp = login(app, superuser)
|
|
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
assert 'Metadata cache time' not in resp.text
|
|
assert 'Metadata HTTP timeout' not in resp.text
|
|
|
|
resp.form['metadata_url'] = 'https://example.com/metadata.xml'
|
|
resp = resp.form.submit().follow()
|
|
|
|
resp = resp.click('Edit')
|
|
assert 'Metadata cache time' in resp.text
|
|
assert 'Metadata HTTP timeout' in resp.text
|
|
|
|
|
|
def test_authenticators_saml_validate_metadata(app, superuser):
|
|
authenticator = SAMLAuthenticator.objects.create(slug='idp1')
|
|
|
|
resp = login(app, superuser)
|
|
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
resp.form['metadata'] = 'invalid'
|
|
|
|
resp.form['metadata'] = '<a/>'
|
|
resp = resp.form.submit()
|
|
assert 'Invalid metadata, missing tag {urn:oasis:names:tc:SAML:2.0:metadata}EntityDescriptor' in resp.text
|
|
|
|
resp.form[
|
|
'metadata'
|
|
] = '<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"></ns0:EntityDescriptor>'
|
|
resp = resp.form.submit()
|
|
assert 'Invalid metadata, missing entityID' in resp.text
|
|
|
|
resp.form['metadata'] = (
|
|
'<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"'
|
|
' entityID="https://example.com"></ns0:EntityDescriptor>'
|
|
)
|
|
resp.form.submit(status=302)
|
|
|
|
|
|
def test_authenticators_saml_empty_attribute_mapping(app, superuser):
|
|
authenticator = SAMLAuthenticator.objects.create(metadata_url='https://example.com/meta.xml', slug='idp1')
|
|
|
|
resp = login(app, superuser)
|
|
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
|
|
resp.form['attribute_mapping'] = None
|
|
resp = resp.form.submit().follow()
|
|
|
|
authenticator.refresh_from_db()
|
|
assert authenticator.attribute_mapping == {}
|
|
|
|
|
|
def test_authenticators_saml_view_metadata(app, superuser):
|
|
authenticator = SAMLAuthenticator.objects.create(slug='idp1')
|
|
|
|
resp = login(app, superuser)
|
|
resp = app.get('/manage/authenticators/%s/detail/' % authenticator.pk)
|
|
|
|
assert 'Metadata (XML):' not in resp.text
|
|
assert app.get('/manage/authenticators/%s/metadata.xml' % authenticator.pk, status=404)
|
|
|
|
authenticator.metadata = '<a><b></b></a>'
|
|
authenticator.save()
|
|
|
|
resp = app.get('/manage/authenticators/%s/detail/' % authenticator.pk)
|
|
assert 'Metadata (XML):' in resp.text
|
|
|
|
resp = resp.click('View metadata')
|
|
assert resp.text == '<a><b></b></a>'
|
|
|
|
|
|
def test_authenticators_saml_missing_signing_key(app, superuser, settings):
|
|
authenticator = SAMLAuthenticator.objects.create(slug='idp1')
|
|
|
|
resp = login(app, superuser)
|
|
resp = app.get(authenticator.get_absolute_url())
|
|
assert 'Signing key is missing' in resp.text
|
|
|
|
settings.MELLON_PRIVATE_KEY = 'xxx'
|
|
settings.MELLON_PUBLIC_KEYS = ['yyy']
|
|
resp = app.get(authenticator.get_absolute_url())
|
|
assert 'Signing key is missing' not in resp.text
|
|
|
|
|
|
def test_authenticators_saml_no_name_display(app, superuser, ou1, ou2):
|
|
SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
|
|
|
|
resp = login(app, superuser, path='/manage/authenticators/')
|
|
assert 'SAML - idp1' in resp.text
|
|
|
|
|
|
def test_authenticators_saml_name_id_format_select(app, superuser):
|
|
authenticator = SAMLAuthenticator.objects.create(metadata_url='https://example.com/meta.xml', slug='idp1')
|
|
|
|
resp = login(app, superuser, path='/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
resp.form['name_id_policy_format'].select(
|
|
text='Persistent (urn:oasis:names:tc:SAML:2.0:nameid-format:persistent)'
|
|
)
|
|
resp.form.submit().follow()
|
|
|
|
authenticator.refresh_from_db()
|
|
assert authenticator.name_id_policy_format == 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
|
|
|
|
|
|
def test_authenticators_saml_attribute_lookup(app, superuser):
|
|
authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
|
|
resp = login(app, superuser, path=authenticator.get_absolute_url())
|
|
|
|
resp = resp.click('Add', href='samlattributelookup')
|
|
resp.form['user_field'].select(text='Email address (email)')
|
|
resp.form['saml_attribute'] = 'mail'
|
|
resp = resp.form.submit()
|
|
assert_event('authenticator.related_object.creation', user=superuser, session=app.session)
|
|
assert '#open:samlattributelookup' in resp.location
|
|
|
|
resp = resp.follow()
|
|
assert escape('"mail" (from "Email address (email)")') in resp.text
|
|
|
|
resp = resp.click('mail')
|
|
resp.form['ignore_case'] = True
|
|
resp = resp.form.submit().follow()
|
|
assert escape('"mail" (from "Email address (email)"), case insensitive') in resp.text
|
|
assert_event('authenticator.related_object.edit', user=superuser, session=app.session)
|
|
|
|
Attribute.objects.create(kind='string', name='test', label='Test')
|
|
resp = resp.click('mail')
|
|
resp.form['user_field'].select(text='Test (test)')
|
|
resp = resp.form.submit().follow()
|
|
assert escape('"mail" (from "Test (test)"), case insensitive') in resp.text
|
|
|
|
resp = resp.click('Remove', href='samlattributelookup')
|
|
resp = resp.form.submit().follow()
|
|
assert 'Test (test)' not in resp.text
|
|
assert_event('authenticator.related_object.deletion', user=superuser, session=app.session)
|
|
|
|
|
|
def test_authenticators_saml_set_attribute(app, superuser):
|
|
authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
|
|
resp = login(app, superuser, path=authenticator.get_absolute_url())
|
|
|
|
resp = resp.click('Add', href='setattributeaction')
|
|
resp.form['user_field'].select(text='Email address (email)')
|
|
resp.form['saml_attribute'] = 'mail'
|
|
resp = resp.form.submit().follow()
|
|
assert escape('"Email address (email)" from "mail"') in resp.text
|
|
|
|
resp = resp.click('mail')
|
|
resp.form['mandatory'] = True
|
|
resp = resp.form.submit().follow()
|
|
assert escape('"Email address (email)" from "mail" (mandatory)') in resp.text
|
|
|
|
|
|
def test_authenticators_saml_add_role(app, superuser, role_ou1, role_ou2):
|
|
authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
|
|
resp = login(app, superuser, path=authenticator.get_absolute_url())
|
|
|
|
resp = resp.click('Add', href='addroleaction')
|
|
select2_json = request_select2(app, resp, term='role_ou')
|
|
assert [x['text'] for x in select2_json['results']] == ['OU1 - role_ou1', 'OU2 - role_ou2']
|
|
|
|
resp.form['role'].force_value(select2_json['results'][0]['id'])
|
|
resp = resp.form.submit().follow()
|
|
assert 'role_ou1' in resp.text
|
|
|
|
resp = resp.click('role_ou1')
|
|
resp.form['role'].force_value(select2_json['results'][1]['id'])
|
|
resp = resp.form.submit().follow()
|
|
assert 'role_ou1' not in resp.text
|
|
assert 'role_ou2' in resp.text
|
|
|
|
|
|
def test_authenticators_saml_export(app, superuser, simple_role):
|
|
authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
|
|
SetAttributeAction.objects.create(authenticator=authenticator, user_field='test', saml_attribute='hop')
|
|
AddRoleAction.objects.create(authenticator=authenticator, role=simple_role)
|
|
|
|
resp = login(app, superuser, path=authenticator.get_absolute_url())
|
|
export_resp = resp.click('Export')
|
|
|
|
SAMLAuthenticator.objects.all().delete()
|
|
SetAttributeAction.objects.all().delete()
|
|
AddRoleAction.objects.all().delete()
|
|
|
|
resp = app.get('/manage/authenticators/import/')
|
|
resp.form['authenticator_json'] = Upload('export.json', export_resp.body, 'application/json')
|
|
resp = resp.form.submit().follow()
|
|
|
|
authenticator = SAMLAuthenticator.objects.get()
|
|
assert authenticator.slug == 'idp1'
|
|
assert authenticator.metadata == 'meta1.xml'
|
|
assert SetAttributeAction.objects.filter(
|
|
authenticator=authenticator, user_field='test', saml_attribute='hop'
|
|
).exists()
|
|
assert AddRoleAction.objects.filter(authenticator=authenticator, role=simple_role).exists()
|
|
|
|
|
|
def test_authenticators_order(app, superuser):
|
|
resp = login(app, superuser, path='/manage/authenticators/')
|
|
|
|
saml_authenticator = SAMLAuthenticator.objects.create(name='Test', slug='test', enabled=True, order=42)
|
|
SAMLAuthenticator.objects.create(name='Test disabled', slug='test-disabled', enabled=False)
|
|
fc_authenticator = FcAuthenticator.objects.create(slug='fc-authenticator', enabled=True, order=-1)
|
|
password_authenticator = LoginPasswordAuthenticator.objects.get()
|
|
|
|
assert fc_authenticator.order == -1
|
|
assert password_authenticator.order == 0
|
|
assert saml_authenticator.order == 42
|
|
|
|
resp = resp.click('Edit order')
|
|
assert resp.text.index('FranceConnect') < resp.text.index('Password') < resp.text.index('SAML - Test')
|
|
assert 'SAML - Test disabled' not in resp.text
|
|
|
|
resp.form['order'] = '%s,%s,%s' % (saml_authenticator.pk, password_authenticator.pk, fc_authenticator.pk)
|
|
resp.form.submit()
|
|
|
|
fc_authenticator.refresh_from_db()
|
|
password_authenticator.refresh_from_db()
|
|
saml_authenticator.refresh_from_db()
|
|
assert fc_authenticator.order == 2
|
|
assert password_authenticator.order == 1
|
|
assert saml_authenticator.order == 0
|
|
|
|
|
|
def test_authenticators_add_last(app, superuser):
|
|
resp = login(app, superuser, path='/manage/authenticators/')
|
|
|
|
BaseAuthenticator.objects.all().delete()
|
|
|
|
resp = resp.click('Add new authenticator')
|
|
resp.form['name'] = 'Test'
|
|
resp.form['authenticator'] = 'saml'
|
|
resp.form.submit()
|
|
|
|
authenticator = SAMLAuthenticator.objects.get()
|
|
assert authenticator.order == 1
|
|
|
|
authenticator.order = 42
|
|
authenticator.save()
|
|
resp = app.get('/manage/authenticators/add/')
|
|
resp.form['name'] = 'Test 2'
|
|
resp.form['authenticator'] = 'saml'
|
|
resp.form.submit()
|
|
|
|
authenticator = SAMLAuthenticator.objects.filter(slug='test-2').get()
|
|
assert authenticator.order == 43
|
|
|
|
|
|
def test_authenticators_configuration_info(app, superuser, ou1, ou2):
|
|
resp = login(app, superuser, path='/manage/authenticators/')
|
|
|
|
resp = resp.click('Add new authenticator')
|
|
assert resp.text.count('infonotice') == 2
|
|
assert '<div class="infonotice saml idp-info">' in resp.text
|
|
assert '<div class="infonotice oidc idp-info">' in resp.text
|
|
assert resp.text.count('Configuration information for your identity provider') == 2
|
|
|
|
# saml
|
|
assert (
|
|
'Metadata URL:<br><a href="https://testserver/accounts/saml/metadata/" rel="nofollow">'
|
|
'https://testserver/accounts/saml/metadata/</a>'
|
|
) in resp.text
|
|
assert 'Commonly expected attributes:' in resp.text
|
|
assert 'Email (email)' in resp.text
|
|
|
|
authenticator = SAMLAuthenticator.objects.create(metadata='meta1.xml', slug='idp1')
|
|
SetAttributeAction.objects.create(
|
|
authenticator=authenticator, user_field='email', saml_attribute='mail', mandatory=True
|
|
)
|
|
SetAttributeAction.objects.create(
|
|
authenticator=authenticator, user_field='first_name', saml_attribute='given_name'
|
|
)
|
|
|
|
resp = app.get('/manage/authenticators/%s/detail/' % authenticator.pk)
|
|
assert 'Information for configuration' in resp.text
|
|
assert 'Expected attributes' in resp.text
|
|
assert 'mail (mandatory)' in resp.text
|
|
assert 'given_name' in resp.text
|
|
|
|
# oidc
|
|
authenticator = OIDCProvider.objects.create(slug='idp2')
|
|
for url in ('/manage/authenticators/add/', '/manage/authenticators/%s/detail/' % authenticator.pk):
|
|
resp = app.get(url)
|
|
assert (
|
|
'Redirect URI (redirect_uri):<br><a href="https://testserver/accounts/oidc/callback/" '
|
|
'rel="nofollow">https://testserver/accounts/oidc/callback/</a>'
|
|
) in resp.text
|
|
assert (
|
|
'Redirect URI after logout (post_logout_redirect_uri):<br><a href="https://testserver/logout/" '
|
|
'rel="nofollow">https://testserver/logout/</a>'
|
|
) in resp.text
|
|
|
|
|
|
def test_authenticators_journal_pages(app, superuser):
|
|
authenticator = LoginPasswordAuthenticator.objects.get()
|
|
|
|
# generate login failure event
|
|
login(app, 'noone', password='wrong', fail=True)
|
|
|
|
login(app, superuser)
|
|
resp = app.get('/manage/authenticators/%s/edit/' % authenticator.pk)
|
|
|
|
# generate edit event
|
|
resp.form['button_description'] = 'abc'
|
|
resp = resp.form.submit().follow()
|
|
|
|
resp = app.get('/manage/authenticators/%s/detail/' % authenticator.pk)
|
|
resp = resp.click('Journal of edits')
|
|
assert resp.pyquery('td.journal-list--message-column').text() == 'edit (button_description)'
|
|
assert 'noone' not in resp.text
|
|
|
|
resp = app.get('/manage/authenticators/%s/detail/' % authenticator.pk)
|
|
resp = resp.click('Journal of logins')
|
|
assert resp.pyquery('td.journal-list--message-column').text() == 'login failure with username "noone"'
|
|
assert 'edit (button_description)' not in resp.text
|