222 lines
8.3 KiB
Python
222 lines
8.3 KiB
Python
# authentic2 - versatile identity manager
|
|
# Copyright (C) 2010-2022 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from rest_framework.exceptions import MethodNotAllowed, ValidationError
|
|
|
|
from authentic2.a2_rbac.models import OrganizationalUnit as OU
|
|
from authentic2.a2_rbac.models import Role
|
|
from authentic2.models import Service
|
|
from authentic2.utils.api import DjangoRBACPermission, NaturalKeyRelatedField
|
|
from tests.utils import scoped_db_fixture
|
|
|
|
|
|
class TestNaturalKeyRelatedField:
|
|
@scoped_db_fixture(scope='class', autouse=True)
|
|
def fixture(self):
|
|
class Namespace:
|
|
ou = OU.objects.create(name='ou', uuid='1' * 32)
|
|
service = Service.objects.create(name='service', slug='service', ou=ou)
|
|
role = Role.objects.create(name='role', ou=ou, uuid='2' * 32)
|
|
ou2 = OU.objects.create(name='ou2', uuid='3' * 32)
|
|
|
|
yield Namespace
|
|
|
|
def test_to_representation(self, db, fixture):
|
|
assert NaturalKeyRelatedField(read_only=True).to_representation(fixture.role) == {
|
|
'name': 'role',
|
|
'ou': {'name': 'ou', 'slug': 'ou', 'uuid': '1' * 32},
|
|
'service': None,
|
|
'slug': 'role',
|
|
'uuid': '2' * 32,
|
|
}
|
|
|
|
def test_to_representation_service(self, db, fixture):
|
|
fixture.role.service = fixture.service
|
|
fixture.role.save()
|
|
assert NaturalKeyRelatedField(read_only=True).to_representation(fixture.role) == {
|
|
'name': 'role',
|
|
'ou': {'name': 'ou', 'slug': 'ou', 'uuid': '1' * 32},
|
|
'service': {
|
|
'slug': 'service',
|
|
'ou': {'name': 'ou', 'slug': 'ou', 'uuid': '1' * 32},
|
|
},
|
|
'slug': 'role',
|
|
'uuid': '2' * 32,
|
|
}
|
|
|
|
@pytest.mark.parametrize(
|
|
'value',
|
|
[
|
|
{'uuid': '2' * 32},
|
|
{'name': 'role'},
|
|
{'slug': 'role'},
|
|
{'name': 'role', 'ou': {'name': 'ou'}},
|
|
{'slug': 'role', 'ou': {'slug': 'ou'}},
|
|
{'slug': 'role', 'ou': {'uuid': '1' * 32}},
|
|
],
|
|
ids=[
|
|
'by uuid',
|
|
'by name',
|
|
'by slug',
|
|
'by name and ou by name',
|
|
'by name and ou by slug',
|
|
'by name and ou by uuid',
|
|
],
|
|
)
|
|
def test_to_internal_value_role(self, value, db, fixture):
|
|
assert NaturalKeyRelatedField(queryset=Role.objects.all()).to_internal_value(value) == fixture.role
|
|
|
|
@pytest.mark.parametrize(
|
|
'value',
|
|
[
|
|
{'name': 'role'},
|
|
{'slug': 'role'},
|
|
],
|
|
ids=['by name', 'by slug'],
|
|
)
|
|
def test_to_internal_value_role_ambiguous(self, value, db, fixture):
|
|
Role.objects.create(slug='role', name='role', ou=fixture.ou2)
|
|
with pytest.raises(ValidationError, match='multiple'):
|
|
assert (
|
|
NaturalKeyRelatedField(queryset=Role.objects.all()).to_internal_value(value) == fixture.role
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
'value',
|
|
[
|
|
{'name': 'role', 'ou': {'slug': 'ou'}},
|
|
{'slug': 'role', 'ou': {'slug': 'ou'}},
|
|
],
|
|
ids=['by name and ou', 'by slug and ou'],
|
|
)
|
|
def test_to_internal_value_role_unique(self, value, db, fixture):
|
|
Role.objects.create(slug='role', name='role', ou=fixture.ou2)
|
|
assert NaturalKeyRelatedField(queryset=Role.objects.all()).to_internal_value(value) == fixture.role
|
|
|
|
@pytest.mark.parametrize(
|
|
'value',
|
|
[
|
|
{'uuid': '2' * 32},
|
|
{'name': 'role'},
|
|
{'slug': 'role'},
|
|
{'name': 'role', 'ou': {'name': 'ou'}},
|
|
{'slug': 'role', 'ou': {'slug': 'ou'}},
|
|
{'slug': 'role', 'ou': {'uuid': '1' * 32}},
|
|
],
|
|
ids=[
|
|
'by uuid',
|
|
'by name',
|
|
'by slug',
|
|
'by name and ou by name',
|
|
'by name and ou by slug',
|
|
'by name and ou by uuid',
|
|
],
|
|
)
|
|
def test_to_internal_value_role_not_found(self, value, db, fixture):
|
|
Role.objects.all().delete()
|
|
with pytest.raises(ValidationError, match='not found'):
|
|
assert (
|
|
NaturalKeyRelatedField(queryset=Role.objects.all()).to_internal_value(value) == fixture.role
|
|
)
|
|
|
|
|
|
class TestDjangoRBACPermission:
|
|
@pytest.fixture
|
|
def permission(self):
|
|
return DjangoRBACPermission(
|
|
perms_map={
|
|
'GET': [],
|
|
'POST': ['create'],
|
|
'DELETE': [],
|
|
},
|
|
object_perms_map={
|
|
'GET': [],
|
|
'DELETE': ['delete'],
|
|
},
|
|
)
|
|
|
|
@pytest.fixture
|
|
def view(self):
|
|
view = mock.Mock()
|
|
view.get_queryset.return_value = Role.objects.all()
|
|
return view
|
|
|
|
class TestHasPermission:
|
|
def test_user_must_be_authenticated(self, permission):
|
|
request = mock.Mock()
|
|
request.user.is_authenticated = False
|
|
assert not permission.has_permission(request=request, view=mock.Mock())
|
|
|
|
def test_method_is_not_allowed(self, rf, permission):
|
|
request = mock.Mock()
|
|
request.method = 'PATCH'
|
|
request.user.is_authenticated = True
|
|
|
|
with pytest.raises(MethodNotAllowed):
|
|
permission.has_permission(request=request, view=mock.Mock())
|
|
|
|
def test_method_post(self, permission, view):
|
|
request = mock.Mock()
|
|
request.method = 'POST'
|
|
request.user.is_authenticated = True
|
|
request.user.has_perms = lambda perms, obj=None: not obj and set(perms) <= {'a2_rbac.create_role'}
|
|
assert permission.has_permission(request=request, view=view)
|
|
|
|
request.user.has_perms = lambda perms, obj=None: not obj and set(perms) <= set()
|
|
assert not permission.has_permission(request=request, view=view)
|
|
|
|
def test_method_get(self, permission, view):
|
|
request = mock.Mock()
|
|
request.user.is_authenticated = True
|
|
request.method = 'GET'
|
|
request.user.has_perms = lambda perms, obj=None: not obj and (not perms or set(perms) <= set())
|
|
assert permission.has_permission(request=request, view=view)
|
|
|
|
class TestHasObjectPermission:
|
|
def test_user_must_be_authenticated(self, permission):
|
|
request = mock.Mock()
|
|
request.user.is_authenticated = False
|
|
assert not permission.has_object_permission(request=request, view=mock.Mock(), obj=mock.Mock())
|
|
|
|
def test_method_is_not_allowed(self, rf, permission):
|
|
request = mock.Mock()
|
|
request.method = 'PATCH'
|
|
request.user.is_authenticated = True
|
|
|
|
with pytest.raises(MethodNotAllowed):
|
|
permission.has_object_permission(request=request, view=mock.Mock(), obj=mock.Mock())
|
|
|
|
def test_method_delete(self, permission, view):
|
|
request = mock.Mock()
|
|
request.method = 'DELETE'
|
|
request.user.is_authenticated = True
|
|
mock_obj = mock.Mock()
|
|
request.user.has_perms = (
|
|
lambda perms, obj=None: set(perms) <= {'a2_rbac.delete_role'} and obj is mock_obj
|
|
)
|
|
assert permission.has_object_permission(request=request, view=view, obj=mock_obj)
|
|
|
|
request.user.has_perms = mock.Mock(return_value=False)
|
|
assert not permission.has_object_permission(request=request, view=view, obj=mock_obj)
|
|
assert request.user.has_perms.call_args[1]['obj'] is mock_obj
|
|
|
|
request.method = 'GET'
|
|
request.user.has_perms = lambda perms, obj=None: not obj and set(perms) <= {'a2_rbac.create_role'}
|
|
assert permission.has_permission(request=request, view=view)
|