8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-23 15:33:45 +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):
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:
return False

View file

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

View file

@ -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
@ -60,7 +61,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
@ -121,6 +122,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
@ -175,8 +179,7 @@ ModelC)
# `wrapper` inside the `decorator` function, you need to read some
#  documentation on decorators !
def decorator(view):
"""The decorator to use on a specific view
"""
"""The decorator to use on a specific view"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request"""
@ -243,6 +246,7 @@ ModelC)
warning_messages.append(acl_error_message(msg, permissions))
# Display the warning messages
if not api:
if warning_messages:
for msg in warning_messages:
messages.warning(request, msg)
@ -250,6 +254,7 @@ ModelC)
# If there is any error message, then the request must be denied.
if error_messages:
# We display the message
if not api:
for msg in error_messages:
messages.error(
request,
@ -257,8 +262,19 @@ ModelC)
)
# And redirect the user to the right place.
if request.user.id is not None:
if not api:
return redirect(
reverse("users:profil", kwargs={"userid": str(request.user.id)})
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"))
@ -310,12 +326,10 @@ def can_delete_set(model):
If none of them, return an error"""
def decorator(view):
"""The decorator to use on a specific view
"""
"""The decorator to use on a specific view"""
def wrapper(request, *args, **kwargs):
"""The wrapper used for a specific request
"""
"""The wrapper used for a specific request"""
all_objects = model.objects.all()
instances_id = []
for instance in all_objects:
@ -356,8 +370,7 @@ def can_view_all(*targets):
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:
assert app_name in sys.modules.keys()
return acl_base_decorator(
@ -371,8 +384,7 @@ def can_edit_history(view):
"""Decorator to check if an user can edit history."""
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"):
return view(request, *args, **kwargs)
messages.error(request, _("You don't have the right to edit the history."))
@ -381,3 +393,29 @@ def can_edit_history(view):
)
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):
""" 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
really happen """
really happen"""
def save(self, *args, **kwargs):
""" Creates a version of this object and save it to database """
@ -49,8 +49,8 @@ class RevMixin(object):
class FormRevMixin(object):
""" A mixin to subclass the save function of a form
to enforce the versionning of the object before it is really edited """
"""A mixin to subclass the save function of a form
to enforce the versionning of the object before it is really edited"""
def save(self, *args, **kwargs):
""" Create a version of this object and save it to database """
@ -187,6 +187,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