mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-09-12 09:13:08 +00:00
Add customizable ACL-based permission
This commit is contained in:
parent
0356947e4a
commit
ca0744a38c
3 changed files with 66 additions and 21 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue