authentic/tests/test_manager_apiclient.py

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