Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests for permissions and more #37

Merged
merged 16 commits into from
Jul 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion colibris/authentication/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

class ModelBackend(AuthenticationBackend):
def __init__(self, model, active_field=None, inactive_field=None, **kwargs):
self.model = utils.import_member(model)
self.model = model
if isinstance(model, str):
self.model = utils.import_member(self.model)

self.active_field = active_field
self.inactive_field = inactive_field

Expand Down
3 changes: 1 addition & 2 deletions colibris/authorization/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ def get_actual_permissions(self, account, method, path):
raise NotImplementedError

def authorize(self, account, method, path, handler, required_permissions):
actual_permissions = self.get_actual_permissions(account, method, path)
required_permissions.verify(actual_permissions)
required_permissions.verify(self.get_actual_permissions(account, method, path))
5 changes: 4 additions & 1 deletion colibris/authorization/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

class ModelBackend(AuthorizationBackend):
def __init__(self, model, account_field, **kwargs):
self.model = utils.import_member(model)
self.model = model
if isinstance(model, str):
self.model = utils.import_member(self.model)

self.account_field = account_field

super().__init__(**kwargs)
Expand Down
2 changes: 1 addition & 1 deletion colibris/authorization/null.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ def authorize(self, account, method, path, handler, required_permissions):
return True

def get_actual_permissions(self, account, method, path):
return ()
return set()
4 changes: 1 addition & 3 deletions colibris/authorization/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ def combine(self, permissions):
return Permissions(self.and_set | permissions.and_set, self.or_set | permissions.or_set)

def verify(self, actual_permissions):
actual_permissions = set(actual_permissions)

# Verify permissions in and set
for p in self.and_set:
if p not in actual_permissions:
Expand All @@ -24,7 +22,7 @@ def verify(self, actual_permissions):

permissions = self.or_set & actual_permissions
if len(permissions) == 0:
raise PermissionNotMet(permissions.pop())
raise PermissionNotMet(list(self.or_set)[0])

def __str__(self):
s = ''
Expand Down
2 changes: 1 addition & 1 deletion colibris/authorization/rights.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ def get_actual_permissions(self, account, method, path):
# Gather all rights from all entries for the given account
rights = self.model.select().where(self.get_account_field() == account)

return ['{}:{}'.format(self.get_resource(r), self.get_operation(r)) for r in rights]
return {'{}:{}'.format(self.get_resource(r), self.get_operation(r)) for r in rights}
4 changes: 2 additions & 2 deletions colibris/authorization/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ def get_role(self, account):

def get_actual_permissions(self, account, method, path):
role = self.get_role(account)
actual_permissions = [role]
actual_permissions = {role}

try:
index = self.order.index(role)

except ValueError:
index = 0

actual_permissions += self.order[:index]
actual_permissions.update(self.order[:index])

return actual_permissions
11 changes: 8 additions & 3 deletions colibris/conf/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ def configure(cls, settings):
cls._instance = None

try:
backend_path = settings.pop('backend')
backend = settings.pop('backend')

except KeyError:
return # Backend class not specified

cls._class = utils.import_member(backend_path)
logger.debug('%s: using class %s', cls.__name__, backend_path)
if isinstance(backend, str):
cls._class = utils.import_member(backend)
logger.debug('%s: using class %s', cls.__name__, backend)

else:
cls._class = backend
logger.debug('%s: using class %s', cls.__name__, backend.__name__)

@classmethod
def get_instance(cls):
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@

from .functional.fixtures import *
from .unit.fixtures import *
Empty file.
10 changes: 10 additions & 0 deletions tests/unit/authorization/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import types


DUMMY_PERMISSION = 'dummy_permission'
ANOTHER_PERMISSION = 'another_permission'
YET_ANOTHER_PERMISSION = 'yet_another_permission'

