Skip to content

Commit

Permalink
Merge pull request #35 from colibris-framework/feature-views-require-…
Browse files Browse the repository at this point in the history
…auth-flag

Feature: authentication_required views flag
  • Loading branch information
ccrisan authored Jul 26, 2019
2 parents 51b3db6 + b9dad39 commit 423119c
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 38 deletions.
13 changes: 13 additions & 0 deletions colibris/authentication/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@
logger = logging.getLogger(__name__)


def require_authentication(required=True):
def decorator(handler):
handler.authentication_required = required

return handler

return decorator


def get_authentication_required(handler):
return getattr(handler, 'authentication_required', None)


def authenticate(request):
account = AuthenticationBackend.get_instance().authenticate(request)
request[_REQUEST_ACCOUNT_ITEM_NAME] = account
Expand Down
3 changes: 3 additions & 0 deletions colibris/authorization/null.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ def authorize(self, account, method, path, handler, required_permissions):
# Consider any authenticated request authorized

return True

def get_actual_permissions(self, account, method, path):
return ()
22 changes: 7 additions & 15 deletions colibris/authorization/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
from .exceptions import PermissionNotMet


ANY_PERMISSION = ()


class Permissions:
def __init__(self, and_set=None, or_set=None):
self.and_set = set(and_set or ())
Expand Down Expand Up @@ -44,6 +41,9 @@ def __str__(self):

return s or '*'

def __bool__(self):
return bool(self.and_set) or bool(self.or_set)


def _require_permissions(and_set=None, or_set=None):
and_set = and_set or set()
Expand All @@ -53,8 +53,8 @@ def _require_permissions(and_set=None, or_set=None):

def decorator(handler):
# Combine any existing permissions with the new ones
required_permissions = get_required_permissions(handler) or Permissions()
handler.required_permissions = required_permissions.combine(new_permissions)
permissions = get_required_permissions(handler) or Permissions()
handler.permissions = permissions.combine(new_permissions)

return handler

Expand All @@ -65,10 +65,6 @@ def require_permission(permission):
return _require_permissions(and_set=[permission])


def require_any_permission():
return _require_permissions()


def require_one_permission(permissions):
return _require_permissions(or_set=permissions)

Expand All @@ -78,20 +74,16 @@ def require_all_permissions(permissions):


def get_required_permissions(handler):
permissions = getattr(handler, 'required_permissions', None)
permissions = getattr(handler, 'permissions', None)
if permissions is None:
return

# Permissions can be stored as:
# * one single permission
# * a set of permissions
# * a Permissions instance

# Normalize any possible way of storing permissions
# Normalize possible ways of storing permissions
if not isinstance(permissions, Permissions):
if not isinstance(permissions, (set, list, tuple)):
permissions = {permissions}

permissions = Permissions(and_set=permissions)

return permissions
44 changes: 27 additions & 17 deletions colibris/middleware/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from colibris import authentication
from colibris import authorization
from colibris import views
from colibris.authorization.permissions import get_required_permissions


@web.middleware
Expand All @@ -23,35 +22,46 @@ async def handle_auth(request, handler):

original_handler = request.match_info.handler

# First look for required permissions in the view handler itself (class or function)
required_permissions = get_required_permissions(original_handler)
# First look for auth info in the view handler itself (class or function)
authentication_required = authentication.get_authentication_required(original_handler)
permissions = authorization.get_required_permissions(original_handler)

# Then, if we've got a class-based view, look for required permissions in method function
if inspect.isclass(original_handler) and issubclass(original_handler, views.View):
method_func = getattr(original_handler, method.lower(), None)
if method_func:
method_func_required_permissions = get_required_permissions(method_func)
if method_func_required_permissions and required_permissions:
required_permissions = required_permissions.combine(method_func_required_permissions)
method_func_authentication_required = authentication.get_authentication_required(method_func)
method_func_permissions = authorization.get_required_permissions(method_func)

if method_func_permissions and permissions:
permissions = permissions.combine(method_func_permissions)

else:
required_permissions = required_permissions or method_func_required_permissions
permissions = permissions or method_func_permissions

try:
account = authentication.authenticate(request)
if method_func_authentication_required is not None:
authentication_required = method_func_authentication_required

except authentication.AuthenticationException:
if required_permissions is not None:
raise api.UnauthenticatedException()
# A value of None for authentication_required indicates decision based on permissions
if authentication_required is None:
authentication_required = bool(permissions)

account = None
if not authentication_required and permissions:
raise authorization.AuthorizationException('view requires permissions but does not require authentication')

if required_permissions is not None:
if authentication_required:
try:
authorization.authorize(account, method, path, original_handler, required_permissions)
account = authentication.authenticate(request)

except authentication.AuthenticationException:
raise api.UnauthenticatedException()

if permissions is not None:
try:
authorization.authorize(account, method, path, original_handler, permissions)

except authorization.AuthorizationException:
raise api.ForbiddenException()
except authorization.AuthorizationException:
raise api.ForbiddenException()

response = await handler(request)
response = authentication.process_response(request, response)
Expand Down
7 changes: 2 additions & 5 deletions colibris/skeleton/__packagename__/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from colibris import views
from colibris.conf import settings
from colibris.authentication import get_account
from colibris.authorization import ANY_PERMISSION
from colibris.views.generic import RetrieveUpdateDestroyModelView, ListCreateModelView

from __packagename__ import constants
Expand All @@ -30,8 +29,6 @@ async def get(self):


class MeView(views.View):
required_permissions = ANY_PERMISSION

@docs(tags=['Users'], summary='Reveal details about the current user')
@response_schema(schemas.UserSchema())
async def get(self):
Expand All @@ -42,7 +39,7 @@ async def get(self):


class UsersView(ListCreateModelView):
required_permissions = constants.ROLE_ADMIN
permissions = {constants.ROLE_ADMIN}
body_schema_class = schemas.UserSchema
model = models.User

Expand All @@ -56,7 +53,7 @@ async def post(self):


class UserView(RetrieveUpdateDestroyModelView):
required_permissions = constants.ROLE_ADMIN
permissions = {constants.ROLE_ADMIN}
body_schema_class = schemas.UserSchema
model = models.User

Expand Down
3 changes: 2 additions & 1 deletion colibris/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ def __init__(cls, name, bases, attrs):


class View(web.View, metaclass=ViewMeta):
pass
authentication_required = None
permissions = None

0 comments on commit 423119c

Please sign in to comment.