mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-12-22 23:13:46 +00:00
Merge branch 'make_acl_compatible_with_api' of https://gitlab.federez.net/re2o/re2o into new_radius_api
This commit is contained in:
commit
344f836209
2 changed files with 86 additions and 20 deletions
82
re2o/acl.py
82
re2o/acl.py
|
@ -35,6 +35,7 @@ from django.contrib import messages
|
|||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from rest_framework.response import Response
|
||||
|
||||
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."""
|
||||
if permissions is None:
|
||||
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.")
|
||||
if groups:
|
||||
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:
|
||||
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
|
||||
# use it, and it is a fairly complicated piece of code. Let me guide you through
|
||||
# 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
|
||||
permission by calling model.method_name. If the flag on_instance is True,
|
||||
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
|
||||
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:
|
||||
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
|
||||
|
@ -186,7 +192,8 @@ ModelC)
|
|||
# and store it to pass it to the view.
|
||||
if on_instance:
|
||||
try:
|
||||
target = target.get_instance(target_id, *args, **kwargs)
|
||||
target = target.get_instance(
|
||||
target_id, *args, **kwargs)
|
||||
instances.append(target)
|
||||
except target.DoesNotExist:
|
||||
# A non existing instance is a valid reason to deny
|
||||
|
@ -232,28 +239,37 @@ ModelC)
|
|||
# Store the messages at the right place.
|
||||
for can, msg, permissions in process_target(target, fields, target_id):
|
||||
if not can:
|
||||
error_messages.append(acl_error_message(msg, permissions))
|
||||
error_messages.append(
|
||||
acl_error_message(msg, permissions))
|
||||
elif msg:
|
||||
warning_messages.append(acl_error_message(msg, permissions))
|
||||
warning_messages.append(
|
||||
acl_error_message(msg, permissions))
|
||||
|
||||
# Display the warning messages
|
||||
if warning_messages:
|
||||
for msg in warning_messages:
|
||||
messages.warning(request, msg)
|
||||
if not api:
|
||||
if warning_messages:
|
||||
for msg in warning_messages:
|
||||
messages.warning(request, msg)
|
||||
|
||||
# If there is any error message, then the request must be denied.
|
||||
if error_messages:
|
||||
# We display the message
|
||||
for msg in error_messages:
|
||||
messages.error(
|
||||
request,
|
||||
msg or _("You don't have the right to access this menu."),
|
||||
)
|
||||
if not api:
|
||||
for msg in error_messages:
|
||||
messages.error(
|
||||
request,
|
||||
msg or _(
|
||||
"You don't have the right to access this menu."),
|
||||
)
|
||||
# And redirect the user to the right place.
|
||||
if request.user.id is not None:
|
||||
return redirect(
|
||||
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
||||
)
|
||||
if not api:
|
||||
return redirect(
|
||||
reverse("users:profil", kwargs={
|
||||
"userid": str(request.user.id)})
|
||||
)
|
||||
else:
|
||||
return Response(data={"errors": error_messages, "warning": warning_messages}, status=403)
|
||||
else:
|
||||
return redirect(reverse("index"))
|
||||
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.")
|
||||
)
|
||||
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)
|
||||
|
||||
|
@ -369,9 +386,36 @@ def can_edit_history(view):
|
|||
"""
|
||||
if request.user.has_perm("admin.change_logentry"):
|
||||
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(
|
||||
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
||||
)
|
||||
|
||||
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)
|
||||
|
|
|
@ -61,7 +61,8 @@ class FormRevMixin(object):
|
|||
)
|
||||
elif self.changed_data:
|
||||
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)
|
||||
|
||||
|
@ -187,6 +188,27 @@ class AclMixin(object):
|
|||
(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):
|
||||
"""Check if a user can view an instance of an object
|
||||
|
||||
|
|
Loading…
Reference in a new issue