8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-25 22:22:26 +00:00

Add customizable ACL-based permission

This commit is contained in:
Maël Kervella 2018-06-13 22:39:37 +00:00 committed by Maël Kervella
parent 0356947e4a
commit ca0744a38c
3 changed files with 66 additions and 21 deletions

View file

@ -3,14 +3,69 @@ from re2o.acl import can_create, can_edit, can_delete, can_view_all
from . import acl from . import acl
def can_see_api(_): def can_see_api(*_, **__):
return lambda user: acl.can_view(user) return lambda user: acl.can_view(user)
class DefaultACLPermission(permissions.BasePermission): def _get_param_in_view(view, param_name):
assert hasattr(view, 'get_'+param_name) \
or getattr(view, param_name, None) is not None, (
'cannot apply {} on a view that does not set '
'`.{}` or have a `.get_{}()` method.'
).format(self.__class__.__name__, param_name, param_name)
if hasattr(view, 'get_'+param_name):
param = getattr(view, 'get_'+param_name)()
assert param is not None, (
'{}.get_{}() returned None'
).format(view.__class__.__name__, param_name)
return param
return getattr(view, param_name)
class ACLPermission(permissions.BasePermission):
"""
Permission subclass for views that requires a specific model-based
permission or don't define a queryset
"""
def get_required_permissions(self, method, view):
"""
Given a list of models and an HTTP method, return the list
of acl functions that the user is required to verify.
"""
perms_map = _get_param_in_view(view, 'perms_map')
if method not in perms_map:
raise exceptions.MethodNotAllowed(method)
return [can_see_api()] + list(perms_map[method])
def has_permission(self, request, view):
# Workaround to ensure ACLPermissions are not applied
# to the root view when using DefaultRouter.
if getattr(view, '_ignore_model_permissions', False):
return True
if not request.user or not request.user.is_authenticated:
return False
perms = self.get_required_permissions(request.method, view)
return all(perm(request.user)[0] for perm in perms)
def has_object_permission(self, request, view, obj):
# Should never be called here but documentation
# requires to implement this function
return False
class AutodetectACLPermission(permissions.BasePermission):
""" """
Permission subclass in charge of checking the ACL to determine Permission subclass in charge of checking the ACL to determine
if a user can access the models if a user can access the models. Autodetect which ACL are required
based on a queryset. Requires `.queryset` or `.get_queryset()`
to be defined in the view.
""" """
perms_map = { perms_map = {
'GET': [can_see_api, lambda model: model.can_view_all], 'GET': [can_see_api, lambda model: model.can_view_all],
@ -46,29 +101,17 @@ class DefaultACLPermission(permissions.BasePermission):
Given an object and an HTTP method, return the list of acl Given an object and an HTTP method, return the list of acl
functions that the user is required to verify. functions that the user is required to verify.
""" """
if method not in self.perms_map: if method not in self.perms_obj_map:
raise exceptions.MethodNotAllowed(method) raise exceptions.MethodNotAllowed(method)
return [perm(obj) for perm in self.perms_map[method]] return [perm(obj) for perm in self.perms_obj_map[method]]
def _queryset(self, view): def _queryset(self, view):
""" """
Return the queryset associated with view and raise an error Return the queryset associated with view and raise an error
is there is none. is there is none.
""" """
assert hasattr(view, 'get_queryset') \ return _get_param_in_view(view, 'queryset')
or getattr(view, 'queryset', None) is not None, (
'Cannot apply {} on a view that does not set '
'`.queryset` or have a `.get_queryset()` method.'
).format(self.__class__.__name__)
if hasattr(view, 'get_queryset'):
queryset = view.get_queryset()
assert queryset is not None, (
'{}.get_queryset() returned None'.format(view.__class__.__name__)
)
return queryset
return view.queryset
def has_permission(self, request, view): def has_permission(self, request, view):
# Workaround to ensure ACLPermissions are not applied # Workaround to ensure ACLPermissions are not applied

View file

@ -33,7 +33,7 @@ REST_FRAMEWORK = {
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
), ),
'DEFAULT_PERMISSION_CLASSES': ( 'DEFAULT_PERMISSION_CLASSES': (
'api.permissions.DefaultACLPermission', 'api.permissions.AutodetectACLPermission',
), ),
'DEFAULT_PAGINATION_CLASS': 'api.pagination.PageSizedPagination', 'DEFAULT_PAGINATION_CLASS': 'api.pagination.PageSizedPagination',
'PAGE_SIZE': 100 'PAGE_SIZE': 100

View file

@ -43,6 +43,7 @@ from re2o.utils import all_active_interfaces, all_has_access
from . import serializers from . import serializers
from .pagination import PageSizedPagination from .pagination import PageSizedPagination
from .permissions import ACLPermission
# COTISATIONS APP # COTISATIONS APP
@ -351,10 +352,11 @@ class DNSZonesView(generics.ListAPIView):
class StandardMailingView(views.APIView): class StandardMailingView(views.APIView):
pagination_class = PageSizedPagination pagination_class = PageSizedPagination
get_queryset = lambda self: all_has_access() permission_classes = (ACLPermission, )
perms_map = {'GET' : [users.User.can_view_all]}
def get(self, request, format=None): def get(self, request, format=None):
adherents_data = serializers.MailingMemberSerializer(self.get_queryset(), many=True).data adherents_data = serializers.MailingMemberSerializer(all_has_access(), many=True).data
data = [{'name': 'adherents', 'members': adherents_data}] data = [{'name': 'adherents', 'members': adherents_data}]
paginator = self.pagination_class() paginator = self.pagination_class()
paginator.paginate_queryset(data, request) paginator.paginate_queryset(data, request)