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

modification des acl pour gérer les vues de l'api

This commit is contained in:
chapeau 2020-11-29 17:17:42 +01:00
parent 108d0201d2
commit 9720d80e1c
2 changed files with 86 additions and 20 deletions

View file

@ -35,6 +35,7 @@ from django.contrib import messages
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework.response import Response
from re2o.utils import get_group_having_permission from re2o.utils import get_group_having_permission
@ -43,11 +44,13 @@ def acl_error_message(msg, permissions):
"""Create an error message for msg and permissions.""" """Create an error message for msg and permissions."""
if permissions is None: if permissions is None:
return msg return msg
groups = ", ".join([g.name for g in get_group_having_permission(*permissions)]) groups = ", ".join(
[g.name for g in get_group_having_permission(*permissions)])
message = msg or _("You don't have the right to edit this option.") message = msg or _("You don't have the right to edit this option.")
if groups: if groups:
return ( return (
message + _("You need to be a member of one of these groups: %s.") % groups message +
_("You need to be a member of one of these groups: %s.") % groups
) )
else: else:
return message + _("No group has the %s permission(s)!") % " or ".join( return message + _("No group has the %s permission(s)!") % " or ".join(
@ -60,7 +63,7 @@ def acl_error_message(msg, permissions):
# This is the function of main interest of this file. Almost all the decorators # This is the function of main interest of this file. Almost all the decorators
# use it, and it is a fairly complicated piece of code. Let me guide you through # use it, and it is a fairly complicated piece of code. Let me guide you through
# this ! 🌈😸 # this ! 🌈😸
def acl_base_decorator(method_name, *targets, on_instance=True): def acl_base_decorator(method_name, *targets, on_instance=True, api=False):
"""Base decorator for acl. It checks if the `request.user` has the """Base decorator for acl. It checks if the `request.user` has the
permission by calling model.method_name. If the flag on_instance is True, permission by calling model.method_name. If the flag on_instance is True,
tries to get an instance of the model by calling tries to get an instance of the model by calling
@ -115,6 +118,9 @@ on_instance=False)
method `get_instance` of the model, with the arguments originally method `get_instance` of the model, with the arguments originally
passed to the view. passed to the view.
api: when set to True, errors will no longer trigger redirection and
messages but will send a 403 with errors in JSON
Returns: Returns:
The user is either redirected to their own page with an explanation The user is either redirected to their own page with an explanation
message if at least one access is not granted, or to the view. In order message if at least one access is not granted, or to the view. In order
@ -186,7 +192,8 @@ ModelC)
# and store it to pass it to the view. # and store it to pass it to the view.
if on_instance: if on_instance:
try: try:
target = target.get_instance(target_id, *args, **kwargs) target = target.get_instance(
target_id, *args, **kwargs)
instances.append(target) instances.append(target)
except target.DoesNotExist: except target.DoesNotExist:
# A non existing instance is a valid reason to deny # A non existing instance is a valid reason to deny
@ -232,28 +239,37 @@ ModelC)
# Store the messages at the right place. # Store the messages at the right place.
for can, msg, permissions in process_target(target, fields, target_id): for can, msg, permissions in process_target(target, fields, target_id):
if not can: if not can:
error_messages.append(acl_error_message(msg, permissions)) error_messages.append(
acl_error_message(msg, permissions))
elif msg: elif msg:
warning_messages.append(acl_error_message(msg, permissions)) warning_messages.append(
acl_error_message(msg, permissions))
# Display the warning messages # Display the warning messages
if warning_messages: if not api:
for msg in warning_messages: if warning_messages:
messages.warning(request, msg) for msg in warning_messages:
messages.warning(request, msg)
# If there is any error message, then the request must be denied. # If there is any error message, then the request must be denied.
if error_messages: if error_messages:
# We display the message # We display the message
for msg in error_messages: if not api:
messages.error( for msg in error_messages:
request, messages.error(
msg or _("You don't have the right to access this menu."), request,
) msg or _(
"You don't have the right to access this menu."),
)
# And redirect the user to the right place. # And redirect the user to the right place.
if request.user.id is not None: if request.user.id is not None:
return redirect( if not api:
reverse("users:profil", kwargs={"userid": str(request.user.id)}) return redirect(
) reverse("users:profil", kwargs={
"userid": str(request.user.id)})
)
else:
return Response(data={"errors": error_messages, "warning": warning_messages}, status=403)
else: else:
return redirect(reverse("index")) return redirect(reverse("index"))
return view(request, *chain(instances, args), **kwargs) return view(request, *chain(instances, args), **kwargs)
@ -322,7 +338,8 @@ def can_delete_set(model):
request, _("You don't have the right to access this menu.") request, _("You don't have the right to access this menu.")
) )
return redirect( return redirect(
reverse("users:profil", kwargs={"userid": str(request.user.id)}) reverse("users:profil", kwargs={
"userid": str(request.user.id)})
) )
return view(request, instances, *args, **kwargs) return view(request, instances, *args, **kwargs)
@ -369,9 +386,36 @@ def can_edit_history(view):
""" """
if request.user.has_perm("admin.change_logentry"): if request.user.has_perm("admin.change_logentry"):
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
messages.error(request, _("You don't have the right to edit the history.")) messages.error(request, _(
"You don't have the right to edit the history."))
return redirect( return redirect(
reverse("users:profil", kwargs={"userid": str(request.user.id)}) reverse("users:profil", kwargs={"userid": str(request.user.id)})
) )
return wrapper return wrapper
def can_view_all_api(*models):
"""Decorator to check if an user can see an api page
Only used on functionnal api views (class-based api views ACL are checked
in api/permissions.py)
"""
return acl_base_decorator("can_view_all", *models, on_instance=False, api=True)
def can_edit_all_api(*models):
"""Decorator to check if an user can edit via the api
We do not always know which instances will be edited, so we may need to know
if the user can edit any instance.
Only used on functionnal api views (class-based api views ACL are checked
in api/permissions.py)
"""
return acl_base_decorator("can_edit_all", *models, on_instance=False, api=True)
def can_create_api(*models):
"""Decorator to check if an user can create the given models. via the api
Only used on functionnal api views (class-based api views ACL are checked
in api/permissions.py)
"""
return acl_base_decorator("can_create", *models, on_instance=False, api=True)

View file

@ -61,7 +61,8 @@ class FormRevMixin(object):
) )
elif self.changed_data: elif self.changed_data:
reversion.set_comment( reversion.set_comment(
"Field(s) edited: %s" % ", ".join(field for field in self.changed_data) "Field(s) edited: %s" % ", ".join(
field for field in self.changed_data)
) )
return super(FormRevMixin, self).save(*args, **kwargs) return super(FormRevMixin, self).save(*args, **kwargs)
@ -187,6 +188,27 @@ class AclMixin(object):
(permission,), (permission,),
) )
@classmethod
def can_edit_all(cls, user_request, *_args, **_kwargs):
"""Check if a user can edit all instances of an object
Parameters:
user_request: User calling for this action
Returns:
Boolean: True if user_request has the right access to do it, else
false with reason for reject authorization
"""
permission = cls.get_modulename() + ".change_" + cls.get_classname()
can = user_request.has_perm(permission)
return (
can,
_("You don't have the right to edit every %s object.") % cls.get_classname()
if not can
else None,
(permission,),
)
def can_view(self, user_request, *_args, **_kwargs): def can_view(self, user_request, *_args, **_kwargs):
"""Check if a user can view an instance of an object """Check if a user can view an instance of an object