DUMMY_ACCOUNT = types.SimpleNamespace(role=DUMMY_PERMISSION)
ANOTHER_ACCOUNT = types.SimpleNamespace(role=ANOTHER_PERMISSION)
67 changes: 67 additions & 0 deletions tests/unit/authorization/test_permissions_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

import pytest

from colibris.authorization import permissions

from .fixtures import DUMMY_PERMISSION, ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION


def test_conjunction():
p = permissions.Permissions(and_set={DUMMY_PERMISSION, ANOTHER_PERMISSION})

with pytest.raises(permissions.PermissionNotMet):
p.verify(set())

with pytest.raises(permissions.PermissionNotMet):
p.verify({DUMMY_PERMISSION})

with pytest.raises(permissions.PermissionNotMet):
p.verify({ANOTHER_PERMISSION})

p.verify({DUMMY_PERMISSION, ANOTHER_PERMISSION})


def test_disjunction():
p = permissions.Permissions(or_set={DUMMY_PERMISSION, ANOTHER_PERMISSION})

with pytest.raises(permissions.PermissionNotMet):
p.verify(set())

p.verify({DUMMY_PERMISSION})
p.verify({ANOTHER_PERMISSION})
p.verify({DUMMY_PERMISSION, ANOTHER_PERMISSION})


def test_conjunction_disjunction():
p = permissions.Permissions(and_set={DUMMY_PERMISSION, ANOTHER_PERMISSION},
or_set={DUMMY_PERMISSION, YET_ANOTHER_PERMISSION})

with pytest.raises(permissions.PermissionNotMet):
p.verify(set())

with pytest.raises(permissions.PermissionNotMet):
p.verify({DUMMY_PERMISSION})

with pytest.raises(permissions.PermissionNotMet):
p.verify({ANOTHER_PERMISSION})

with pytest.raises(permissions.PermissionNotMet):
p.verify({YET_ANOTHER_PERMISSION})

with pytest.raises(permissions.PermissionNotMet):
p.verify({DUMMY_PERMISSION, YET_ANOTHER_PERMISSION})

with pytest.raises(permissions.PermissionNotMet):
p.verify({ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION})

p.verify({DUMMY_PERMISSION, ANOTHER_PERMISSION})
p.verify({DUMMY_PERMISSION, ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION})


def test_combine():
p1 = permissions.Permissions(and_set={DUMMY_PERMISSION}, or_set={ANOTHER_PERMISSION})
p2 = permissions.Permissions(and_set={ANOTHER_PERMISSION}, or_set={YET_ANOTHER_PERMISSION})

c = p1.combine(p2)
assert c.and_set == {DUMMY_PERMISSION, ANOTHER_PERMISSION}
assert c.or_set == {ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION}
57 changes: 57 additions & 0 deletions tests/unit/authorization/test_rights_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

import pytest

from colibris.authorization.rights import RightsBackend
from colibris import persist


USERNAME1 = 'username1'
USERNAME2 = 'username2'

RESOURCE = 'resource'
OPERATION = 'operation'
PERMISSION = '{}:{}'.format(RESOURCE, OPERATION)


class User(persist.Model):
username = persist.CharField()


class Right(persist.Model):
user = persist.ForeignKeyField(User)
resource = persist.CharField()
operation = persist.CharField()


@pytest.fixture
def database(database_maker):
return database_maker(models=[User, Right])


@pytest.fixture
def user1(database):
return User.create(username=USERNAME1)


@pytest.fixture
def user2(database):
return User.create(username=USERNAME2)


@pytest.fixture
def right(user1):
return Right.create(user=user1, resource=RESOURCE, operation=OPERATION)


@pytest.fixture
def backend():
return RightsBackend(model=Right, account_field='user',
resource_field='resource', operation_field='operation')
ccrisan marked this conversation as resolved.
Show resolved Hide resolved


def test_allowed(backend, user1, right):
assert backend.get_actual_permissions(account=user1, method='GET', path='/') == {PERMISSION}


