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

Merge branch 'make_acl_compatible_with_api' into 'dev'

Make acl compatible with api

See merge request re2o/re2o!576
This commit is contained in:
klafyvel 2020-12-28 21:02:37 +01:00
commit 5a4ff3583b
4 changed files with 103 additions and 32 deletions

View file

@ -239,6 +239,11 @@ class AutodetectACLPermission(permissions.BasePermission):
if getattr(view, "_ignore_model_permissions", False): if getattr(view, "_ignore_model_permissions", False):
return True return True
# Bypass permission verifications if it is a functional view
# (permissions are handled by ACL)
if not getattr(view, "queryset", getattr(view, "get_queryset", None)):
return True
if not request.user or not request.user.is_authenticated: if not request.user or not request.user.is_authenticated:
return False return False

View file

@ -150,7 +150,8 @@ def display_options(request):
optionnal_templates_list = [ optionnal_templates_list = [
app.preferences.views.aff_preferences(request) app.preferences.views.aff_preferences(request)
for app in optionnal_apps for app in optionnal_apps
if hasattr(app.preferences.views, "aff_preferences") if hasattr(app, "preferences")
and hasattr(app.preferences.views, "aff_preferences")
] ]
return form( return form(
@ -347,7 +348,10 @@ def add_switchmanagementcred(request):
messages.success(request, _("The switch management credentials were added.")) messages.success(request, _("The switch management credentials were added."))
return redirect(reverse("preferences:display-options")) return redirect(reverse("preferences:display-options"))
return form( return form(
{"preferenceform": switchmanagementcred, "action_name": _("Add"),}, {
"preferenceform": switchmanagementcred,
"action_name": _("Add"),
},
"preferences/preferences.html", "preferences/preferences.html",
request, request,
) )
@ -410,7 +414,10 @@ def add_mailcontact(request):
messages.success(request, _("The contact email address was created.")) messages.success(request, _("The contact email address was created."))
return redirect(reverse("preferences:display-options")) return redirect(reverse("preferences:display-options"))
return form( return form(
{"preferenceform": mailcontact, "action_name": _("Add"),}, {
"preferenceform": mailcontact,
"action_name": _("Add"),
},
"preferences/preferences.html", "preferences/preferences.html",
request, request,
) )

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
@ -60,7 +61,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
@ -121,6 +122,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
@ -175,8 +179,7 @@ ModelC)
# `wrapper` inside the `decorator` function, you need to read some # `wrapper` inside the `decorator` function, you need to read some
#  documentation on decorators ! #  documentation on decorators !
def decorator(view): def decorator(view):
"""The decorator to use on a specific view """The decorator to use on a specific view"""
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request""" """The wrapper used for a specific request"""
@ -243,23 +246,36 @@ ModelC)
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)
@ -310,12 +326,10 @@ def can_delete_set(model):
If none of them, return an error""" If none of them, return an error"""
def decorator(view): def decorator(view):
"""The decorator to use on a specific view """The decorator to use on a specific view"""
"""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request """The wrapper used for a specific request"""
"""
all_objects = model.objects.all() all_objects = model.objects.all()
instances_id = [] instances_id = []
for instance in all_objects: for instance in all_objects:
@ -356,8 +370,7 @@ def can_view_all(*targets):
def can_view_app(*apps_name): def can_view_app(*apps_name):
"""Decorator to check if an user can view the applications. """Decorator to check if an user can view the applications."""
"""
for app_name in apps_name: for app_name in apps_name:
assert app_name in sys.modules.keys() assert app_name in sys.modules.keys()
return acl_base_decorator( return acl_base_decorator(
@ -371,8 +384,7 @@ def can_edit_history(view):
"""Decorator to check if an user can edit history.""" """Decorator to check if an user can edit history."""
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request """The wrapper used for a specific request"""
"""
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."))
@ -381,3 +393,29 @@ def can_edit_history(view):
) )
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

@ -29,9 +29,9 @@ from django.utils.translation import ugettext as _
class RevMixin(object): class RevMixin(object):
""" A mixin to subclass the save and delete function of a model """A mixin to subclass the save and delete function of a model
to enforce the versioning of the object before those actions to enforce the versioning of the object before those actions
really happen """ really happen"""
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" Creates a version of this object and save it to database """ """ Creates a version of this object and save it to database """
@ -49,8 +49,8 @@ class RevMixin(object):
class FormRevMixin(object): class FormRevMixin(object):
""" A mixin to subclass the save function of a form """A mixin to subclass the save function of a form
to enforce the versionning of the object before it is really edited """ to enforce the versionning of the object before it is really edited"""
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" Create a version of this object and save it to database """ """ Create a version of this object and save it to database """
@ -187,6 +187,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