406 lines
16 KiB
Python
406 lines
16 KiB
Python
# authentic2 - versatile identity manager
|
|
# Copyright (C) 2010-2019 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/>.
|
|
|
|
from urllib.parse import urlparse
|
|
|
|
import pytest
|
|
from django.urls import reverse
|
|
|
|
from authentic2.a2_rbac.models import Role
|
|
from authentic2.a2_rbac.utils import get_default_ou
|
|
from authentic2.models import APIClient, Attribute
|
|
|
|
from .utils import login
|
|
|
|
|
|
class TestAuthorization:
|
|
@pytest.fixture
|
|
def app(self, app, user):
|
|
login(app, user)
|
|
return app
|
|
|
|
@pytest.fixture
|
|
def api_client(self, db, ou1):
|
|
return APIClient.objects.create(
|
|
name='foo',
|
|
description='foo-description',
|
|
identifier='foo-description',
|
|
password='foo-password',
|
|
ou=ou1,
|
|
)
|
|
|
|
class Mixin:
|
|
status_code = -1
|
|
existing_client_status_code = -1
|
|
|
|
def test_list(self, app):
|
|
app.get(reverse('a2-manager-api-clients'), status=self.status_code)
|
|
|
|
def test_add(self, app):
|
|
app.get(reverse('a2-manager-api-client-add'), status=self.status_code)
|
|
|
|
def test_detail(self, app, api_client):
|
|
app.get(
|
|
reverse('a2-manager-api-client-detail', kwargs={'pk': api_client.pk}),
|
|
status=self.existing_client_status_code,
|
|
)
|
|
|
|
def test_edit(self, app, api_client):
|
|
app.get(
|
|
reverse('a2-manager-api-client-edit', kwargs={'pk': api_client.pk}),
|
|
status=self.existing_client_status_code,
|
|
)
|
|
|
|
def test_delete(self, app, api_client):
|
|
app.get(
|
|
reverse('a2-manager-api-client-delete', kwargs={'pk': api_client.pk}),
|
|
status=self.existing_client_status_code,
|
|
)
|
|
|
|
class TestAuthorization(Mixin):
|
|
status_code = 403
|
|
existing_client_status_code = 404
|
|
|
|
@pytest.fixture
|
|
def user(self, simple_user):
|
|
return simple_user
|
|
|
|
class TestAuthorizationLocalAdminWrongOu(Mixin):
|
|
status_code = 200
|
|
existing_client_status_code = 404
|
|
|
|
@pytest.fixture
|
|
def user(self, admin_ou2):
|
|
return admin_ou2
|
|
|
|
class TestAuthorizationLocalAdminRightOu(Mixin):
|
|
status_code = 200
|
|
existing_client_status_code = 200
|
|
|
|
@pytest.fixture
|
|
def user(self, admin_ou1):
|
|
return admin_ou1
|
|
|
|
class TestAuthorizationAdmin(Mixin):
|
|
status_code = 200
|
|
existing_client_status_code = 200
|
|
|
|
@pytest.fixture
|
|
def user(self, simple_user):
|
|
simple_user.roles.add(Role.objects.get(ou__isnull=True, slug='_a2-manager-of-api-clients'))
|
|
return simple_user
|
|
|
|
|
|
def test_list_empty(superuser, app):
|
|
resp = login(app, superuser, 'a2-manager-api-clients')
|
|
assert 'There are no API client defined.' in resp.text
|
|
|
|
|
|
def test_list_add_button(superuser, app):
|
|
resp = login(app, superuser, 'a2-manager-api-clients')
|
|
anchor = resp.pyquery('span.actions a[href="%s"]' % reverse('a2-manager-api-client-add'))
|
|
assert anchor.text() == 'Add new API client'
|
|
|
|
|
|
def test_list_show_objects(superuser, app):
|
|
api_client = APIClient.objects.create(
|
|
name='foo', description='foo-description', identifier='foo-description', password='foo-password'
|
|
)
|
|
url = '/manage/api-clients/%s/' % api_client.pk
|
|
resp = login(app, superuser, 'a2-manager-api-clients')
|
|
anchor = resp.pyquery('div.content ul.objects-list a[href="%s"]' % url)
|
|
assert anchor.text() == 'foo (foo-description) - Default organizational unit'
|
|
|
|
|
|
def test_list_show_objects_local_admin(admin_ou1, app, ou1, ou2):
|
|
api_client_ou1 = APIClient.objects.create(
|
|
name='foo',
|
|
description='foo-description',
|
|
identifier='foo-description',
|
|
password='foo-password',
|
|
ou=ou1,
|
|
)
|
|
api_client_ou2 = APIClient.objects.create(
|
|
name='bar',
|
|
description='bar-description',
|
|
identifier='bar-description',
|
|
password='bar-password',
|
|
ou=ou2,
|
|
)
|
|
url = '/manage/api-clients/%s/' % api_client_ou1.pk
|
|
resp = login(app, admin_ou1, 'a2-manager-api-clients')
|
|
assert len(resp.pyquery('div.content ul.objects-list li')) == 1
|
|
anchor = resp.pyquery('div.content ul.objects-list a[href="%s"]' % url)
|
|
assert anchor.text() == 'foo (foo-description) - OU1'
|
|
|
|
role = Role.objects.get(slug='_a2-manager-of-api-clients-%s' % ou2.slug)
|
|
admin_ou1.roles.add(role)
|
|
admin_ou1.save()
|
|
resp = app.get(reverse('a2-manager-api-clients'))
|
|
assert len(resp.pyquery('div.content ul.objects-list li')) == 2
|
|
anchor = resp.pyquery('div.content ul.objects-list a[href="%s"]' % url)
|
|
assert anchor.text() == 'foo (foo-description) - OU1'
|
|
url = '/manage/api-clients/%s/' % api_client_ou2.pk
|
|
anchor = resp.pyquery('div.content ul.objects-list a[href="%s"]' % url)
|
|
assert anchor.text() == 'bar (bar-description) - OU2'
|
|
|
|
|
|
def test_add(superuser, app):
|
|
preferred_color = Attribute.objects.create(
|
|
name='preferred_color',
|
|
label='Preferred color',
|
|
kind='string',
|
|
disabled=False,
|
|
multiple=False,
|
|
)
|
|
phone2 = Attribute.objects.create(
|
|
name='phone2',
|
|
label='Second phone number',
|
|
kind='phone_number',
|
|
disabled=False,
|
|
multiple=False,
|
|
)
|
|
assert APIClient.objects.count() == 0
|
|
role_1 = Role.objects.create(name='role-1', ou=get_default_ou())
|
|
role_2 = Role.objects.create(name='role-2', ou=get_default_ou())
|
|
resp = login(app, superuser, 'a2-manager-api-client-add')
|
|
form = resp.form
|
|
# password is prefilled
|
|
assert form.get('password').value
|
|
assert ('', False, '---------') in form['ou'].options
|
|
form.set('name', 'api-client-name')
|
|
form.set('description', 'api-client-description')
|
|
form.set('identifier', 'api-client-identifier')
|
|
form.set('password', 'api-client-password')
|
|
form['apiclient_roles'].force_value([role_1.id, role_2.id])
|
|
form.set('allowed_user_attributes', [preferred_color.id, phone2.id])
|
|
response = form.submit().follow()
|
|
assert APIClient.objects.count() == 1
|
|
api_client = APIClient.objects.get(name='api-client-name')
|
|
assert set(api_client.apiclient_roles.all()) == {role_1, role_2}
|
|
assert set(api_client.allowed_user_attributes.all()) == {preferred_color, phone2}
|
|
assert urlparse(response.request.url).path == api_client.get_absolute_url()
|
|
|
|
|
|
def test_add_local_admin(admin_ou1, app, ou1, ou2):
|
|
assert APIClient.objects.count() == 0
|
|
resp = login(app, admin_ou1, 'a2-manager-api-client-add')
|
|
form = resp.form
|
|
assert len(form['ou'].options) == 1
|
|
assert ('', False, '---------') not in form['ou'].options
|
|
assert form['ou'].options[0][2] == 'OU1'
|
|
|
|
role = Role.objects.get(slug='_a2-manager-of-api-clients-%s' % ou2.slug)
|
|
admin_ou1.roles.add(role)
|
|
resp = app.get(reverse('a2-manager-api-client-add'))
|
|
assert len(resp.form['ou'].options) == 2
|
|
assert ('', False, '---------') not in form['ou'].options
|
|
|
|
|
|
def test_add_description_non_mandatory(superuser, app):
|
|
assert APIClient.objects.count() == 0
|
|
role_1 = Role.objects.create(name='role-1', ou=get_default_ou())
|
|
role_2 = Role.objects.create(name='role-2', ou=get_default_ou())
|
|
resp = login(app, superuser, 'a2-manager-api-client-add')
|
|
form = resp.form
|
|
form.set('name', 'api-client-name')
|
|
form.set('identifier', 'api-client-identifier')
|
|
form.set('password', 'api-client-password')
|
|
form['apiclient_roles'].force_value([role_1.id, role_2.id])
|
|
response = form.submit().follow()
|
|
assert APIClient.objects.count() == 1
|
|
api_client = APIClient.objects.get(name='api-client-name')
|
|
assert set(api_client.apiclient_roles.all()) == {role_1, role_2}
|
|
assert urlparse(response.request.url).path == api_client.get_absolute_url()
|
|
|
|
|
|
def test_detail(superuser, app, phone_activated_authn):
|
|
role_1 = Role.objects.create(name='role-1')
|
|
role_2 = Role.objects.create(name='role-2')
|
|
api_client = APIClient.objects.create(
|
|
name='foo',
|
|
description='foo-description',
|
|
identifier='foo-identifier',
|
|
password='foo-password',
|
|
restrict_to_anonymised_data=True,
|
|
)
|
|
api_client.apiclient_roles.add(role_1, role_2)
|
|
api_client.allowed_user_attributes.add(phone_activated_authn.phone_identifier_field)
|
|
resp = login(app, superuser, api_client.get_absolute_url())
|
|
assert 'foo-description' in resp.text
|
|
assert 'Identifier: foo-identifier' in resp.text
|
|
assert 'Password: foo-password' in resp.text
|
|
assert 'foo-description' in resp.text
|
|
assert 'Restricted to anonymised data' in resp.text
|
|
assert 'role-1' in resp.text
|
|
assert 'role-2' in resp.text
|
|
assert 'Allowed user attributes' in resp.text
|
|
assert 'phone' in resp.text
|
|
|
|
edit_button = resp.pyquery(
|
|
'span.actions a[href="%s"]' % reverse('a2-manager-api-client-edit', kwargs={'pk': api_client.pk})
|
|
)
|
|
assert edit_button
|
|
assert edit_button.text() == 'Edit'
|
|
delete_button = resp.pyquery(
|
|
'span.actions a[href="%s"]' % reverse('a2-manager-api-client-delete', kwargs={'pk': api_client.pk})
|
|
)
|
|
assert delete_button
|
|
assert delete_button.text() == 'Delete'
|
|
|
|
|
|
def test_manager_apiclient_roles_list(app, superuser):
|
|
login(app, superuser, '/')
|
|
|
|
role = Role.objects.create(name='parent', slug='parent', ou=get_default_ou())
|
|
api_client = APIClient.objects.create(name='foo')
|
|
api_client.apiclient_roles.set([role.pk])
|
|
|
|
resp = app.get(reverse('a2-manager-api-client-detail', kwargs={'pk': api_client.pk}))
|
|
|
|
assert 'href="%s"' % (reverse('a2-manager-role-members', kwargs={'pk': role.pk})) in resp.text
|
|
|
|
|
|
def test_edit(superuser, app, ou1, ou2):
|
|
preferred_color = Attribute.objects.create(
|
|
name='preferred_color',
|
|
label='Preferred color',
|
|
kind='string',
|
|
disabled=False,
|
|
multiple=False,
|
|
)
|
|
phone2 = Attribute.objects.create(
|
|
name='phone2',
|
|
label='Second phone number',
|
|
kind='phone_number',
|
|
disabled=False,
|
|
multiple=False,
|
|
)
|
|
role_1 = Role.objects.create(name='role-1', ou=ou1)
|
|
role_2 = Role.objects.create(name='role-2', ou=ou2)
|
|
role_3 = Role.objects.create(name='role-3', ou=ou1)
|
|
api_client = APIClient.objects.create(
|
|
name='foo',
|
|
description='foo-description',
|
|
identifier='foo-identifier',
|
|
password='foo-password',
|
|
ou=ou1,
|
|
)
|
|
api_client.allowed_user_attributes.add(preferred_color, phone2)
|
|
api_client.save()
|
|
assert APIClient.objects.count() == 1
|
|
resp = login(app, superuser, 'a2-manager-api-client-edit', kwargs={'pk': api_client.pk})
|
|
form = resp.form
|
|
assert form.get('password').value == 'foo-password'
|
|
assert set(form.get('allowed_user_attributes').value) == {str(preferred_color.id), str(phone2.id)}
|
|
assert ('', False, '---------') in form['ou'].options
|
|
resp.form.set('password', 'easy')
|
|
with pytest.raises(KeyError):
|
|
# forcing values not presented by the Select2ModelMultipleChoiceField,
|
|
# should not happen in UI
|
|
form['apiclient_roles'].force_value([role_1.id, role_2.id])
|
|
form.submit()
|
|
form['apiclient_roles'].force_value([role_1.id, role_3.id])
|
|
form['allowed_user_attributes'].force_value([phone2.id])
|
|
response = form.submit().follow()
|
|
assert urlparse(response.request.url).path == api_client.get_absolute_url()
|
|
assert APIClient.objects.count() == 1
|
|
api_client = APIClient.objects.get(password='easy')
|
|
assert api_client.identifier == 'foo-identifier'
|
|
assert set(api_client.allowed_user_attributes.all()) == {phone2}
|
|
|
|
resp = app.get(reverse('a2-manager-api-client-edit', kwargs={'pk': api_client.pk}))
|
|
form = resp.form
|
|
form.set('ou', ou2.id)
|
|
response = form.submit()
|
|
assert (
|
|
response.pyquery('.error').text()
|
|
== 'The following roles do not belong to organizational unit OU2: role-1, role-3.'
|
|
)
|
|
response.form.set('ou', ou2.id)
|
|
response.form['apiclient_roles'].force_value([])
|
|
response.form['allowed_user_attributes'].force_value([])
|
|
response.form.submit().follow()
|
|
api_client = APIClient.objects.get()
|
|
assert set(api_client.apiclient_roles.all()) == set()
|
|
assert set(api_client.allowed_user_attributes.all()) == set()
|
|
assert api_client.ou == ou2
|
|
|
|
resp = app.get(reverse('a2-manager-api-client-edit', kwargs={'pk': api_client.pk}))
|
|
form = resp.form
|
|
form['apiclient_roles'].force_value([role_2.id])
|
|
form['allowed_user_attributes'].force_value([preferred_color.id])
|
|
response = form.submit().follow()
|
|
api_client = APIClient.objects.get()
|
|
assert api_client.ou == ou2
|
|
assert set(api_client.apiclient_roles.all()) == {role_2}
|
|
assert set(api_client.allowed_user_attributes.all()) == {preferred_color}
|
|
|
|
|
|
def test_edit_local_admin(admin_ou1, app, ou1, ou2):
|
|
role_1 = Role.objects.create(name='role-1', ou=ou1)
|
|
role_2 = Role.objects.create(name='role-2', ou=ou2)
|
|
role_3 = Role.objects.create(name='role-3', ou=ou1)
|
|
api_client_ou1 = APIClient.objects.create(
|
|
name='foo',
|
|
description='foo-description',
|
|
identifier='foo-description',
|
|
password='foo-password',
|
|
ou=ou1,
|
|
)
|
|
api_client_ou2 = APIClient.objects.create(
|
|
name='bar',
|
|
description='bar-description',
|
|
identifier='bar-description',
|
|
password='bar-password',
|
|
ou=ou2,
|
|
)
|
|
resp = login(app, admin_ou1, 'a2-manager-api-client-edit', kwargs={'pk': api_client_ou1.pk})
|
|
form = resp.form
|
|
assert form.get('password').value == 'foo-password'
|
|
resp.form.set('password', 'easy')
|
|
assert ('', False, '---------') not in form['ou'].options
|
|
with pytest.raises(KeyError):
|
|
# forcing values not presented by the Select2ModelMultipleChoiceField,
|
|
# should not happen in UI
|
|
form['apiclient_roles'].force_value([role_1.id, role_2.id])
|
|
form.submit()
|
|
form['apiclient_roles'].force_value([role_1.id, role_3.id])
|
|
response = form.submit().follow()
|
|
assert urlparse(response.request.url).path == api_client_ou1.get_absolute_url()
|
|
api_client = APIClient.objects.get(password='easy')
|
|
assert api_client.identifier == 'foo-description'
|
|
|
|
role = Role.objects.get(slug='_a2-manager-of-api-clients-%s' % ou2.slug)
|
|
admin_ou1.roles.add(role)
|
|
resp = app.get(reverse('a2-manager-api-client-edit', kwargs={'pk': api_client_ou2.pk}))
|
|
assert resp.form.get('password').value == 'bar-password'
|
|
assert ('', False, '---------') not in form['ou'].options
|
|
resp.form.set('ou', ou1.id)
|
|
resp.form.submit().follow()
|
|
assert APIClient.objects.filter(ou=ou1).count() == 2
|
|
|
|
|
|
def test_delete(superuser, app):
|
|
api_client = APIClient.objects.create(
|
|
name='foo', description='foo-description', identifier='foo-identifier', password='foo-password'
|
|
)
|
|
assert APIClient.objects.count() == 1
|
|
resp = login(app, superuser, 'a2-manager-api-client-delete', kwargs={'pk': api_client.pk})
|
|
response = resp.form.submit().follow()
|
|
assert urlparse(response.request.url).path == reverse('a2-manager-api-clients')
|
|
assert APIClient.objects.count() == 0
|