Skip to content

Commit

Permalink
Merge pull request #37 from colibris-framework/tests-permissions-and-…
Browse files Browse the repository at this point in the history
…more

Tests for permissions and more
  • Loading branch information
ccrisan authored Jul 30, 2019
2 parents 5fc8c01 + bdccfaf commit bdf73e0
Show file tree
Hide file tree
Showing 19 changed files with 378 additions and 70 deletions.
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')


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

0 comments on commit bdf73e0

Please sign in to comment.