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:
commit
5a4ff3583b
4 changed files with 103 additions and 32 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
82
re2o/acl.py
82
re2o/acl.py
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue