authentic/tests/api/test_all.py

2819 lines
106 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/>.
import datetime
import json
from unittest import mock
import django
import pytest
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import check_password
from django.core import mail
from django.urls import reverse
from django.utils.encoding import force_str
from django.utils.text import slugify
from requests.models import Response
from authentic2.a2_rbac.models import OrganizationalUnit as OU
from authentic2.a2_rbac.models import Role
from authentic2.a2_rbac.utils import get_default_ou
from authentic2.apps.journal.models import Event, EventType
from authentic2.custom_user.models import Profile, ProfileType
from authentic2.models import APIClient, Attribute, AttributeValue, AuthorizedRole, PasswordReset, Service
from authentic2.utils.misc import good_next_url
from authentic2_idp_cas.models import Service as CASService
from ..utils import assert_event, basic_authorization_header, get_link_from_mail, login
pytestmark = pytest.mark.django_db
User = get_user_model()
USER_ATTRIBUTES_SET = {
'ou',
'id',
'uuid',
'is_staff',
'is_superuser',
'first_name',
'first_name_verified',
'last_name',
'last_name_verified',
'date_joined',
'last_login',
'username',
'password',
'email',
'is_active',
'modified',
'email_verified',
'email_verified_date',
'phone',
'phone_verified_on',
'last_account_deletion_alert',
'deactivation',
'deactivation_reason',
}
def test_api_user_simple(logged_app):
resp = logged_app.get('/api/user/')
assert isinstance(resp.json, dict)
assert 'username' in resp.json
def test_api_user(client):
# create an user, an ou role, a service and a service role
ou = get_default_ou()
Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=True)
user = User.objects.create(
ou=ou, username='john.doe', first_name='Jôhn', last_name='Doe', email='john.doe@example.net'
)
user.attributes.birthdate = datetime.date(2019, 2, 2)
user.set_password('password')
user.save()
role1 = Role.objects.create(name='Role1', ou=ou)
role1.members.add(user)
service = Service.objects.create(name='Service1', slug='service1', ou=ou)
role2 = Role.objects.create(name='Role2', service=service)
role2.members.add(user)
Role.objects.create(name='Role3', ou=ou)
Role.objects.create(name='Role4', service=service)
# test failure when unlogged
response = client.get('/api/user/', HTTP_ORIGIN='http://testserver')
assert response.content == b'{}'
# login
client.login(request=None, username='john.doe', password='password')
response = client.get('/api/user/', HTTP_ORIGIN='http://testserver')
data = json.loads(force_str(response.content))
assert isinstance(data, dict)
assert set(data.keys()) == {
'uuid',
'username',
'first_name',
'ou__slug',
'ou__uuid',
'ou__name',
'last_name',
'email',
'roles',
'services',
'is_superuser',
'ou',
'birthdate',
}
assert data['uuid'] == user.uuid
assert data['username'] == user.username
assert data['first_name'] == user.first_name
assert data['last_name'] == user.last_name
assert data['email'] == user.email
assert data['is_superuser'] == user.is_superuser
assert data['ou'] == ou.name
assert data['ou__name'] == ou.name
assert data['ou__slug'] == ou.slug
assert data['ou__uuid'] == ou.uuid
assert data['birthdate'] == '2019-02-02'
assert isinstance(data['roles'], list)
assert len(data['roles']) == 2
for role in data['roles']:
assert set(role.keys()) == {
'uuid',
'name',
'slug',
'is_admin',
'is_service',
'ou__uuid',
'ou__name',
'ou__slug',
}
assert (
role['uuid'] == role1.uuid
and role['name'] == role1.name
and role['slug'] == role1.slug
and role['is_admin'] is False
and role['is_service'] is False
and role['ou__uuid'] == ou.uuid
and role['ou__name'] == ou.name
and role['ou__slug'] == ou.slug
) or (
role['uuid'] == role2.uuid
and role['name'] == role2.name
and role['slug'] == role2.slug
and role['is_admin'] is False
and role['is_service'] is True
and role['ou__uuid'] == ou.uuid
and role['ou__name'] == ou.name
and role['ou__slug'] == ou.slug
)
assert isinstance(data['services'], list)
assert len(data['services']) == 1
s = data['services'][0]
assert set(s.keys()) == {'name', 'slug', 'ou', 'ou__name', 'ou__slug', 'ou__uuid', 'roles'}
assert s['name'] == service.name
assert s['slug'] == service.slug
assert s['ou'] == ou.name
assert s['ou__name'] == ou.name
assert s['ou__slug'] == ou.slug
assert s['ou__uuid'] == ou.uuid
assert isinstance(s['roles'], list)
assert len(s['roles']) == 2
for role in s['roles']:
assert set(role.keys()) == {
'uuid',
'name',
'slug',
'is_admin',
'is_service',
'ou__uuid',
'ou__name',
'ou__slug',
}
assert (
role['uuid'] == role1.uuid
and role['name'] == role1.name
and role['slug'] == role1.slug
and role['is_admin'] is False
and role['is_service'] is False
and role['ou__uuid'] == ou.uuid
and role['ou__name'] == ou.name
and role['ou__slug'] == ou.slug
) or (
role['uuid'] == role2.uuid
and role['name'] == role2.name
and role['slug'] == role2.slug
and role['is_admin'] is False
and role['is_service'] is True
and role['ou__uuid'] == ou.uuid
and role['ou__name'] == ou.name
and role['ou__slug'] == ou.slug
)
def test_api_users_list(app, user):
app.authorization = ('Basic', (user.username, user.username))
resp = app.get('/api/users/')
assert isinstance(resp.json, dict)
assert {'previous', 'next', 'results'} == set(resp.json.keys())
assert resp.json['previous'] is None
assert resp.json['next'] is None
def test_api_member_users_list(app, user, simple_role):
app.authorization = ('Basic', (user.username, user.username))
url = reverse('a2-api-role-members-list', kwargs={'role_uuid': simple_role.uuid})
simple_role.members.add(user)
resp = app.get(url)
assert isinstance(resp.json, dict)
assert {'previous', 'next', 'results'} == set(resp.json.keys())
assert resp.json['previous'] is None
assert resp.json['next'] is None
def test_api_users_update_with_email_verified(settings, app, admin, simple_user):
simple_user.set_email_verified(True)
simple_user.save()
payload = {
'username': simple_user.username,
'id': simple_user.id,
'email': 'john.doe@nowhere.null',
'first_name': 'Johnny',
'last_name': 'Doeny',
'email_verified': True,
}
headers = basic_authorization_header(admin)
resp = app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
user = User.objects.get(id=simple_user.id)
assert user.email_verified
assert resp.json['email_verified']
assert_event('manager.user.profile.edit', user=admin, api=True)
user.set_email_verified(True)
user.email = 'johnny.doeny@foo.bar'
user.save()
resp = app.patch_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
user = User.objects.get(id=simple_user.id)
assert user.email_verified
assert resp.json['email_verified']
def test_api_users_update_without_email_verified(settings, app, admin, simple_user):
simple_user.set_email_verified(True)
simple_user.save()
payload = {
'username': simple_user.username,
'id': simple_user.id,
'email': 'john.doe@nowhere.null',
'first_name': 'Johnny',
'last_name': 'Doeny',
}
headers = basic_authorization_header(admin)
resp = app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
user = User.objects.get(id=simple_user.id)
assert not user.email_verified
assert not resp.json['email_verified']
user.set_email_verified(True)
user.email = 'johnny.doeny@foo.bar'
user.save()
resp = app.patch_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
user = User.objects.get(id=simple_user.id)
assert not user.email_verified
assert not resp.json['email_verified']
def test_api_users_update_with_same_unique_email(settings, app, admin, simple_user):
ou = get_default_ou()
ou.email_is_unique = True
ou.save()
payload = {
'username': simple_user.username,
'id': simple_user.id,
'email': 'john.doe@nowhere.null',
'first_name': 'Johnny',
'last_name': 'Doeny',
}
headers = basic_authorization_header(admin)
app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
User.objects.get(id=simple_user.id)
app.patch_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
def test_api_users_create_with_email_verified(settings, app, admin):
payload = {
'username': 'janedoe',
'email': 'jane.doe@nowhere.null',
'first_name': 'Jane',
'last_name': 'Doe',
'email_verified': True,
}
headers = basic_authorization_header(admin)
resp = app.post_json('/api/users/', headers=headers, params=payload, status=201)
assert resp.json['email_verified']
user = User.objects.get(uuid=resp.json['uuid'])
assert user.email_verified
assert_event('manager.user.creation', user=admin, api=True)
def test_api_users_create_without_email_verified(settings, app, admin):
payload = {
'username': 'janedoe',
'email': 'jane.doe@nowhere.null',
'first_name': 'Jane',
'last_name': 'Doe',
}
headers = basic_authorization_header(admin)
resp = app.post_json('/api/users/', headers=headers, params=payload, status=201)
assert not resp.json['email_verified']
user = User.objects.get(uuid=resp.json['uuid'])
assert not user.email_verified
def test_api_email_unset_verification(settings, app, admin, simple_user):
simple_user.set_email_verified(True)
simple_user.save()
payload = {
'email': 'john.doe@nowhere.null',
}
headers = basic_authorization_header(admin)
app.post_json(f'/api/users/{simple_user.uuid}/email/', params=payload, headers=headers, status=200)
user = User.objects.get(id=simple_user.id)
assert not user.email_verified
def test_api_users_boolean_attribute(app, superuser):
Attribute.objects.create(kind='boolean', name='boolean', label='boolean', required=True)
superuser.attributes.boolean = True
app.authorization = ('Basic', (superuser.username, superuser.username))
resp = app.get('/api/users/%s/' % superuser.uuid)
assert resp.json['boolean'] is True
def test_api_users_boolean_attribute_optional(app, superuser):
Attribute.objects.create(kind='boolean', name='boolean', label='boolean', required=False)
superuser.attributes.boolean = True
app.authorization = ('Basic', (superuser.username, superuser.username))
resp = app.get('/api/users/%s/' % superuser.uuid)
assert resp.json['boolean'] is True
def test_api_users_list_by_authorized_service(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
user1 = User.objects.create(username='user1')
user2 = User.objects.create(username='user2')
User.objects.create(username='user3')
role1 = Role.objects.create(name='role1')
role2 = Role.objects.create(name='role2')
role1.add_child(role2)
user1.roles.set([role1])
user2.roles.set([role2])
service1 = Service.objects.create(ou=get_default_ou(), name='service1', slug='service1')
service1.add_authorized_role(role1)
Service.objects.create(ou=get_default_ou(), name='service2', slug='service2')
resp = app.get('/api/users/')
assert len(resp.json['results']) == 4
resp = app.get('/api/users/?service-ou=default&service-slug=service1')
assert len(resp.json['results']) == 2
assert {user['username'] for user in resp.json['results']} == {'user1', 'user2'}
resp = app.get('/api/users/?service-ou=default&service-slug=service2')
assert len(resp.json['results']) == 4
def test_api_member_users_list_by_authorized_service(app, superuser, simple_role):
app.authorization = ('Basic', (superuser.username, superuser.username))
url = reverse('a2-api-role-members-list', kwargs={'role_uuid': simple_role.uuid})
user1 = User.objects.create(username='user1')
user2 = User.objects.create(username='user2')
user3 = User.objects.create(username='user3')
role1 = Role.objects.create(name='role1')
role2 = Role.objects.create(name='role2')
role1.add_child(role2)
user1.roles.set([role1])
user2.roles.set([role2])
service1 = Service.objects.create(ou=get_default_ou(), name='service1', slug='service1')
service1.add_authorized_role(role1)
Service.objects.create(ou=get_default_ou(), name='service2', slug='service2')
for user in superuser, user1, user2, user3:
simple_role.members.add(user)
resp = app.get(url)
assert len(resp.json['results']) == 4
resp = app.get(url + '?service-ou=default&service-slug=service1')
assert len(resp.json['results']) == 2
assert {user['username'] for user in resp.json['results']} == {'user1', 'user2'}
resp = app.get(url + '?service-ou=default&service-slug=service2')
assert len(resp.json['results']) == 4
def test_api_users_list_search_text(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
User = get_user_model()
someuser = User.objects.create(username='someuser')
resp = app.get('/api/users/?q=some')
results = resp.json['results']
assert len(results) == 1
assert results[0]['username'] == 'someuser'
someuser.delete()
resp = app.get('/api/users/?q=some')
results = resp.json['results']
assert len(results) == 0
def test_api_member_users_list_search_text(app, superuser, simple_role):
app.authorization = ('Basic', (superuser.username, superuser.username))
url = reverse('a2-api-role-members-list', kwargs={'role_uuid': simple_role.uuid})
User = get_user_model()
someuser = User.objects.create(username='someuser')
simple_role.members.add(someuser)
resp = app.get(url + '?q=some')
results = resp.json['results']
assert len(results) == 1
assert results[0]['username'] == 'someuser'
someuser.delete()
resp = app.get(url + '?q=some')
results = resp.json['results']
assert len(results) == 0
def test_api_users_create(settings, app, api_user):
at = Attribute.objects.create(kind='title', name='title', label='title')
app.authorization = ('Basic', (api_user.username, api_user.username))
# test missing first_name
payload = {
'username': 'john.doe',
'email': 'john.doe@example.net',
}
if api_user.roles.exists():
status = 400
payload['ou'] = api_user.ou.slug
resp = app.post_json('/api/users/', params=payload, status=400)
assert resp.json['result'] == 0
assert {'first_name', 'last_name'} == set(resp.json['errors'])
settings.A2_API_USERS_REQUIRED_FIELDS = ['email']
if api_user.is_superuser or hasattr(api_user, 'oidc_client') or api_user.roles.exists():
status = 201
else:
status = 403
resp = app.post_json('/api/users/', params=payload, status=status)
if status == 201:
assert resp.json
del settings.A2_API_USERS_REQUIRED_FIELDS
payload = {
'username': 'john.doe',
'first_name': 'John',
'last_name': 'Doe',
'email': 'john.doe@example.net',
'password': 'password',
'title': 'Mr',
}
if api_user.is_superuser:
status = 201
elif api_user.roles.exists():
status = 201
payload['ou'] = api_user.ou.slug
else:
status = 403
resp = app.post_json('/api/users/', params=payload, status=status)
if api_user.is_superuser or api_user.roles.exists():
assert (USER_ATTRIBUTES_SET | {'title', 'title_verified'}) == set(resp.json)
assert resp.json['first_name'] == payload['first_name']
assert resp.json['last_name'] == payload['last_name']
assert resp.json['email'] == payload['email']
assert resp.json['username'] == payload['username']
assert resp.json['title'] == payload['title']
assert resp.json['uuid']
assert resp.json['id']
assert resp.json['date_joined']
assert not resp.json['first_name_verified']
assert not resp.json['last_name_verified']
assert not resp.json['title_verified']
if api_user.is_superuser:
assert resp.json['ou'] == 'default'
elif api_user.roles.exists():
assert resp.json['ou'] == api_user.ou.slug
new_user = get_user_model().objects.get(id=resp.json['id'])
assert new_user.uuid == resp.json['uuid']
assert new_user.username == resp.json['username']
assert new_user.email == resp.json['email']
assert new_user.first_name == resp.json['first_name']
assert new_user.last_name == resp.json['last_name']
assert AttributeValue.objects.with_owner(new_user).count() == 3
assert AttributeValue.objects.with_owner(new_user).filter(verified=True).count() == 0
assert AttributeValue.objects.with_owner(new_user).filter(attribute=at).exists()
assert AttributeValue.objects.with_owner(new_user).get(attribute=at).content == payload['title']
resp2 = app.get('/api/users/%s/' % resp.json['uuid'])
assert resp.json == resp2.json
payload.update({'uuid': '1234567890', 'email': 'foo@example.com', 'username': 'foobar'})
resp = app.post_json('/api/users/', params=payload, status=status)
assert resp.json['uuid'] == '1234567890'
assert 'title' in resp.json
at.disabled = True
at.save()
resp = app.get('/api/users/1234567890/')
assert 'title' not in resp.json
at.disabled = False
at.save()
payload = {
'username': 'john.doe2',
'first_name': 'John',
'first_name_verified': True,
'last_name': 'Doe',
'last_name_verified': True,
'email': 'john.doe@example.net',
'password': 'password',
'title': 'Mr',
'title_verified': True,
}
if api_user.is_superuser:
status = 201
elif api_user.roles.exists():
status = 201
payload['ou'] = api_user.ou.slug
else:
status = 403
resp = app.post_json('/api/users/', params=payload, status=status)
if api_user.is_superuser or api_user.roles.exists():
assert (USER_ATTRIBUTES_SET | {'title', 'title_verified'}) == set(resp.json)
user = get_user_model().objects.get(pk=resp.json['id'])
assert AttributeValue.objects.with_owner(user).filter(verified=True).count() == 3
assert AttributeValue.objects.with_owner(user).filter(verified=False).count() == 0
assert user.verified_attributes.first_name == 'John'
assert user.verified_attributes.last_name == 'Doe'
assert user.verified_attributes.title == 'Mr'
assert resp.json['first_name_verified']
assert resp.json['last_name_verified']
assert resp.json['title_verified']
resp2 = app.patch_json('/api/users/%s/' % resp.json['uuid'], params={'title_verified': False})
assert resp.json['first_name_verified']
assert resp.json['last_name_verified']
assert not resp2.json['title_verified']
def test_api_users_create_email_is_unique(settings, app, superuser):
ou1 = OU.objects.create(name='OU1', slug='ou1')
ou2 = OU.objects.create(name='OU2', slug='ou2', email_is_unique=True)
app.authorization = ('Basic', (superuser.username, superuser.username))
# test missing first_name
payload = {
'ou': 'ou1',
'first_name': 'John',
'last_name': 'Doe',
'email': 'john.doe@example.net',
}
assert User.objects.filter(ou=ou1).count() == 0
assert User.objects.filter(ou=ou2).count() == 0
app.post_json('/api/users/', params=payload)
assert User.objects.filter(ou=ou1).count() == 1
app.post_json('/api/users/', params=payload)
assert User.objects.filter(ou=ou1).count() == 2
payload['ou'] = 'ou2'
app.post_json('/api/users/', params=payload)
assert User.objects.filter(ou=ou2).count() == 1
resp = app.post_json('/api/users/', params=payload, status=400)
assert User.objects.filter(ou=ou2).count() == 1
assert resp.json['result'] == 0
assert resp.json['errors']['email']
settings.A2_EMAIL_IS_UNIQUE = True
User.objects.filter(ou=ou1).delete()
assert User.objects.filter(ou=ou1).count() == 0
payload['ou'] = 'ou1'
app.post_json('/api/users/', params=payload, status=400)
assert User.objects.filter(ou=ou1).count() == 0
assert resp.json['result'] == 0
assert resp.json['errors']['email']
payload['email'] = 'john.doe2@example.net'
resp = app.post_json('/api/users/', params=payload)
uuid = resp.json['uuid']
app.patch_json('/api/users/%s/' % uuid, params={'email': 'john.doe3@example.net'})
resp = app.patch_json('/api/users/%s/' % uuid, params={'email': 'john.doe@example.net'}, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['email']
settings.A2_EMAIL_IS_UNIQUE = False
payload['ou'] = 'ou2'
payload['email'] = 'john.doe2@example.net'
resp = app.post_json('/api/users/', params=payload)
assert User.objects.filter(ou=ou2).count() == 2
uuid = resp.json['uuid']
resp = app.patch_json('/api/users/%s/' % uuid, params={'email': 'john.doe@example.net'}, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['email']
def test_api_users_create_send_mail(app, settings, superuser, rf):
# Use case is often that Email is the main identifier
settings.A2_EMAIL_IS_UNIQUE = True
Attribute.objects.create(kind='title', name='title', label='title')
app.authorization = ('Basic', (superuser.username, superuser.username))
payload = {
'username': 'john.doe',
'first_name': 'John',
'last_name': 'Doe',
'email': 'john.doe@example.net',
'title': 'Mr',
'send_registration_email': True,
'send_registration_email_next_url': 'http://example.com/',
}
assert len(mail.outbox) == 0
resp = app.post_json('/api/users/', params=payload, status=201)
user_id = resp.json['id']
assert len(mail.outbox) == 1
# Follow activation link
url = get_link_from_mail(mail.outbox[0])
relative_url = url.split('testserver')[1]
resp = app.get(relative_url, status=200)
resp.form.set('new_password1', '1234==aA')
resp.form.set('new_password2', '1234==aA')
resp = resp.form.submit()
# Check user was properly logged in
assert str(app.session['_auth_user_id']) == str(user_id)
assert not good_next_url(rf.get('/'), 'http://example.com')
assert resp.location == 'http://example.com/'
def test_api_users_create_force_password_reset(app, client, settings, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
payload = {
'username': 'john.doe',
'first_name': 'John',
'last_name': 'Doe',
'email': 'john.doe@example.net',
'password': '1234',
'force_password_reset': True,
}
app.post_json('/api/users/', params=payload, status=201)
# Verify password reset is enforced on next login
resp = login(app, 'john.doe', path='/', password='1234').follow()
resp.form.set('old_password', '1234')
resp.form.set('new_password1', '1234==aB')
resp.form.set('new_password2', '1234==aB')
resp = resp.form.submit('Submit').follow().maybe_follow()
assert 'Password changed' in resp
def test_api_role_get_member(app, api_user, role, member):
app.authorization = ('Basic', (api_user.username, api_user.username))
authorized = api_user.has_perm('a2_rbac.view_role', role)
if member.username == 'fake' or role.name == 'fake':
status = 404
elif authorized:
status = 404
else:
status = 403
resp = app.get(f'/api/roles/{role.uuid}/members/{member.uuid}/', status=status)
if member.username == 'fake' or role.name == 'fake':
assert resp.json == {'result': 0, 'errors': {'detail': 'Not found.'}}
elif status == 404:
assert resp.json == {'result': 0, 'errors': {'detail': 'Not found.'}}
member.roles.add(role)
resp = app.get(f'/api/roles/{role.uuid}/members/{member.uuid}/')
assert resp.json['uuid'] == member.uuid
assert USER_ATTRIBUTES_SET == set(resp.json)
else:
assert resp.json['result'] == 0
assert resp.json['errors'] == 'User not allowed to view role'
def test_api_role_get_member_nested(app, admin_ou1, user_ou1, role_ou1, role_random):
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
url = reverse(
'a2-api-role-member',
kwargs={
'role_uuid': role_ou1.uuid,
'member_uuid': admin_ou1.uuid,
},
)
Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=True)
user_ou1.attributes.birthdate = datetime.date(2019, 2, 2)
user_ou1.save()
# admin.ou1 is an inherited member of role_ou1
role_random.members.add(admin_ou1)
role_random.add_parent(role_ou1)
assert admin_ou1 not in role_ou1.members.all()
assert admin_ou1 in role_ou1.all_members()
# default api call without nested users
resp = app.get(url, status=404)
assert resp.json == {'result': 0, 'errors': {'detail': 'Not found.'}}
# api call with nested users
resp = app.get(url, params={'nested': 'true'})
assert resp.json['username'] == 'admin.ou1'
assert USER_ATTRIBUTES_SET | {'birthdate', 'birthdate_verified'} == set(resp.json)
def test_api_role_add_member(app, api_user, role, member):
app.authorization = ('Basic', (api_user.username, api_user.username))
authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
if member.username == 'fake' or role.name == 'fake':
status = 404
elif authorized:
status = 201
else:
status = 403
resp = app.post_json(f'/api/roles/{role.uuid}/members/{member.uuid}/', status=status)
if status == 404:
pass
elif authorized:
assert resp.json['result'] == 1
assert resp.json['detail'] == 'User successfully added to role'
assert_event(
'manager.role.membership.grant',
user=api_user if isinstance(api_user, User) else None,
api=True,
role_name=role.name,
member_name=member.get_full_name(),
)
else:
assert resp.json['result'] == 0
assert resp.json['errors'] == 'User not allowed to manage role members'
def test_api_role_remove_member(app, api_user, role, member):
app.authorization = ('Basic', (api_user.username, api_user.username))
authorized = api_user.is_superuser or api_user.has_perm('a2_rbac.admin_role', role)
if member.username == 'fake' or role.name == 'fake':
status = 404
elif authorized:
status = 200
else:
status = 403
resp = app.delete_json(f'/api/roles/{role.uuid}/members/{member.uuid}/', status=status)
if status == 404:
pass
elif authorized:
assert resp.json['result'] == 1
assert resp.json['detail'] == 'User successfully removed from role'
resp = app.get(f'/api/roles/{role.uuid}/members/{member.uuid}/', status=404)
assert resp.json == {'result': 0, 'errors': {'detail': 'Not found.'}}
assert_event(
'manager.role.membership.removal',
user=api_user if isinstance(api_user, User) else None,
api=True,
role_name=role.name,
member_name=member.get_full_name(),
)
else:
assert resp.json['result'] == 0
assert resp.json['errors'] == 'User not allowed to manage role members'
def test_api_role_add_members(app, api_user, role, member, member_rando2):
app.authorization = ('Basic', (api_user.username, api_user.username))
authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
if role.name == 'fake':
status = 404
elif not authorized:
status = 403
elif member.username == 'fake':
status = 400
else:
status = 201
payload = {"data": []}
for m in [member, member_rando2, member_rando2]: # test no duplicate
payload['data'].append({"uuid": m.uuid})
resp = app.post_json(f'/api/roles/{role.uuid}/relationships/members/', params=payload, status=status)
if status in (400, 404):
pass
elif authorized:
assert resp.json['result'] == 1
assert resp.json['detail'] == 'Users successfully added to role'
for m in [member, member_rando2]:
assert m in role.members.all()
assert_event(
'manager.role.membership.grant',
user=api_user if isinstance(api_user, User) else None,
api=True,
role_name=role.name,
member_name=m.get_full_name(),
)
else:
assert resp.json['result'] == 0
assert resp.json['errors'] == 'User not allowed to manage role members'
def test_api_role_remove_members(app, api_user, role, member, member_rando2):
app.authorization = ('Basic', (api_user.username, api_user.username))
authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
if role.name == 'fake':
status = 404
elif not authorized:
status = 403
elif member.username == 'fake':
status = 400
else:
status = 200
payload = {"data": []}
for m in [member, member_rando2, member_rando2]: # test no duplicate
payload['data'].append({"uuid": m.uuid})
resp = app.delete_json(f'/api/roles/{role.uuid}/relationships/members/', params=payload, status=status)
if status in (400, 404):
pass
elif authorized:
assert resp.json['result'] == 1
assert resp.json['detail'] == 'Users successfully removed from role'
for m in [member, member_rando2]:
assert m not in role.members.all()
assert_event(
'manager.role.membership.removal',
user=api_user if isinstance(api_user, User) else None,
api=True,
role_name=role.name,
member_name=m.get_full_name(),
)
else:
assert resp.json['result'] == 0
assert resp.json['errors'] == 'User not allowed to manage role members'
def test_api_role_set_members(app, api_user, role, member, member_rando2, ou_rando):
user = User.objects.create(
username='test3', first_name='test3', last_name='test3', email='test3@test.org', ou=ou_rando
)
app.authorization = ('Basic', (api_user.username, api_user.username))
authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
if role.name == 'fake':
status = 404
elif not authorized:
status = 403
elif member.username == 'fake':
status = 400
else:
status = 200
payload = {"data": []}
role.members.add(user)
for m in [member, member_rando2, member_rando2]: # test no duplicate
payload['data'].append({"uuid": m.uuid})
resp = app.put_json(f'/api/roles/{role.uuid}/relationships/members/', params=payload, status=status)
if status in (400, 404):
pass
elif authorized:
assert resp.json['result'] == 1
assert resp.json['detail'] == 'Users successfully assigned to role'
assert len(role.members.all()) == 2
for m in [member, member_rando2]:
assert m in role.members.all()
assert_event(
'manager.role.membership.grant',
user=api_user if isinstance(api_user, User) else None,
api=True,
role_name=role.name,
member_name=m.get_full_name(),
)
assert_event(
'manager.role.membership.removal',
user=api_user if isinstance(api_user, User) else None,
api=True,
role_name=role.name,
member_name=user.get_full_name(),
)
else:
assert resp.json['result'] == 0
assert resp.json['errors'] == 'User not allowed to manage role members'
def test_api_role_set_empty_members(app, api_user):
app.authorization = ('Basic', (api_user.username, api_user.username))
ou = get_default_ou()
user = User.objects.create(
ou=ou, username='john.doe', first_name='Jôhn', last_name='Doe', email='john.doe@example.net'
)
user.save()
role = Role.objects.create(name='Role1', ou=ou)
role.members.add(user)
status = 200
if not api_user.has_perm('a2_rbac.manage_members_role', role):
status = 403
app.put_json(f'/api/roles/{role.uuid}/relationships/members/', params={'data': []}, status=status)
if api_user.has_perm('a2_rbac.manage_members_role', role):
assert len(role.members.all()) == 0
else:
assert len(role.members.all()) == 1
def test_api_role_get_members(app, api_user, role):
app.authorization = ('Basic', (api_user.username, api_user.username))
authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
status = 405 if authorized else 403
app.get(f'/api/roles/{role.uuid}/relationships/members/', status=status)
def test_api_role_members_payload_missing(app, api_user, role):
app.authorization = ('Basic', (api_user.username, api_user.username))
authorized = api_user.has_perm('a2_rbac.manage_members_role', role)
status = 400 if authorized else 403
app.post_json(f'/api/roles/{role.uuid}/relationships/members/', status=status)
app.delete_json(f'/api/roles/{role.uuid}/relationships/members/', status=status)
app.put_json(f'/api/roles/{role.uuid}/relationships/members/', status=status)
def test_api_role_members_wrong_payload_types(app, superuser, role_random, member_rando2):
app.authorization = ('Basic', (superuser.username, superuser.username))
payload = [{"data": [{'uuid': member_rando2.uuid}]}]
resp = app.post_json(f'/api/roles/{role_random.uuid}/relationships/members/', params=payload, status=400)
assert resp.json['result'] == 0
assert resp.json['errors'] == ['Payload must be a dictionary']
payload = {"data": [[member_rando2.uuid]]}
resp = app.post_json(f'/api/roles/{role_random.uuid}/relationships/members/', params=payload, status=400)
assert resp.json['result'] == 0
assert resp.json['errors'] == ["List elements of the 'data' dict entry must be dictionaries"]
payload = {"data": [member_rando2.uuid]}
resp = app.post_json(f'/api/roles/{role_random.uuid}/relationships/members/', params=payload, status=400)
assert resp.json['result'] == 0
assert resp.json['errors'] == ["List elements of the 'data' dict entry must be dictionaries"]
def test_register_no_email_validation(settings, app, admin, django_user_model):
settings.A2_USERNAME_IS_UNIQUE = False
settings.A2_REGISTRATION_USERNAME_IS_UNIQUE = False
User = django_user_model
password = '12XYab'
username = 'john.doe'
email = 'john.doe@example.com'
first_name = 'John'
last_name = 'Doe'
return_url = 'http://sp.example.com/validate/'
# invalid payload
payload = {
'last_name': last_name,
'return_url': return_url,
}
headers = basic_authorization_header(admin)
assert len(mail.outbox) == 0
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=400)
assert 'errors' in response.json
assert response.json['result'] == 0
assert response.json['errors'] == {
'__all__': ['You must set at least a username, an email or a first name and a last name'],
}
# valid payload
payload = {
'username': username,
'email': email,
'first_name': first_name,
'last_name': last_name,
'password': password,
'no_email_validation': True,
'return_url': return_url,
}
assert len(mail.outbox) == 0
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers)
assert len(mail.outbox) == 0
assert response.status_code == 201
assert response.json['result'] == 1
assert response.json['user']['username'] == username
assert response.json['user']['email'] == email
assert response.json['user']['first_name'] == first_name
assert response.json['user']['last_name'] == last_name
assert check_password(password, response.json['user']['password'])
assert response.json['token']
assert response.json['validation_url'].startswith('http://testserver/accounts/activate/')
assert User.objects.count() == 2
user = User.objects.latest('id')
assert user.ou == get_default_ou()
assert user.username == username
assert user.email == email
assert user.first_name == first_name
assert user.last_name == last_name
assert user.check_password(password)
def test_register_ou_no_email_validation(settings, app, admin, django_user_model):
settings.A2_USERNAME_IS_UNIQUE = False
settings.A2_REGISTRATION_USERNAME_IS_UNIQUE = False
User = django_user_model
password = '12XYab'
username = 'john.doe'
email = 'john.doe@example.com'
first_name = 'John'
last_name = 'Doe'
return_url = 'http://sp.example.com/validate/'
ou = 'default'
# invalid payload
payload = {
'last_name': last_name,
'return_url': return_url,
}
headers = basic_authorization_header(admin)
assert len(mail.outbox) == 0
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=400)
assert 'errors' in response.json
assert response.json['result'] == 0
assert response.json['errors'] == {
'__all__': ['You must set at least a username, an email or a first name and a last name'],
}
# valid payload
payload = {
'username': username,
'email': email,
'first_name': first_name,
'last_name': last_name,
'password': password,
'no_email_validation': True,
'return_url': return_url,
'ou': ou,
}
assert len(mail.outbox) == 0
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers)
assert len(mail.outbox) == 0
assert response.status_code == 201
assert response.json['result'] == 1
assert response.json['user']['username'] == username
assert response.json['user']['email'] == email
assert response.json['user']['first_name'] == first_name
assert response.json['user']['last_name'] == last_name
assert check_password(password, response.json['user']['password'])
assert response.json['token']
assert response.json['validation_url'].startswith('http://testserver/accounts/activate/')
assert User.objects.count() == 2
user = User.objects.latest('id')
assert user.username == username
assert user.email == email
assert user.first_name == first_name
assert user.last_name == last_name
assert user.check_password(password)
def test_api_drf_authentication_class(app, admin, user_ou1, oidc_client):
url = '/api/users/%s/' % user_ou1.uuid
# test invalid client
app.authorization = ('Basic', ('foo', 'bar'))
resp = app.get(url, status=401)
assert resp.json['result'] == 0
assert resp.json['errors'] == "Invalid username/password."
# test inactive client
admin.is_active = False
admin.save()
app.authorization = ('Basic', (admin.username, admin.username))
resp = app.get(url, status=401)
assert resp.json['result'] == 0
assert resp.json['errors'] == "User inactive or deleted."
# test oidc client
app.authorization = ('Basic', (oidc_client.username, oidc_client.username))
app.get(url, status=200)
# test oidc client without has API access
oidc_client.oidc_client.has_api_access = False
oidc_client.oidc_client.save()
app.authorization = ('Basic', (oidc_client.username, oidc_client.username))
response = app.get(url, status=401)
assert response.json['result'] == 0
assert response.json['errors']
def test_api_check_password(app, superuser, oidc_client, user_ou1):
app.authorization = ('Basic', (superuser.username, superuser.username))
# test with invalid paylaod
payload = {'username': 'whatever'}
resp = app.post_json(reverse('a2-api-check-password'), params=payload, status=400)
assert resp.json['result'] == 0
assert resp.json['errors'] == {'password': ['This field is required.']}
# test with invalid credentials
payload = {'username': 'whatever', 'password': 'password'}
resp = app.post_json(reverse('a2-api-check-password'), params=payload, status=200)
assert resp.json['result'] == 0
assert resp.json['errors'] == ["Invalid username/password."]
# test with valid credentials
payload = {'username': user_ou1.username, 'password': user_ou1.username}
resp = app.post_json(reverse('a2-api-check-password'), params=payload, status=200)
assert resp.json['result'] == 1
# test valid oidc credentials
payload = {
'username': oidc_client.oidc_client.client_id,
'password': oidc_client.oidc_client.client_secret,
}
resp = app.post_json(reverse('a2-api-check-password'), params=payload, status=200)
assert resp.json['result'] == 1
assert resp.json['oidc_client'] is True
def test_password_change(app, ou1, admin):
app.authorization = ('Basic', (admin.username, admin.username))
user1 = User(username='john.doe', email='john.doe@example.com', ou=ou1)
user1.set_password('password')
user1.save()
user2 = User(username='john.doe2', email='john.doe@example.com', ou=ou1)
user2.set_password('password')
user2.save()
payload = {
'email': 'none@example.com',
'ou': ou1.slug,
'old_password': 'password',
'new_password': 'password2',
}
url = reverse('a2-api-password-change')
response = app.post_json(url, params=payload, status=400)
assert 'errors' in response.json
assert response.json['result'] == 0
payload = {
'email': 'john.doe@example.com',
'ou': ou1.slug,
'old_password': 'password',
'new_password': 'password2',
}
response = app.post_json(url, params=payload, status=400)
assert 'errors' in response.json
assert response.json['result'] == 0
user2.delete()
response = app.post_json(url, params=payload)
assert response.json['result'] == 1
assert User.objects.get(username='john.doe').check_password('password2')
assert_event('manager.user.password.change', user=admin, api=True)
def test_password_reset(app, ou1, admin, user_ou1, mailoutbox):
email = user_ou1.email
url = reverse('a2-api-users-password-reset', kwargs={'uuid': user_ou1.uuid})
app.authorization = ('Basic', (user_ou1.username, user_ou1.username))
app.post(url, status=403)
app.authorization = ('Basic', (admin.username, admin.username))
app.get(url, status=405)
user_ou1.email = ''
user_ou1.save()
resp = app.post(url, status=500)
assert resp.json['result'] == 0
assert resp.json['reason'] == 'User has no mail'
user_ou1.email = email
user_ou1.save()
app.post(url, status=204)
assert len(mailoutbox) == 1
mail = mailoutbox[0]
assert mail.to[0] == email
assert 'http://testserver/password/reset/confirm/' in mail.body
assert_event('manager.user.password.reset.request', user=admin, api=True)
def test_force_password_reset(app, ou1, admin, user_ou1, mailoutbox):
url = reverse('a2-api-users-force-password-reset', kwargs={'uuid': user_ou1.uuid})
app.authorization = ('Basic', (user_ou1.username, user_ou1.username))
app.post(url, status=403)
app.authorization = ('Basic', (admin.username, admin.username))
app.get(url, status=405)
app.post(url, status=204)
assert_event('manager.user.password.change.force', user=admin, api=True)
assert PasswordReset.objects.filter(user=user_ou1).exists()
def test_users_email(app, ou1, admin, user_ou1, mailoutbox):
url = reverse('a2-api-users-email', kwargs={'uuid': user_ou1.uuid})
# test access error
app.authorization = ('Basic', (user_ou1.username, user_ou1.username))
app.post(url, status=403)
# test method error
app.authorization = ('Basic', (admin.username, admin.username))
app.get(url, status=405)
new_email = 'newmail@yopmail.com'
resp = app.post_json(url, params={'email': new_email})
assert resp.json['result'] == 1
assert len(mailoutbox) == 1
mail = mailoutbox[0]
assert mail.to[0] == new_email
assert 'http://testserver/accounts/change-email/verify/' in mail.body
def test_api_delete_role(app, admin_ou1, role_ou1):
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
app.delete(f'/api/roles/{role_ou1.uuid}/')
assert not Role.objects.filter(slug='role_ou1').exists()
assert_event('manager.role.deletion', user=admin_ou1, api=True, role_name=role_ou1.name)
def test_api_delete_role_unauthorized(app, simple_user, role_ou1):
app.authorization = ('Basic', (simple_user.username, simple_user.username))
app.delete(f'/api/roles/{role_ou1.uuid}/', status=404)
assert len(Role.objects.filter(slug='role_ou1'))
def test_api_patch_role(app, admin_ou1, role_ou1):
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
role_data = {
'slug': 'updated-role',
}
app.patch_json(f'/api/roles/{role_ou1.uuid}/', params=role_data)
assert_event('manager.role.edit', user=admin_ou1, api=True, role_name=role_ou1.name)
# The role API won't change the organizational unit attribute:
role_ou1.refresh_from_db()
assert role_ou1.name == 'role_ou1'
assert role_ou1.slug == 'updated-role'
assert role_ou1.ou.slug == 'ou1'
def test_api_patch_role_unauthorized(app, simple_user, role_ou1):
app.authorization = ('Basic', (simple_user.username, simple_user.username))
role_data = {
'slug': 'updated-role',
}
app.patch_json(f'/api/roles/{role_ou1.uuid}/', params=role_data, status=404)
role_ou1.refresh_from_db()
assert role_ou1.slug == 'role_ou1'
assert not Role.objects.filter(slug='updated-role').exists()
def test_api_put_role(app, admin_ou1, role_ou1, ou1):
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
role_data = {'name': 'updated-role', 'slug': 'updated-role', 'ou': 'ou2'}
app.put_json(f'/api/roles/{role_ou1.uuid}/', params=role_data)
role_ou1.refresh_from_db()
assert role_ou1.name == 'updated-role'
assert role_ou1.slug == 'updated-role'
assert role_ou1.ou.slug == 'ou1'
assert_event('manager.role.edit', user=admin_ou1, api=True, role_name=role_ou1.name)
def test_api_put_role_unauthorized(app, simple_user, role_ou1, ou1):
app.authorization = ('Basic', (simple_user.username, simple_user.username))
role_data = {'name': 'updated-role', 'slug': 'updated-role', 'ou': 'ou2'}
app.put_json(f'/api/roles/{role_ou1.uuid}/', params=role_data, status=404)
role_ou1.refresh_from_db()
assert role_ou1.name == 'role_ou1'
assert role_ou1.slug == 'role_ou1'
assert role_ou1.ou.slug == 'ou1'
def test_api_post_role(app, admin_ou1, ou1):
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
role_data = {'slug': 'coffee-manager', 'name': 'Coffee Manager', 'ou': 'ou1'}
resp = app.post_json('/api/roles/', params=role_data)
assert_event('manager.role.creation', user=admin_ou1, api=True, role_name='Coffee Manager')
assert isinstance(resp.json, dict)
uuid = resp.json['uuid']
# Check attribute values against the server's response:
assert set(role_data.items()) < set(resp.json.items())
# Check attributes values against the DB:
role = Role.objects.get(uuid=uuid)
assert role.slug == role_data['slug']
assert role.name == role_data['name']
assert role.ou.slug == role_data['ou']
def test_api_post_role_no_ou(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
role_data = {
'slug': 'tea-manager',
'name': 'Tea Manager',
}
resp = app.post_json('/api/roles/', params=role_data)
uuid = resp.json['uuid']
role = Role.objects.get(uuid=uuid)
assert role.ou == get_default_ou()
def test_api_post_role_no_slug(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
role_data = {
'name': 'Some Role',
}
resp = app.post_json('/api/roles/', params=role_data)
uuid = resp.json['uuid']
role = Role.objects.get(uuid=uuid)
assert role.slug == slugify(role.name)
assert role.slug == slugify(role_data['name'])
# another call with same role name
role_data = {
'name': 'Some Role',
}
resp = app.post_json('/api/roles/', params=role_data, status=400)
assert resp.json['errors']['__all__'] == [
'The fields name, ou must make a unique set.',
'The fields slug, ou must make a unique set.',
]
# no slug no name
role_data = {
'id': 42,
}
resp = app.post_json('/api/roles/', params=role_data, status=400)
assert resp.json['errors']['name'] == ['This field is required.']
def test_api_post_ou_no_slug(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
ou_data = {
'name': 'Some Organizational Unit',
}
resp = app.post_json('/api/ous/', params=ou_data)
uuid = resp.json['uuid']
ou = OU.objects.get(uuid=uuid)
assert ou.id != get_default_ou().id
assert ou.slug == slugify(ou.name)
assert ou.slug == slugify(ou_data['name'])
# another call with same ou name
ou_data = {
'name': 'Some Organizational Unit',
}
resp = app.post_json('/api/ous/', params=ou_data, status=400)
assert resp.json['errors']['__all__'] == [
"The fields name must make a unique set.",
"The fields slug must make a unique set.",
]
# no slug no name
ou_data = {
'id': 42,
}
resp = app.post_json('/api/ous/', params=ou_data, status=400)
assert resp.json['errors']['name'] == ['This field is required.']
def test_api_post_ou_get_or_create(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
# first get-or-create? -> create
ou_data = {
'name': 'Some Organizational Unit',
}
resp = app.post_json('/api/ous/', params=ou_data)
uuid = resp.json['uuid']
ou = OU.objects.get(uuid=uuid)
# second get-or-create? -> get
ou_data = {
'name': 'Some Organizational Unit',
'slug': ou.slug,
}
resp = app.post_json('/api/ous/?get_or_create=slug', params=ou_data)
assert resp.json['uuid'] == ou.uuid
# update-or-create? -> update
ou_data = {
'name': 'Another name',
'slug': ou.slug,
}
resp = app.post_json('/api/ous/?update_or_create=slug', params=ou_data)
assert resp.json['uuid'] == ou.uuid
assert OU.objects.get(uuid=resp.json['uuid']).name == ou_data['name']
def test_api_post_role_unauthorized(app, simple_user, ou1):
app.authorization = ('Basic', (simple_user.username, simple_user.username))
role_data = {'slug': 'mocca-manager', 'name': 'Mocca Manager', 'ou': 'ou1'}
app.post_json('/api/roles/', params=role_data, status=403)
assert not Role.objects.filter(slug='mocca-manager').exists()
def test_api_get_role_description(app, admin_rando_role, role_random):
app.authorization = ('Basic', (admin_rando_role.username, admin_rando_role.username))
resp = app.get(f'/api/roles/{role_random.uuid}/')
assert resp.json['slug'] == 'rando'
assert resp.json['ou'] == 'ou_rando'
def test_api_get_role_not_found(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
app.get('/api/roles/thisisnotavalidroleslug/', status=404)
def test_api_get_role_list(app, admin_ou1, role_ou1, role_random):
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
resp = app.get('/api/roles/')
role_fields = ['slug', 'uuid', 'name', 'ou']
assert len(resp.json['results'])
for role_dict in resp.json['results']:
for field in role_fields:
assert field in role_dict
def test_api_filter_role_list(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
role = Role.objects.create(name='Test role')
for i in range(10):
Role.objects.create(name=f'Prefixed test role {i}')
resp = app.get('/api/roles/?name__icontains=test role')
assert len(resp.json['results']) == 11
resp = app.get('/api/roles/?slug__icontains=test-role')
assert len(resp.json['results']) == 11
resp = app.get('/api/roles/?name__startswith=Prefixed')
assert len(resp.json['results']) == 10
resp = app.get('/api/roles/?slug__startswith=prefixed')
assert len(resp.json['results']) == 10
resp = app.get('/api/roles/?uuid=%s' % role.uuid)
assert len(resp.json['results']) == 1
assert resp.json['results'][0]['name'] == 'Test role'
resp = app.get('/api/roles/?name=Test role')
assert len(resp.json['results']) == 1
resp = app.get('/api/roles/?name=test role')
assert len(resp.json['results']) == 0
resp = app.get('/api/roles/?ou__slug=ou2&slug__icontains=test-role')
assert len(resp.json['results']) == 0
ou2 = OU.objects.create(name='OU2', slug='ou2', email_is_unique=True)
role = Role.objects.create(name='Test role', ou=ou2)
resp = app.get('/api/roles/?ou__slug=ou2&slug__icontains=test-role')
assert len(resp.json['results']) == 1
def test_api_get_role_member_list(app, admin_ou1, user_ou1, role_ou1, role_random):
app.authorization = ('Basic', (admin_ou1.username, admin_ou1.username))
url = reverse('a2-api-role-members-list', kwargs={'role_uuid': role_ou1.uuid})
Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=True)
user_ou1.attributes.birthdate = datetime.date(2019, 2, 2)
user_ou1.save()
# john.doe is a direct member of role_ou1
role_ou1.members.add(user_ou1)
assert user_ou1 in role_ou1.members.all()
# admin.ou1 is an inherited member of role_ou1
role_random.members.add(admin_ou1)
role_random.add_parent(role_ou1)
assert admin_ou1 not in role_ou1.members.all()
assert admin_ou1 in role_ou1.all_members()
# default api call without nested users
resp = app.get(url)
assert len(resp.json['results']) > 0
for user_dict in resp.json['results']:
assert USER_ATTRIBUTES_SET | {'birthdate', 'birthdate_verified'} == set(user_dict)
assert [x['username'] for x in resp.json['results']] == ['john.doe']
# api call with nested users
resp = app.get(url, params={'nested': 'true'})
assert [x['username'] for x in resp.json['results']] == ['john.doe', 'admin.ou1']
# get users ordered by usernames
resp = app.get(url, params={'nested': 'true', 'ordering': 'username'})
assert [x['username'] for x in resp.json['results']] == ['admin.ou1', 'john.doe']
def test_no_opened_session_cookie_on_api(app, user, settings):
settings.A2_OPENED_SESSION_COOKIE_DOMAIN = 'testserver.local'
app.authorization = ('Basic', (user.username, user.username))
app.get('/api/users/')
assert 'A2_OPENED_SESSION' not in app.cookies
def test_validate_password_default(app):
for password, ok, length, lower, digit, upper in (
('.', False, False, False, False, False),
('x' * 8, False, True, True, False, False),
('x' * 8 + '1', False, True, True, True, False),
('x' * 8 + '1X', True, True, True, True, True),
):
response = app.post_json('/api/validate-password/', params={'password': password})
assert response.json['result'] == 1
assert response.json['ok'] is ok
assert len(response.json['checks']) == 4
assert response.json['checks'][0]['label'] == '8 characters'
assert response.json['checks'][0]['result'] is length
assert response.json['checks'][1]['label'] == '1 lowercase letter'
assert response.json['checks'][1]['result'] is lower
assert response.json['checks'][2]['label'] == '1 digit'
assert response.json['checks'][2]['result'] is digit
assert response.json['checks'][3]['label'] == '1 uppercase letter'
assert response.json['checks'][3]['result'] is upper
def test_validate_password_regex(app, settings):
settings.A2_PASSWORD_POLICY_REGEX = '^.*ok.*$'
settings.A2_PASSWORD_POLICY_REGEX_ERROR_MSG = 'must contain "ok"'
response = app.post_json('/api/validate-password/', params={'password': 'x' * 8 + '1X'})
assert response.json['result'] == 1
assert response.json['ok'] is False
assert len(response.json['checks']) == 5
assert response.json['checks'][0]['label'] == '8 characters'
assert response.json['checks'][0]['result'] is True
assert response.json['checks'][1]['label'] == '1 lowercase letter'
assert response.json['checks'][1]['result'] is True
assert response.json['checks'][2]['label'] == '1 digit'
assert response.json['checks'][2]['result'] is True
assert response.json['checks'][3]['label'] == '1 uppercase letter'
assert response.json['checks'][3]['result'] is True
assert response.json['checks'][4]['label'] == 'must contain "ok"'
assert response.json['checks'][4]['result'] is False
response = app.post_json('/api/validate-password/', params={'password': 'x' * 8 + 'ok1X'})
assert response.json['result'] == 1
assert response.json['ok'] is True
assert len(response.json['checks']) == 5
assert response.json['checks'][0]['label'] == '8 characters'
assert response.json['checks'][0]['result'] is True
assert response.json['checks'][1]['label'] == '1 lowercase letter'
assert response.json['checks'][1]['result'] is True
assert response.json['checks'][2]['label'] == '1 digit'
assert response.json['checks'][2]['result'] is True
assert response.json['checks'][3]['label'] == '1 uppercase letter'
assert response.json['checks'][3]['result'] is True
assert response.json['checks'][4]['label'] == 'must contain "ok"'
assert response.json['checks'][4]['result'] is True
@pytest.mark.parametrize(
'min_length, password,strength,label',
[
(0, '', 0, 'Very Weak'),
(0, '?', 0, 'Very Weak'),
(0, '?JR!', 1, 'Weak'),
(0, '?JR!p4A', 2, 'Fair'),
(0, '?JR!p4A2i', 3, 'Good'),
(0, '?JR!p4A2i:#', 4, 'Strong'),
(12, '?JR!p4A2i:#', 0, 'Very Weak'),
],
)
def test_password_strength(app, settings, min_length, password, strength, label):
settings.A2_PASSWORD_POLICY_MIN_LENGTH = min_length
response = app.post_json('/api/password-strength/', params={'password': password})
assert response.json['result'] == 1
assert response.json['strength'] == strength
assert response.json['strength_label'] == label
@pytest.mark.parametrize(
'min_length, password, hint',
[
(0, '', 'add more words or characters.'),
(0, 'sdfgh', 'avoid straight rows of keys like "sdfgh".'),
(0, 'ertgfd', 'avoid short keyboard patterns like "ertgfd".'),
(0, 'abab', 'avoid repeated words and characters like "abab".'),
(0, 'abcd', 'avoid sequences like "abcd".'),
(0, '2019', 'avoid recent years.'),
(0, '02/08/14', 'avoid dates and years that are associated with you.'),
(0, '02/08/14', 'avoid dates and years that are associated with you.'),
(0, 'p@ssword', 'avoid "p@ssword" : it\'s similar to a commonly used password'),
(0, 'password', 'avoid "password" : it\'s a commonly used password.'),
(42, 'password', 'use at least 42 characters.'),
],
)
def test_password_strength_hints(app, settings, min_length, password, hint):
settings.A2_PASSWORD_POLICY_MIN_LENGTH = min_length
settings.A2_PASSWORD_POLICY_MIN_STRENGTH = 3
response = app.post_json('/api/password-strength/', params={'password': password})
assert response.json['result'] == 1
assert response.json['hint'] == hint
def test_api_users_get_or_create(settings, app, admin):
app.authorization = ('Basic', (admin.username, admin.username))
# test missing first_name
payload = {
'email': 'john.doe@example.net',
'first_name': 'John',
'last_name': 'Doe',
}
resp = app.post_json('/api/users/?get_or_create=email', params=payload, status=201)
id = resp.json['id']
assert User.objects.get(id=id).first_name == 'John'
assert User.objects.get(id=id).last_name == 'Doe'
password = User.objects.get(id=id).password
resp = app.post_json('/api/users/?get_or_create=email', params=payload, status=200)
assert id == resp.json['id']
assert User.objects.get(id=id).first_name == 'John'
assert User.objects.get(id=id).last_name == 'Doe'
assert User.objects.get(id=id).password == password
payload = {
'email': 'john.doe@example.net',
'first_name': 'Jane',
}
resp = app.post_json('/api/users/?update_or_create=email', params=payload, status=200)
assert id == resp.json['id']
assert User.objects.get(id=id).first_name == 'Jane'
assert User.objects.get(id=id).last_name == 'Doe'
assert User.objects.get(id=id).password == password
payload['password'] = 'secret'
resp = app.post_json('/api/users/?update_or_create=email', params=payload, status=200)
assert User.objects.get(id=id).first_name == 'Jane'
assert User.objects.get(id=id).last_name == 'Doe'
assert User.objects.get(id=id).password != password
assert User.objects.get(id=id).check_password('secret')
# do not get deleted user, create a new one
User.objects.get(id=id).delete()
payload['last_name'] = 'Doe'
resp = app.post_json('/api/users/?get_or_create=email', params=payload, status=201)
assert id != resp.json['id']
id = resp.json['id']
assert User.objects.get(id=id).first_name == 'Jane'
assert User.objects.get(id=id).last_name == 'Doe'
assert User.objects.get(id=id).password != password
assert User.objects.get(id=id).check_password('secret')
def test_api_users_get_or_create_email_is_unique(settings, app, admin):
settings.A2_EMAIL_IS_UNIQUE = True
app.authorization = ('Basic', (admin.username, admin.username))
# test missing first_name
payload = {
'email': 'john.doe@example.net',
'first_name': 'John',
'last_name': 'Doe',
}
resp = app.post_json('/api/users/?get_or_create=email', params=payload, status=201)
id = resp.json['id']
assert User.objects.get(id=id).first_name == 'John'
assert User.objects.get(id=id).last_name == 'Doe'
resp = app.post_json('/api/users/?get_or_create=email', params=payload, status=200)
assert id == resp.json['id']
assert User.objects.get(id=id).first_name == 'John'
assert User.objects.get(id=id).last_name == 'Doe'
payload['first_name'] = 'Jane'
resp = app.post_json('/api/users/?update_or_create=email', params=payload, status=200)
assert id == resp.json['id']
assert User.objects.get(id=id).first_name == 'Jane'
assert User.objects.get(id=id).last_name == 'Doe'
def test_api_users_get_or_create_email_not_unique(settings, app, admin):
settings.A2_EMAIL_IS_UNIQUE = False
ou1 = OU.objects.create(name='OU1', slug='ou1', email_is_unique=True)
ou2 = OU.objects.create(name='OU2', slug='ou2', email_is_unique=False)
app.authorization = ('Basic', (admin.username, admin.username))
payload = {'email': 'john.doe@example.net', 'first_name': 'John', 'last_name': 'Doe', 'ou': 'ou1'}
# 1. create
resp = app.post_json('/api/users/?get_or_create=email', params=payload, status=201)
id_user = resp.json['id']
assert User.objects.get(id=id_user).first_name == 'John'
assert User.objects.get(id=id_user).last_name == 'Doe'
assert User.objects.get(id=id_user).ou == ou1
# 2. get but override email with an uppercase version, to test if search is case-insensitive
resp = app.post_json(
'/api/users/?get_or_create=email', params=dict(payload, email=payload['email'].upper()), status=200
)
# 3. explicitly create in a different OU
payload['ou'] = 'ou2'
resp = app.post_json('/api/users/', params=payload, status=201)
id_user2 = resp.json['id']
assert id_user2 != id_user
assert User.objects.get(id=id_user2).first_name == 'John'
assert User.objects.get(id=id_user2).last_name == 'Doe'
assert User.objects.get(id=id_user2).ou == ou2
# 4. fail to retrieve a single instance for an ambiguous get-or-create key
resp = app.post_json('/api/users/?get_or_create=email', params=payload, status=409)
assert (
resp.json['errors']
== "retrieved several instances of model User for key attributes {'email__iexact': 'john.doe@example.net'}"
)
def test_api_users_get_or_create_multi_key(settings, app, admin):
app.authorization = ('Basic', (admin.username, admin.username))
# test missing first_name
payload = {
'email': 'john.doe@example.net',
'first_name': 'John',
'last_name': 'Doe',
}
resp = app.post_json(
'/api/users/?get_or_create=first_name&get_or_create=last_name', params=payload, status=201
)
id = resp.json['id']
assert User.objects.get(id=id).first_name == 'John'
assert User.objects.get(id=id).last_name == 'Doe'
password = User.objects.get(id=id).password
resp = app.post_json(
'/api/users/?get_or_create=first_name&get_or_create=last_name', params=payload, status=200
)
assert id == resp.json['id']
assert User.objects.get(id=id).first_name == 'John'
assert User.objects.get(id=id).last_name == 'Doe'
assert User.objects.get(id=id).password == password
payload['email'] = 'john.doe@example2.net'
payload['password'] = 'secret'
resp = app.post_json(
'/api/users/?update_or_create=first_name&update_or_create=last_name', params=payload, status=200
)
assert id == resp.json['id']
assert User.objects.get(id=id).email == 'john.doe@example2.net'
assert User.objects.get(id=id).password != password
assert User.objects.get(id=id).check_password('secret')
def test_api_roles_get_or_create(settings, ou1, app, admin):
app.authorization = ('Basic', (admin.username, admin.username))
# test missing first_name
payload = {
'ou': 'ou1',
'name': 'Role 1',
'slug': 'role-1',
}
resp = app.post_json('/api/roles/?get_or_create=slug', params=payload, status=201)
uuid = resp.json['uuid']
assert Role.objects.get(uuid=uuid).name == 'Role 1'
assert Role.objects.get(uuid=uuid).slug == 'role-1'
assert Role.objects.get(uuid=uuid).ou == ou1
resp = app.post_json('/api/roles/?get_or_create=slug', params=payload, status=200)
assert uuid == resp.json['uuid']
payload['name'] = 'Role 2'
resp = app.post_json('/api/roles/?update_or_create=slug', params=payload, status=200)
assert uuid == resp.json['uuid']
assert Role.objects.get(uuid=uuid).name == 'Role 2'
assert Role.objects.get(uuid=uuid).slug == 'role-1'
def test_api_users_hashed_password(settings, app, admin):
app.authorization = ('Basic', (admin.username, admin.username))
payload = {
'email': 'john.doe@example.net',
'first_name': 'John',
'last_name': 'Doe',
'hashed_password': 'pbkdf2_sha256$36000$re9zaUj1ize0$bX1cqB91ni4aMOtRh8//TLaJkX+xnD2w84MCQx9AJcE=',
}
resp = app.post_json('/api/users/?get_or_create=email', params=payload, status=201)
id = resp.json['id']
assert User.objects.get(id=id).first_name == 'John'
assert User.objects.get(id=id).last_name == 'Doe'
assert User.objects.get(id=id).check_password('admin')
password = User.objects.get(id=id).password
payload['hashed_password'] = 'sha-oldap$$e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4'
resp = app.post_json('/api/users/?update_or_create=email', params=payload, status=200)
assert User.objects.get(id=id).password != password
assert User.objects.get(id=id).check_password('secret')
payload['password'] = 'secret'
resp = app.post_json('/api/users/?update_or_create=email', params=payload, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['password'] == ['conflict with provided hashed_password']
del payload['password']
payload['hashed_password'] = 'unknown_format'
resp = app.post_json('/api/users/?update_or_create=email', params=payload, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['hashed_password'] == ['unknown hash format']
payload['hashed_password'] = 'argon2$wrong_format'
resp = app.post_json('/api/users/?update_or_create=email', params=payload, status=400)
assert resp.json['result'] == 0
if django.VERSION >= (1, 10, 0):
assert resp.json['errors']['hashed_password'] == ['hash format error']
else: # before Django 1.10 Argon2 is not recognized
assert resp.json['errors']['hashed_password'] == ['unknown hash format']
def test_api_users_required_attribute(settings, app, admin, simple_user):
assert Attribute.objects.get(name='last_name').required is True
payload = {
'username': simple_user.username,
'id': simple_user.id,
'email': 'john.doe@nowhere.null',
'first_name': 'Johnny',
}
headers = basic_authorization_header(admin)
# create fails
resp = app.post_json('/api/users/', params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['last_name'] == ['This field is required.']
# update from missing value to blank field fails
payload['last_name'] = ''
resp = app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['last_name'] == ['This field may not be blank.']
# update with value pass
payload['last_name'] = 'Foobar'
resp = app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
user = User.objects.get(id=simple_user.id)
assert user.last_name == 'Foobar'
# update from non-empty value to blank fails
payload['last_name'] = ''
resp = app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['last_name'] == ['This field may not be blank.']
def test_api_users_required_date_attributes(settings, app, admin, simple_user):
Attribute.objects.create(kind='string', name='prefered_color', label='prefered color', required=True)
Attribute.objects.create(kind='date', name='date', label='date', required=True)
Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=True)
payload = {
'username': simple_user.username,
'id': simple_user.id,
'email': 'john.doe@nowhere.null',
'first_name': 'Johnny',
'last_name': 'Doe',
}
headers = basic_authorization_header(admin)
# create fails
resp = app.post_json('/api/users/', params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['prefered_color'] == ['This field is required.']
assert resp.json['errors']['date'] == ['This field is required.']
assert resp.json['errors']['birthdate'] == ['This field is required.']
# update from missing value to blank fails
payload['prefered_color'] = ''
payload['date'] = ''
payload['birthdate'] = ''
resp = app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['prefered_color'] == ['This field may not be blank.']
assert resp.json['errors']['date'] == ['This field may not be blank.']
assert resp.json['errors']['birthdate'] == ['This field may not be blank.']
# update with invalid values fails
payload['prefered_color'] = '?' * 257
payload['date'] = '0000-00-00'
payload['birthdate'] = '1899-12-31'
resp = app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=400)
assert resp.json['result'] == 0
assert resp.json['errors']['prefered_color'] == ['Ensure this field has no more than 256 characters.']
assert any(error.startswith('Date has wrong format.') for error in resp.json['errors']['date'])
assert resp.json['errors']['birthdate'] == [
'birthdate must be in the past and greater or equal than 1900-01-01.'
]
# update with values pass
del payload['id']
payload['prefered_color'] = 'blue'
payload['date'] = '1515-01-15'
payload['birthdate'] = '1900-02-22'
resp = app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
assert_event('manager.user.profile.edit', user=admin, api=True, new=payload)
# value are properly returned on a get
resp = app.get(f'/api/users/{simple_user.uuid}/', headers=headers, status=200)
assert resp.json['prefered_color'] == 'blue'
assert resp.json['date'] == '1515-01-15'
assert resp.json['birthdate'] == '1900-02-22'
def test_api_users_optional_date_attributes(settings, app, admin, simple_user):
Attribute.objects.create(kind='string', name='prefered_color', label='prefered color', required=False)
Attribute.objects.create(kind='date', name='date', label='date', required=False)
Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=False)
payload = {
'username': simple_user.username,
'id': simple_user.id,
'email': 'john.doe@nowhere.null',
'first_name': 'Johnny',
'last_name': 'Doe',
}
headers = basic_authorization_header(admin)
app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
app.get(f'/api/users/{simple_user.uuid}/', headers=headers, status=200)
payload['prefered_color'] = None
payload['date'] = None
payload['birthdate'] = None
payload['prefered_color'] = ''
payload['date'] = ''
payload['birthdate'] = ''
app.put_json(f'/api/users/{simple_user.uuid}/', params=payload, headers=headers, status=200)
app.get(f'/api/users/{simple_user.uuid}/', headers=headers, status=200)
payload['prefered_color'] = None
payload['date'] = None
payload['birthdate'] = None
def test_filter_users_by_service(app, admin, simple_user, role_random, service):
app.authorization = ('Basic', (admin.username, admin.username))
resp = app.get('/api/users/')
assert len(resp.json['results']) == 2
resp = app.get('/api/users/?service-slug=xxx')
assert len(resp.json['results']) == 0
resp = app.get('/api/users/?service-slug=service&service-ou=default')
assert len(resp.json['results']) == 2
role_random.members.add(simple_user)
AuthorizedRole.objects.get_or_create(service=service, role=role_random)
resp = app.get('/api/users/?service-slug=service&service-ou=default')
assert len(resp.json['results']) == 1
def test_filter_member_users_by_service(app, admin, simple_user, simple_role, role_random, service):
app.authorization = ('Basic', (admin.username, admin.username))
url = reverse('a2-api-role-members-list', kwargs={'role_uuid': simple_role.uuid})
simple_role.members.add(admin)
simple_role.members.add(simple_user)
resp = app.get(url)
assert len(resp.json['results']) == 2
resp = app.get(url + '?service-slug=xxx')
assert len(resp.json['results']) == 0
resp = app.get(url + '?service-slug=service&service-ou=default')
assert len(resp.json['results']) == 2
role_random.members.add(simple_user)
AuthorizedRole.objects.get_or_create(service=service, role=role_random)
resp = app.get(url + '?service-slug=service&service-ou=default')
assert len(resp.json['results']) == 1
def test_filter_users_by_last_modification(app, admin, simple_user, freezer):
app.authorization = ('Basic', (admin.username, admin.username))
freezer.move_to('2019-10-27T02:00:00Z')
admin.save()
simple_user.save()
# AmbiguousTimeError
if django.VERSION[0] <= 2:
resp = app.get('/api/users/', params={'modified__gt': '2019-10-27T02:58:07'})
assert len(resp.json['results']) == 2
resp = app.get('/api/users/', params={'modified__lt': '2019-10-27T02:58:07'})
assert len(resp.json['results']) == 0
else:
# django 3 (justifiably) fails on tz-naive datetimes
app.get('/api/users/', params={'modified__gt': '2019-10-27T02:58:07'}, status=400)
app.get('/api/users/', params={'modified__lt': '2019-10-27T02:58:07'}, status=400)
# of course, tz-aware datetimes work with all supported django versions
resp = app.get('/api/users/', params={'modified__gt': '2019-10-27T02:58:07+00:00'})
assert len(resp.json['results']) == 0
resp = app.get('/api/users/', params={'modified__lt': '2019-10-27T02:58:07+00:00'})
assert len(resp.json['results']) == 2
def test_filter_member_users_by_last_modification(app, admin, simple_user, simple_role, freezer):
app.authorization = ('Basic', (admin.username, admin.username))
url = reverse('a2-api-role-members-list', kwargs={'role_uuid': simple_role.uuid})
simple_role.members.add(admin)
simple_role.members.add(simple_user)
freezer.move_to('2019-10-27T02:00:00Z')
admin.save()
simple_user.save()
# AmbiguousTimeError
if django.VERSION[0] <= 2:
resp = app.get(url, params={'modified__gt': '2019-10-27T02:58:07'})
assert len(resp.json['results']) == 2
resp = app.get(url, params={'modified__lt': '2019-10-27T02:58:07'})
assert len(resp.json['results']) == 0
else:
# XXX handle tz-aware requests
app.get(url, params={'modified__gt': '2019-10-27T02:58:07'}, status=400)
app.get(url, params={'modified__lt': '2019-10-27T02:58:07'}, status=400)
def test_free_text_search(app, admin, settings):
settings.LANGUAGE_CODE = 'fr' # use fr date format
app.authorization = ('Basic', (admin.username, admin.username))
Attribute.objects.create(
kind='birthdate', name='birthdate', label='birthdate', required=False, searchable=True
)
user = User.objects.create()
user.attributes.birthdate = datetime.date(1982, 2, 10)
resp = app.get('/api/users/?q=10/02/1982')
assert len(resp.json['results']) == 1
assert resp.json['results'][0]['id'] == user.id
def test_find_duplicates(app, admin, settings):
settings.LANGUAGE_CODE = 'fr'
app.authorization = ('Basic', (admin.username, admin.username))
first_name = 'Jean-Kévin'
last_name = 'Du Château'
user = User.objects.create(first_name=first_name, last_name=last_name)
exact_match = {
'first_name': first_name,
'last_name': last_name,
}
resp = app.get('/api/users/find_duplicates/', params=exact_match)
assert resp.json['data'][0]['id'] == user.id
assert resp.json['data'][0]['duplicate_distance'] == 0
assert resp.json['data'][0]['text'] == 'Jean-Kévin Du Château'
typo = {
'first_name': 'Jean Kévin',
'last_name': 'Du Châtau',
}
resp = app.get('/api/users/find_duplicates/', params=typo)
assert resp.json['data'][0]['id'] == user.id
assert resp.json['data'][0]['duplicate_distance'] > 0
typo = {
'first_name': 'Jean Kévin',
'last_name': 'Château',
}
resp = app.get('/api/users/find_duplicates/', params=typo)
assert resp.json['data'][0]['id'] == user.id
other_person = {
'first_name': 'Jean-Kévin',
'last_name': 'Du Chêne',
}
user = User.objects.create(first_name='Éléonore', last_name='âêîôû')
resp = app.get('/api/users/find_duplicates/', params=other_person)
assert len(resp.json['data']) == 0
other_person = {
'first_name': 'Pierre',
'last_name': 'Du Château',
}
resp = app.get('/api/users/find_duplicates/', params=other_person)
assert len(resp.json['data']) == 0
def test_find_duplicates_unaccent(app, admin, settings):
settings.LANGUAGE_CODE = 'fr'
app.authorization = ('Basic', (admin.username, admin.username))
user = User.objects.create(first_name='Éléonore', last_name='âêîôû')
resp = app.get('/api/users/find_duplicates/', params={'first_name': 'Eleonore', 'last_name': 'aeiou'})
assert resp.json['data'][0]['id'] == user.id
def test_find_duplicates_birthdate(app, admin, settings):
settings.LANGUAGE_CODE = 'fr'
app.authorization = ('Basic', (admin.username, admin.username))
Attribute.objects.create(
kind='birthdate', name='birthdate', label='birthdate', required=False, searchable=True
)
user = User.objects.create(first_name='Jean', last_name='Dupont')
homonym = User.objects.create(first_name='Jean', last_name='Dupont')
user.attributes.birthdate = datetime.date(1980, 1, 2)
homonym.attributes.birthdate = datetime.date(1980, 1, 3)
params = {
'first_name': 'Jeanne',
'last_name': 'Dupont',
}
resp = app.get('/api/users/find_duplicates/', params=params)
assert len(resp.json['data']) == 2
params['birthdate'] = ('1980-01-2',)
resp = app.get('/api/users/find_duplicates/', params=params)
assert len(resp.json['data']) == 2
assert resp.json['data'][0]['id'] == user.pk
params['birthdate'] = ('1980-01-3',)
resp = app.get('/api/users/find_duplicates/', params=params)
assert len(resp.json['data']) == 2
assert resp.json['data'][0]['id'] == homonym.pk
def test_find_duplicates_ou(app, admin, settings, ou1):
settings.LANGUAGE_CODE = 'fr'
app.authorization = ('Basic', (admin.username, admin.username))
user = User.objects.create(first_name='Jean', last_name='Dupont', ou=get_default_ou())
User.objects.create(first_name='Jean', last_name='Dupont', ou=ou1)
params = {
'first_name': 'Jeanne',
'last_name': 'Dupont',
}
resp = app.get('/api/users/find_duplicates/', params=params)
assert len(resp.json['data']) == 2
params['ou'] = get_default_ou().slug
resp = app.get('/api/users/find_duplicates/', params=params)
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['id'] == user.pk
class MockedRequestResponse(mock.Mock):
status_code = 200
def json(self):
return json.loads(self.content)
def test_api_address_autocomplete(app, admin, settings):
app.authorization = ('Basic', (admin.username, admin.username))
settings.ADDRESS_AUTOCOMPLETE_URL = 'example.com'
params = {'q': '42 avenue'}
with mock.patch('authentic2.api_views.requests.get') as requests_get:
mock_resp = Response()
mock_resp.status_code = 500
requests_get.return_value = mock_resp
resp = app.get('/api/address-autocomplete/', params=params)
assert resp.json == {}
assert requests_get.call_args_list[0][0][0] == 'example.com'
assert requests_get.call_args_list[0][1]['params'] == {'q': ['42 avenue']}
with mock.patch('authentic2.api_views.requests.get') as requests_get:
mock_resp = Response()
mock_resp.status_code = 404
requests_get.return_value = mock_resp
resp = app.get('/api/address-autocomplete/', params=params)
assert resp.json == {}
with mock.patch('authentic2.api_views.requests.get') as requests_get:
requests_get.return_value = MockedRequestResponse(content=json.dumps({'data': {'foo': 'bar'}}))
resp = app.get('/api/address-autocomplete/', params=params)
assert resp.json == {'data': {'foo': 'bar'}}
settings.ADDRESS_AUTOCOMPLETE_URL = None
with mock.patch('authentic2.api_views.requests.get') as requests_get:
resp = app.get('/api/address-autocomplete/', params=params)
assert resp.json == {}
assert requests_get.call_args_list == []
del settings.ADDRESS_AUTOCOMPLETE_URL
with mock.patch('authentic2.api_views.requests.get') as requests_get:
resp = app.get('/api/address-autocomplete/', params=params)
assert resp.json == {}
assert requests_get.call_args_list == []
def test_phone_normalization_ok(settings, app, admin):
headers = basic_authorization_header(admin)
Attribute.objects.create(name='extra_phone', label='extra phone', kind='phone_number')
payload = {
'username': 'janedoe',
'extra_phone': ' + 334-99 98.56/43',
'first_name': 'Jane',
'last_name': 'Doe',
}
resp = app.post_json('/api/users/', headers=headers, params=payload, status=201)
assert resp.json['extra_phone'] == '+33499985643'
assert User.objects.get(username='janedoe').attributes.extra_phone == '+33499985643'
def test_phone_normalization_nok(settings, app, admin):
headers = basic_authorization_header(admin)
Attribute.objects.create(name='extra_phone', label='extra phone', kind='phone_number')
payload = {
'username': 'janedoe',
'first_name': 'Jane',
'last_name': 'Doe',
}
payload['extra_phone'] = (' + 334-99+98.56/43',)
app.post_json('/api/users/', headers=headers, params=payload, status=400)
payload['extra_phone'] = '1#2'
app.post_json('/api/users/', headers=headers, params=payload, status=400)
def test_fr_phone_normalization_ok(settings, app, admin):
headers = basic_authorization_header(admin)
Attribute.objects.create(name='extra_phone', label='extra phone', kind='fr_phone_number')
payload = {
'username': 'janedoe',
'extra_phone': ' 04-99 98.56/43',
'first_name': 'Jane',
'last_name': 'Doe',
}
resp = app.post_json('/api/users/', headers=headers, params=payload, status=201)
assert resp.json['extra_phone'] == '0499985643'
assert User.objects.get(username='janedoe').attributes.extra_phone == '0499985643'
def test_fr_phone_normalization_nok(settings, app, admin):
headers = basic_authorization_header(admin)
Attribute.objects.create(name='extra_phone', label='extra phone', kind='fr_phone_number')
payload = {
'username': 'janedoe',
'extra_phone': '+33499985643',
'first_name': 'Jane',
'last_name': 'Doe',
}
app.post_json('/api/users/', headers=headers, params=payload, status=400)
payload['extra_phone'] = '1#2'
app.post_json('/api/users/', headers=headers, params=payload, status=400)
def test_api_users_create_user_delete(app, settings, admin):
email = 'foo@example.net'
user1 = User.objects.create(username='foo', email=email)
user1.delete()
user2 = User.objects.create(username='foo2', email=email)
app.authorization = ('Basic', (admin.username, admin.username))
resp = app.get(f'/api/users/?email={email}')
assert len(resp.json['results']) == 1
payload = {
'username': 'foo3',
'email': email,
'first_name': 'John',
'last_name': 'Doe',
}
headers = basic_authorization_header(admin)
app.post_json('/api/users/', headers=headers, params=payload, status=201)
resp = app.get(f'/api/users/?email={email}')
assert len(resp.json['results']) == 2
user2.delete()
resp = app.get(f'/api/users/?email={email}')
assert len(resp.json['results']) == 1
settings.A2_EMAIL_IS_UNIQUE = True
# email already used
resp = app.post_json('/api/users/', headers=headers, params=payload, status=400)
assert resp.json['errors'] == {'email': ['email already used']}
def test_api_register_user_delete(app, settings, admin):
settings.A2_EMAIL_IS_UNIQUE = True
user = User.objects.create(username='foo', email='john.doe@example.com', ou=get_default_ou())
headers = basic_authorization_header(admin)
payload = {
'username': 'john.doe',
'email': 'john.doe@example.com',
'first_name': 'John',
'last_name': 'Doe',
'password': '12XYab',
'no_email_validation': True,
'return_url': 'http://sp.example.com/validate/',
'ou': 'default',
}
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=400)
assert response.json['errors'] == {'__all__': ['Account already exists']}
user.delete()
response = app.post_json(reverse('a2-api-register'), params=payload, headers=headers, status=201)
def test_api_password_change_user_delete(app, settings, admin, ou1):
app.authorization = ('Basic', (admin.username, admin.username))
user1 = User.objects.create(username='john.doe', email='john.doe@example.com', ou=ou1)
user1.set_password('password')
user1.save()
user2 = User.objects.create(username='john.doe2', email='john.doe@example.com', ou=ou1)
user2.set_password('password')
user1.save()
payload = {
'email': 'john.doe@example.com',
'ou': ou1.slug,
'old_password': 'password',
'new_password': 'password2',
}
url = reverse('a2-api-password-change')
app.post_json(url, params=payload, status=400)
user2.delete()
app.post_json(url, params=payload)
assert User.objects.get(username='john.doe').check_password('password2')
def test_api_users_delete(settings, app, admin, simple_user):
headers = basic_authorization_header(admin)
app.delete_json(f'/api/users/{simple_user.uuid}/', headers=headers)
assert not User.objects.filter(pk=simple_user.pk).exists()
assert_event('manager.user.deletion', user=admin, api=True)
def test_api_statistics_list(app, admin):
headers = basic_authorization_header(admin)
resp = app.get('/api/statistics/', headers=headers)
assert len(resp.json['data']) == 6
login_stats = {
'name': 'Login count by authentication type',
'url': 'http://testserver/api/statistics/login/',
'id': 'login',
'filters': [
{
"id": "time_interval",
"label": "Time interval",
"options": [
{"id": "day", "label": "Day"},
{"id": "month", "label": "Month"},
{"id": "year", "label": "Year"},
],
"required": True,
"default": "month",
},
{'id': 'service', 'label': 'Service', 'options': []},
],
}
assert login_stats in resp.json['data']
assert {
'name': 'Login count by service',
'url': 'http://testserver/api/statistics/service_login/',
'id': 'service-login',
'filters': [
{
"id": "time_interval",
"label": "Time interval",
"options": [
{"id": "day", "label": "Day"},
{"id": "month", "label": "Month"},
{"id": "year", "label": "Year"},
],
"required": True,
"default": "month",
},
],
} in resp.json['data']
service = Service.objects.create(name='Service1', slug='service1', ou=get_default_ou())
service = Service.objects.create(name='Service2', slug='service2', ou=get_default_ou())
login_stats['filters'][1]['options'].append({'id': 'service1 default', 'label': 'Service1'})
login_stats['filters'][1]['options'].append({'id': 'service2 default', 'label': 'Service2'})
resp = app.get('/api/statistics/', headers=headers)
assert login_stats == resp.json['data'][0]
# adding second ou doesn't change anything
ou = OU.objects.create(name='Second OU', slug='second')
resp = app.get('/api/statistics/', headers=headers)
assert login_stats in resp.json['data']
# if there are services in two differents OUs, filter is shown
service.ou = ou
service.save()
login_stats['filters'][1]['options'][1]['id'] = 'service2 second'
login_stats['filters'].append(
{
'id': 'services_ou',
'label': 'Services organizational unit',
'options': [
{'id': 'default', 'label': 'Default organizational unit'},
{'id': 'second', 'label': 'Second OU'},
],
}
)
resp = app.get('/api/statistics/', headers=headers)
assert login_stats in resp.json['data']
# same goes with users
User.objects.create(username='john.doe', email='john.doe@example.com', ou=ou)
login_stats['filters'].append(
{
'id': 'users_ou',
'label': 'Users organizational unit',
'options': [
{'id': 'default', 'label': 'Default organizational unit'},
{'id': 'second', 'label': 'Second OU'},
],
}
)
resp = app.get('/api/statistics/', headers=headers)
assert login_stats in resp.json['data']
@pytest.mark.parametrize(
'event_type_name,event_name', [('user.login', 'login'), ('user.registration', 'registration')]
)
def test_api_statistics(app, admin, freezer, event_type_name, event_name):
headers = basic_authorization_header(admin)
resp = app.get('/api/statistics/login/?time_interval=month', headers=headers)
assert resp.json == {"data": {"series": [], "x_labels": []}, "err": 0}
user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
ou = OU.objects.create(name='Second OU', slug='second')
portal = Service.objects.create(name='portal', slug='portal', ou=ou)
agendas = CASService.objects.create(
name='agendas', slug='agendas', ou=get_default_ou(), urls='https://agenda.example.net'
)
agenda_service = Service.objects.get(name='agendas')
method = {'how': 'password-on-https'}
method2 = {'how': 'france-connect'}
event_type = EventType.objects.get_for_name(event_type_name)
freezer.move_to('2020-02-03 12:00')
Event.objects.create(type=event_type, references=[portal], data=dict(method, service_name=str(portal)))
Event.objects.create(
type=event_type, references=[agendas, user], user=user, data=dict(method, service_name=str(agendas))
)
freezer.move_to('2020-03-04 13:00')
Event.objects.create(
type=event_type, references=[agenda_service], data=dict(method, service_name=str(agendas))
)
Event.objects.create(type=event_type, references=[portal], data=dict(method2, service_name=str(portal)))
resp = app.get('/api/statistics/%s/?time_interval=month' % event_name, headers=headers)
data = resp.json['data']
assert data == {
'x_labels': ['2020-02', '2020-03'],
'series': [
{'label': 'FranceConnect', 'data': [None, 1]},
{'label': 'password', 'data': [2, 1]},
],
}
# default time interval is 'month'
month_data = data
resp = app.get('/api/statistics/%s/' % event_name, headers=headers)
data = resp.json['data']
assert month_data == data
resp = app.get(
'/api/statistics/%s/?time_interval=month&services_ou=default' % event_name, headers=headers
)
data = resp.json['data']
assert data == {
'x_labels': ['2020-02', '2020-03'],
'series': [{'label': 'password', 'data': [1, 1]}],
}
# legacy way to filter by service OU
services_ou_data = data
resp = app.get('/api/statistics/%s/?time_interval=month&ou=default' % event_name, headers=headers)
data = resp.json['data']
assert services_ou_data == data
resp = app.get(
'/api/statistics/%s/?time_interval=month&users_ou=default&service=agendas default' % event_name,
headers=headers,
)
data = resp.json['data']
assert data == {
'x_labels': ['2020-02'],
'series': [{'label': 'password', 'data': [1]}],
}
resp = app.get('/api/statistics/%s/?time_interval=month&users_ou=default' % event_name, headers=headers)
data = resp.json['data']
assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [1]}]}
resp = app.get(
'/api/statistics/%s/?time_interval=month&service=agendas default' % event_name, headers=headers
)
data = resp.json['data']
assert data == {'x_labels': ['2020-02', '2020-03'], 'series': [{'label': 'password', 'data': [1, 1]}]}
resp = app.get(
'/api/statistics/%s/?time_interval=month&start=2020-03-01T01:01' % event_name, headers=headers
)
data = resp.json['data']
assert data == {
'x_labels': ['2020-03'],
'series': [{'label': 'FranceConnect', 'data': [1]}, {'label': 'password', 'data': [1]}],
}
resp = app.get(
'/api/statistics/%s/?time_interval=month&end=2020-03-01T01:01' % event_name, headers=headers
)
data = resp.json['data']
assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [2]}]}
resp = app.get('/api/statistics/%s/?time_interval=month&end=2020-03-01' % event_name, headers=headers)
data = resp.json['data']
assert data == {'x_labels': ['2020-02'], 'series': [{'label': 'password', 'data': [2]}]}
resp = app.get(
'/api/statistics/%s/?time_interval=year&service=portal second' % event_name, headers=headers
)
data = resp.json['data']
assert data == {
'x_labels': ['2020'],
'series': [{'label': 'FranceConnect', 'data': [1]}, {'label': 'password', 'data': [1]}],
}
resp = app.get('/api/statistics/service_%s/?time_interval=month' % event_name, headers=headers)
data = resp.json['data']
assert data == {
'x_labels': ['2020-02', '2020-03'],
'series': [
{'data': [1, 1], 'label': 'agendas'},
{'data': [1, 1], 'label': 'portal'},
],
}
resp = app.get('/api/statistics/service_ou_%s/?time_interval=month' % event_name, headers=headers)
data = resp.json['data']
assert data == {
'x_labels': ['2020-02', '2020-03'],
'series': [
{'data': [1, 1], 'label': 'Default organizational unit'},
{'data': [1, 1], 'label': 'Second OU'},
],
}
def test_api_statistics_no_crash_older_drf(app, admin):
headers = basic_authorization_header(admin)
app.get('/api/statistics/login/?time_interval=month', headers=headers)
def test_find_duplicates_put(app, admin, settings):
app.authorization = ('Basic', (admin.username, admin.username))
app.put_json(
'/api/users/find_duplicates/', params={'first_name': 'Eleonore', 'last_name': 'aeiou'}, status=405
)
def test_users_page_size(app, admin):
app.authorization = ('Basic', (admin.username, admin.username))
User.objects.create()
resp = app.get('/api/users/')
assert len(resp.json['results']) == 2
resp = app.get('/api/users/?limit=1')
assert len(resp.json['results']) == 1
def test_user_profile_get(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
# wrong type
app.get('/api/users/%s/profiles/non-existant-slug/' % user.uuid, status=404)
profile_type = ProfileType.objects.create(slug='one-type', name='One Type')
# wrong user
app.get('/api/users/1234/profiles/one-type/', status=404)
profile = Profile.objects.create(
profile_type=profile_type, user=user, email='user123@example.org', data={'foo': 'bar'}
)
resp = app.get('/api/users/%s/profiles/one-type/' % user.uuid)
assert resp.json == [{'data': {'foo': 'bar'}, 'identifier': '', 'email': 'user123@example.org'}]
resp = app.get('/api/users/%s/profiles/one-type/?identifier=' % user.uuid)
assert resp.json == {'data': {'foo': 'bar'}, 'identifier': '', 'email': 'user123@example.org'}
profile.identifier = 'Company ABC'
profile.save()
# specify identifier in qs
resp = app.get(
'/api/users/%s/profiles/one-type/?identifier=Company ABC' % user.uuid,
)
assert resp.json == {'data': {'foo': 'bar'}, 'identifier': 'Company ABC', 'email': 'user123@example.org'}
# not found on empty identifier
app.get(
'/api/users/%s/profiles/one-type/?identifier=' % user.uuid,
status=404,
)
def test_user_profile_put(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
# wrong type
resp = app.put('/api/users/%s/profiles/non-existant-slug/' % user.uuid, status=404)
profile_type = ProfileType.objects.create(slug='one-type', name='One Type')
# wrong user
resp = app.put('/api/users/1234/profiles/one-type/', status=404)
# missing profile
resp = app.put_json(
'/api/users/%s/profiles/one-type/' % user.uuid, params={'data': {'foo': 'bar'}}, status=404
)
Profile.objects.create(user=user, profile_type=profile_type, email='manager456@example.org', data={})
resp = app.put_json(
'/api/users/%s/profiles/one-type/' % user.uuid,
params={'data': {'foo': 'bar'}, 'email': 'user789@example.org'},
)
assert resp.json == {'result': 1, 'detail': 'Profile successfully updated'}
assert len(user.profiles.all()) == 1
assert user.profiles.last().data == {'foo': 'bar'}
assert user.profiles.last().email == 'user789@example.org'
# attempt at overwriting profile data
resp = app.put_json('/api/users/%s/profiles/one-type/' % user.uuid, params={'data': {'baz': 'bob'}})
# profile has been updated, no extra profile has been created
assert resp.json == {'result': 1, 'detail': 'Profile successfully updated'}
assert len(user.profiles.all()) == 1
assert user.profiles.last().data == {'baz': 'bob'}
Profile.objects.create(
user=user,
profile_type=profile_type,
identifier='Company DEF',
data={},
)
# overwrite profile data while specifying the identifier
resp = app.put_json(
'/api/users/%s/profiles/one-type/?identifier=Company DEF' % user.uuid,
params={'data': {'baz': 'bob'}},
)
assert resp.json == {'result': 1, 'detail': 'Profile successfully updated'}
assert user.profiles.get(identifier='Company DEF').data == {'baz': 'bob'}
assert user.profiles.get(identifier='Company DEF').email == ''
def test_user_profile_post(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
# wrong type
resp = app.get('/api/users/%s/profiles/non-existant-slug/' % user.uuid, status=404)
ProfileType.objects.create(slug='one-type', name='One Type')
# wrong user
resp = app.get('/api/users/1234/profiles/one-type/', status=404)
assert len(user.profiles.all()) == 0
resp = app.post_json(
'/api/users/%s/profiles/one-type/' % user.uuid,
params={'data': {'baz': 'bob'}, 'email': 'user321@example.org'},
)
assert resp.json == {'result': 1, 'detail': 'Profile successfully assigned to user'}
assert len(user.profiles.all()) == 1
assert user.profiles.last().data == {'baz': 'bob'}
assert user.profiles.last().email == 'user321@example.org'
app.post_json('/api/users/%s/profiles/one-type/' % user.uuid, status=400)
resp = app.post_json(
'/api/users/%s/profiles/one-type/?identifier=FooBar' % user.uuid,
params={'data': {'foo': 'bar'}, 'email': ''},
)
assert resp.json == {'result': 1, 'detail': 'Profile successfully assigned to user'}
assert len(user.profiles.all()) == 2
assert user.profiles.get(identifier='FooBar').data == {'foo': 'bar'}
assert user.profiles.get(identifier='FooBar').email == ''
app.post_json('/api/users/%s/profiles/one-type/?identifier=FooBar' % user.uuid, status=400)
def test_user_profile_delete(app, superuser):
app.authorization = ('Basic', (superuser.username, superuser.username))
user = User.objects.create(username='john.doe', email='john.doe@example.com', ou=get_default_ou())
profile_type = ProfileType.objects.create(slug='one-type', name='One Type')
Profile.objects.create(user=user, profile_type=profile_type, email='user-abc@example.org')
app.delete('/api/users/%s/profiles/non-existant-slug/' % user.uuid, status=404)
app.delete('/api/users/%s/profiles/one-type/' % user.uuid)
assert not Profile.objects.filter(user=user, profile_type=profile_type)
app.delete('/api/users/%s/profiles/one-type/' % user.uuid, status=404)
Profile.objects.create(
user=user, profile_type=profile_type, identifier='FooBar', email='manager-def@example.org'
)
app.delete('/api/users/%s/profiles/one-type/?identifier=FooBar' % user.uuid)
app.delete(
'/api/users/%s/profiles/one-type/?identifier=FooBar' % user.uuid,
status=404,
)
def test_check_api_client(app, superuser):
url = '/api/check-api-client/'
payload = {'identifier': 'foo', 'password': 'bar'}
resp = app.post_json(url, params=payload, status=401)
app.authorization = ('Basic', (superuser.username, superuser.username))
resp = app.post_json(url, params=payload)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'api client not found'
api_client = APIClient.objects.create(identifier='foo', password='foo')
ou = get_default_ou()
role1 = Role.objects.create(name='Role1', ou=ou)
api_client.apiclient_roles.add(role1)
resp = app.post_json(url, params=payload)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'api client not found'
payload = {'identifier': 'foo', 'password': 'foo'}
resp = app.post_json(url, params=payload)
assert resp.json['err'] == 0
data = resp.json['data']
assert data['is_active'] is True
assert data['is_anonymous'] is False
assert data['is_authenticated'] is True
assert data['is_superuser'] is False
assert data['restrict_to_anonymised_data'] is False
assert data['roles'] == [role1.uuid]