def test_forbidden(backend, user2, right):
assert backend.get_actual_permissions(account=user2, method='GET', path='/') == set()
20 changes: 20 additions & 0 deletions tests/unit/authorization/test_role_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

from colibris.authorization.role import RoleBackend

from .fixtures import DUMMY_PERMISSION, ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION, DUMMY_ACCOUNT, ANOTHER_ACCOUNT


def test_extract_role():
backend = RoleBackend(role_field='role')
assert backend.get_role(account=DUMMY_ACCOUNT) == DUMMY_PERMISSION


def test_actual_permissions_simple():
backend = RoleBackend(role_field='role')
assert backend.get_actual_permissions(account=DUMMY_ACCOUNT, method='GET', path='/') == {DUMMY_PERMISSION}


def test_actual_permissions_order():
backend = RoleBackend(role_field='role', order=[DUMMY_PERMISSION, ANOTHER_PERMISSION, YET_ANOTHER_PERMISSION])
permissions = backend.get_actual_permissions(account=ANOTHER_ACCOUNT, method='GET', path='/')
assert permissions == {DUMMY_PERMISSION, ANOTHER_PERMISSION}
92 changes: 92 additions & 0 deletions tests/unit/authorization/test_view_permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@

from aiohttp import web

from colibris import authorization
from colibris import views

from .fixtures import DUMMY_PERMISSION, ANOTHER_PERMISSION


class DummyView(views.View):
async def get(self):
return web.json_response({'message': 'dummy'})


class DummyViewWithClassPermission(DummyView):
permissions = {DUMMY_PERMISSION}


class DummyViewWithMethodPermission(DummyView):
@authorization.require_permission(DUMMY_PERMISSION)
async def get(self):
return await super().get()


class PermissionAuthorizationBackend(authorization.AuthorizationBackend):
def get_actual_permissions(self, account, method, path):
return {DUMMY_PERMISSION}


class NoPermissionAuthorizationBackend(authorization.AuthorizationBackend):
def get_actual_permissions(self, account, method, path):
return set()


class WrongPermissionAuthorizationBackend(authorization.AuthorizationBackend):
def get_actual_permissions(self, account, method, path):
return {ANOTHER_PERMISSION}


async def test_default_view_permissions(http_client_maker):
client = await http_client_maker(routes=[('/dummy', DummyView)])
response = await client.get('/dummy')

assert response.status == 200


async def test_class_required_permissions_fulfilled(http_client_maker):
client = await http_client_maker(routes=[('/dummy', DummyViewWithClassPermission)],
authorization_backend=PermissionAuthorizationBackend)
response = await client.get('/dummy')

assert response.status == 200


async def test_class_required_permissions_no_permissions(http_client_maker):
client = await http_client_maker(routes=[('/dummy', DummyViewWithClassPermission)],
authorization_backend=NoPermissionAuthorizationBackend)
response = await client.get('/dummy')

assert response.status == 403


async def test_class_required_permissions_wrong_permissions(http_client_maker):
client = await http_client_maker(routes=[('/dummy', DummyViewWithClassPermission)],
authorization_backend=WrongPermissionAuthorizationBackend)
response = await client.get('/dummy')

assert response.status == 403


async def test_method_required_permissions_fulfilled(http_client_maker):
client = await http_client_maker(routes=[('/dummy', DummyViewWithMethodPermission)],
authorization_backend=PermissionAuthorizationBackend)
response = await client.get('/dummy')

assert response.status == 200


async def test_method_required_permissions_no_permissions(http_client_maker):
client = await http_client_maker(routes=[('/dummy', DummyViewWithMethodPermission)],
authorization_backend=NoPermissionAuthorizationBackend)
response = await client.get('/dummy')

assert response.status == 403


async def test_method_required_permissions_wrong_permissions(http_client_maker):
client = await http_client_maker(routes=[('/dummy', DummyViewWithMethodPermission)],
authorization_backend=WrongPermissionAuthorizationBackend)
response = await client.get('/dummy')

assert response.status == 403
Loading