8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-23 23:43:47 +00:00

Merge branch 'fix_acl' into 'dev'

Fix acl

See merge request re2o/re2o!555
This commit is contained in:
chirac 2020-08-29 12:49:51 +02:00
commit d5cfe2edd2
12 changed files with 103 additions and 28 deletions

View file

@ -56,7 +56,7 @@ def _create_api_permission():
_create_api_permission() _create_api_permission()
def can_view(user): def can_view(user, *args, **kwargs):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args:

View file

@ -28,7 +28,7 @@ Here are defined some functions to check acl on the application.
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
def can_view(user): def can_view(user, *args, **kwargs):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args:

View file

@ -28,7 +28,7 @@ Here are defined some functions to check acl on the application.
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
def can_view(user): def can_view(user, *args, **kwargs):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args:
@ -41,7 +41,6 @@ def can_view(user):
can = user.has_module_perms("admin") can = user.has_module_perms("admin")
return ( return (
can, can,
None if can else _("You don't have the right to view this" None if can else _("You don't have the right to view this" " application."),
" application."), ("logs",),
"admin",
) )

View file

@ -98,7 +98,13 @@ from re2o.utils import (
all_active_interfaces_count, all_active_interfaces_count,
) )
from re2o.base import re2o_paginator, SortTable from re2o.base import re2o_paginator, SortTable
from re2o.acl import can_view_all, can_view_app, can_edit_history, can_view from re2o.acl import (
can_view_all,
can_view_app,
can_edit_history,
can_view,
acl_error_message,
)
from .models import ( from .models import (
ActionsSearch, ActionsSearch,
@ -109,6 +115,8 @@ from .models import (
from .forms import ActionsSearchForm, MachineHistorySearchForm from .forms import ActionsSearchForm, MachineHistorySearchForm
from .acl import can_view as can_view_logs
@login_required @login_required
@can_view_app("logs") @can_view_app("logs")
@ -536,10 +544,11 @@ def get_history_object(request, model, object_name, object_id):
instance = None instance = None
if instance is None: if instance is None:
authorized = can_view_app("logs") authorized, msg, permissions = can_view_logs(request.user)
msg = None
else: else:
authorized, msg, _permissions = instance.can_view(request.user) authorized, msg, permissions = instance.can_view(request.user)
msg = acl_error_message(msg, permissions)
if not authorized: if not authorized:
messages.error( messages.error(
@ -581,7 +590,7 @@ def history(request, application, object_name, object_id):
raise Http404(_("No model found.")) raise Http404(_("No model found."))
authorized, instance = get_history_object(request, model, object_name, object_id) authorized, instance = get_history_object(request, model, object_name, object_id)
if not can_view: if not authorized:
return instance return instance
history = get_history_class(model) history = get_history_class(model)

View file

@ -28,7 +28,7 @@ Here are defined some functions to check acl on the application.
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
def can_view(user): def can_view(user, *args, **kwargs):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args:
@ -41,7 +41,6 @@ def can_view(user):
can = user.has_module_perms("machines") can = user.has_module_perms("machines")
return ( return (
can, can,
None if can else _("You don't have the right to view this" None if can else _("You don't have the right to view this" " application."),
" application."),
("machines",), ("machines",),
) )

View file

@ -28,7 +28,7 @@ Here are defined some functions to check acl on the application.
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
def can_view(user): def can_view(user, *args, **kwargs):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args:
@ -41,7 +41,6 @@ def can_view(user):
can = user.has_module_perms("preferences") can = user.has_module_perms("preferences")
return ( return (
can, can,
None if can else _("You don't have the right to view this" None if can else _("You don't have the right to view this" " application."),
" application."),
("preferences",), ("preferences",),
) )

View file

@ -57,6 +57,9 @@ 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):
"""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,
@ -128,22 +131,43 @@ ModelC)
where `*args` and `**kwargs` are the original view arguments. where `*args` and `**kwargs` are the original view arguments.
""" """
# First we define a utilitary functions. This is what parses the input of
#  the decorator. It will group a target (i.e. a model class) with a list
# of associated fields (possibly empty).
def group_targets(): def group_targets():
"""This generator parses the targets of the decorator, yielding """This generator parses the targets of the decorator, yielding
2-tuples of (model, [fields]). 2-tuples of (model, [fields]).
""" """
current_target = None current_target = None
current_fields = [] current_fields = []
# We iterate over all the possible target passed in argument of the
# decorator. Let's call the `target` variable a target candidate.
# We basically want to discriminate the valid targets over the field
# names.
for target in targets: for target in targets:
# We enter this conditional block if the current target is not
# a string, i.e. if it is not a field name, i.e. it is a model
# name.
if not isinstance(target, str): if not isinstance(target, str):
# if the current target is defined, this means we already
# encountered a valid target and we have been storing field
# names ever since. This group is ready and we can `yield` it.
if current_target: if current_target:
yield (current_target, current_fields) yield (current_target, current_fields)
# Then we define the current target and reset its fields.
current_target = target current_target = target
current_fields = [] current_fields = []
else: else:
# When we encounter a string, this is not valid target and is
# thus a field name. We store it for later.
current_fields.append(target) current_fields.append(target)
# We need to yield the last pair of target and fields.
yield (current_target, current_fields) yield (current_target, current_fields)
# Now to the main topic ! if you are not sure why we need to use a function
# `wrapper` inside the `decorator` function, you need to read some
#  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
""" """
@ -158,43 +182,74 @@ ModelC)
stores the instances of models in order to avoid duplicate DB stores the instances of models in order to avoid duplicate DB
calls for the view. calls for the view.
""" """
# When working on instances, retrieve the associated instance
# 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
# access to the view.
yield False, _("Nonexistent entry."), [] yield False, _("Nonexistent entry."), []
return return
# Now we can actually make the ACL test, using the right ACL
# method.
if hasattr(target, method_name): if hasattr(target, method_name):
can_fct = getattr(target, method_name) can_fct = getattr(target, method_name)
yield can_fct(request.user, *args, **kwargs) yield can_fct(request.user, *args, **kwargs)
# If working on fields, iterate through the concerned ones
# and check that the user can change this field. (this is
# the only available ACL for fields)
for field in fields: for field in fields:
can_change_fct = getattr(target, "can_change_" + field) can_change_fct = getattr(target, "can_change_" + field)
yield can_change_fct(request.user, *args, **kwargs) yield can_change_fct(request.user, *args, **kwargs)
# Now to the main loop. We are going iterate through the targets
# pairs (remember the `group_targets` function) and the keyword
# arguments of the view to retrieve the associated model instances
# and check that the user making the request is authorized to do it
# as well as storing the the associated error and warning messages.
error_messages = [] error_messages = []
warning_messages = [] warning_messages = []
for arg_key, (target, fields) in zip(kwargs.keys(), group_targets()):
if on_instance: if on_instance:
iterator = zip(kwargs.keys(), group_targets())
else:
iterator = group_targets()
for it in iterator:
# If the decorator must work on instances, retrieve the
# associated instance.
if on_instance:
arg_key, (target, fields) = it
target_id = int(kwargs[arg_key]) target_id = int(kwargs[arg_key])
else: else:
target, fields = it
target_id = None target_id = None
# 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
if warning_messages: if warning_messages:
for msg in warning_messages: for msg in warning_messages:
messages.warning(request, msg) messages.warning(request, msg)
# If there is any error message, then the request must be denied.
if error_messages: if error_messages:
# We display the message
for msg in error_messages: for msg in error_messages:
messages.error( messages.error(
request, request,
msg or _("You don't have the right to access this menu."), 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: if request.user.id is not None:
return redirect( return redirect(
reverse("users:profil", kwargs={"userid": str(request.user.id)}) reverse("users:profil", kwargs={"userid": str(request.user.id)})

View file

@ -31,6 +31,7 @@ from django import template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.forms import TextInput from django.forms import TextInput
from django.forms.widgets import Select from django.forms.widgets import Select
from django.utils.translation import ugettext_lazy as _
from bootstrap3.utils import render_tag from bootstrap3.utils import render_tag
from bootstrap3.forms import render_field from bootstrap3.forms import render_field
@ -244,6 +245,7 @@ class MBFForm:
self.html += mbf_field.render() self.html += mbf_field.render()
else: else:
f = field.get_bound_field(self.form, name), self.args, self.kwargs
self.html += render_field( self.html += render_field(
field.get_bound_field(self.form, name), field.get_bound_field(self.form, name),
*self.args, *self.args,
@ -406,7 +408,10 @@ class MBFField:
self.html += render_field(self.bound, *self.args, **self.kwargs) self.html += render_field(self.bound, *self.args, **self.kwargs)
self.field.widget = TextInput( self.field.widget = TextInput(
attrs={"name": "mbf_" + self.name, "placeholder": self.field.empty_label} attrs={
"name": "mbf_" + self.name,
"placeholder": getattr(self.field, "empty_label", _("Nothing")),
}
) )
self.replace_input = render_field(self.bound, *self.args, **self.kwargs) self.replace_input = render_field(self.bound, *self.args, **self.kwargs)

View file

@ -38,7 +38,7 @@ from __future__ import unicode_literals
from django.utils import timezone from django.utils import timezone
from django.db.models import Q from django.db.models import Q
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission, Group
from cotisations.models import Cotisation, Facture, Vente from cotisations.models import Cotisation, Facture, Vente
from machines.models import Interface, Machine from machines.models import Interface, Machine
@ -58,11 +58,20 @@ def get_group_having_permission(*permission_name):
""" """
groups = set() groups = set()
for name in permission_name: for name in permission_name:
if "." in name:
app_label, codename = name.split(".") app_label, codename = name.split(".")
permission = Permission.objects.get( permission = Permission.objects.get(
content_type__app_label=app_label, codename=codename content_type__app_label=app_label, codename=codename
) )
groups = groups.union(permission.group_set.all()) groups = groups.union(permission.group_set.all())
else:
groups = groups.union(
Group.objects.filter(
permissions__in=Permission.objects.filter(
content_type__app_label="users"
)
).distinct()
)
return groups return groups

View file

@ -27,7 +27,7 @@ Here are defined some functions to check acl on the application.
""" """
def can_view(_user): def can_view(*args, **kwargs):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args:

View file

@ -28,7 +28,7 @@ Here are defined some functions to check acl on the application.
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
def can_view(user): def can_view(user, *args, **kwargs):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args:

View file

@ -28,7 +28,7 @@ Here are defined some functions to check acl on the application.
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
def can_view(user): def can_view(user, *args, **kwargs):
"""Check if an user can view the application. """Check if an user can view the application.
Args: Args: