2017-12-29 00:51:24 +00:00
|
|
|
|
# -*- mode: python; coding: utf-8 -*-
|
2020-11-23 16:06:37 +00:00
|
|
|
|
# Re2o est un logiciel d'administration développé initiallement au Rézo Metz. Il
|
2017-12-29 00:51:24 +00:00
|
|
|
|
# se veut agnostique au réseau considéré, de manière à être installable en
|
|
|
|
|
# quelques clics.
|
|
|
|
|
#
|
|
|
|
|
# Copyright © 2017 Gabriel Détraz
|
2019-09-29 14:02:28 +00:00
|
|
|
|
# Copyright © 2017 Lara Kermarec
|
2017-12-29 00:51:24 +00:00
|
|
|
|
# Copyright © 2017 Augustin Lemesle
|
|
|
|
|
#
|
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU General Public License along
|
|
|
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
|
|
|
|
|
|
"""Handles ACL for re2o.
|
|
|
|
|
|
|
|
|
|
Here are defined some decorators that can be used in views to handle ACL.
|
|
|
|
|
"""
|
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
|
|
|
|
import sys
|
2018-05-07 15:20:55 +00:00
|
|
|
|
from itertools import chain
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
2018-05-07 15:20:55 +00:00
|
|
|
|
from django.db.models import Model
|
2017-12-29 00:51:24 +00:00
|
|
|
|
from django.contrib import messages
|
|
|
|
|
from django.shortcuts import redirect
|
|
|
|
|
from django.urls import reverse
|
2018-06-23 17:53:10 +00:00
|
|
|
|
from django.utils.translation import ugettext as _
|
2020-11-29 16:17:42 +00:00
|
|
|
|
from rest_framework.response import Response
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
2019-09-05 23:08:53 +00:00
|
|
|
|
from re2o.utils import get_group_having_permission
|
|
|
|
|
|
|
|
|
|
|
2019-09-06 14:05:45 +00:00
|
|
|
|
def acl_error_message(msg, permissions):
|
|
|
|
|
"""Create an error message for msg and permissions."""
|
2019-09-09 10:16:37 +00:00
|
|
|
|
if permissions is None:
|
|
|
|
|
return msg
|
2020-12-28 19:59:35 +00:00
|
|
|
|
groups = ", ".join([g.name for g in get_group_having_permission(*permissions)])
|
2019-11-16 14:12:15 +00:00
|
|
|
|
message = msg or _("You don't have the right to edit this option.")
|
2019-09-06 14:05:45 +00:00
|
|
|
|
if groups:
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return (
|
2020-12-28 19:59:35 +00:00
|
|
|
|
message + _("You need to be a member of one of these groups: %s.") % groups
|
2019-11-04 16:55:03 +00:00
|
|
|
|
)
|
2019-09-06 14:05:45 +00:00
|
|
|
|
else:
|
2019-11-16 14:12:15 +00:00
|
|
|
|
return message + _("No group has the %s permission(s)!") % " or ".join(
|
2019-11-04 16:55:03 +00:00
|
|
|
|
[",".join(permissions[:-1]), permissions[-1]]
|
|
|
|
|
if len(permissions) > 2
|
|
|
|
|
else permissions
|
2019-09-06 14:05:45 +00:00
|
|
|
|
)
|
2019-09-05 23:08:53 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# 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 ! 🌈😸
|
2020-11-29 16:17:42 +00:00
|
|
|
|
def acl_base_decorator(method_name, *targets, on_instance=True, api=False):
|
2018-05-07 16:57:08 +00:00
|
|
|
|
"""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
|
2020-12-28 19:50:00 +00:00
|
|
|
|
`model.get_instance(obj_id, *args, **kwargs)` and runs `instance.mehod_name`
|
2018-05-07 16:57:08 +00:00
|
|
|
|
rather than model.method_name.
|
|
|
|
|
|
|
|
|
|
It is not intended to be used as is. It is a base for others ACL
|
2020-12-28 19:50:00 +00:00
|
|
|
|
decorators. Beware, if you redefine the `get_instance` method for your
|
|
|
|
|
model, give it a signature such as
|
|
|
|
|
`def get_instance(cls, object_id, *_args, **_kwargs)`, because you will
|
|
|
|
|
likely have an url with a named parameter "userid" if *e.g.* your model
|
|
|
|
|
is an user. Otherwise, if the parameter name in `get_instance` was also
|
|
|
|
|
`userid`, then `get_instance` would end up having two identical parameter
|
|
|
|
|
passed on, and this would result in a `TypeError` exception.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
method_name: The name of the method which is to to be used for ACL.
|
|
|
|
|
(ex: 'can_edit') WARNING: if no method called 'method_name' exists,
|
|
|
|
|
then no error will be triggered, the decorator will act as if
|
|
|
|
|
permission was granted. This is to allow you to run ACL tests on
|
|
|
|
|
fields only. If the method exists, it has to return a 2-tuple
|
2019-09-05 23:08:53 +00:00
|
|
|
|
`(can, reason, permissions)` with `can` being a boolean stating
|
2019-09-09 10:16:37 +00:00
|
|
|
|
whether the access is granted, `reason` an arror message to be
|
2019-09-05 23:08:53 +00:00
|
|
|
|
displayed if `can` equals `False` (can be `None`) and `permissions`
|
2019-09-09 10:16:37 +00:00
|
|
|
|
a list of permissions needed for access (can be `None`). If can is
|
|
|
|
|
True and permission is not `None`, a warning message will be
|
|
|
|
|
displayed.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
*targets: The targets. Targets are specified like a sequence of models
|
|
|
|
|
and fields names. As an example
|
|
|
|
|
```
|
|
|
|
|
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', \
|
|
|
|
|
ModelB, ModelC, 'field3', on_instance=False)
|
|
|
|
|
```
|
|
|
|
|
will make the following calls (where `user` is the current user,
|
|
|
|
|
`*args` and `**kwargs` are the arguments initially passed to the
|
|
|
|
|
view):
|
|
|
|
|
- `ModelA.can_edit(user, *args, **kwargs)`
|
|
|
|
|
- `ModelA.can_change_field1(user, *args, **kwargs)`
|
|
|
|
|
- `ModelA.can_change_field2(user, *args, **kwargs)`
|
|
|
|
|
- `ModelB.can_edit(user, *args, **kwargs)`
|
|
|
|
|
- `ModelC.can_edit(user, *args, **kwargs)`
|
|
|
|
|
- `ModelC.can_change_field3(user, *args, **kwargs)`
|
|
|
|
|
|
|
|
|
|
Note that
|
|
|
|
|
```
|
|
|
|
|
acl_base_decorator('can_edit', 'field1', ModelA, 'field2', \
|
|
|
|
|
on_instance=False)
|
|
|
|
|
```
|
|
|
|
|
would have the same effect that
|
|
|
|
|
```
|
|
|
|
|
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', \
|
|
|
|
|
on_instance=False)
|
|
|
|
|
```
|
|
|
|
|
But don't do that, it's silly.
|
2018-05-07 18:24:04 +00:00
|
|
|
|
on_instance: When `on_instance` equals `False`, the decorator runs the
|
|
|
|
|
ACL method on the model class rather than on an instance. If an
|
|
|
|
|
instance need to fetched, it is done calling the assumed existing
|
|
|
|
|
method `get_instance` of the model, with the arguments originally
|
|
|
|
|
passed to the view.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
|
2020-11-29 16:17:42 +00:00
|
|
|
|
api: when set to True, errors will no longer trigger redirection and
|
|
|
|
|
messages but will send a 403 with errors in JSON
|
|
|
|
|
|
2018-05-07 16:57:08 +00:00
|
|
|
|
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
|
|
|
|
|
to avoid duplicate DB calls, when the `on_instance` flag equals `True`,
|
|
|
|
|
the instances are passed to the view. Example, with this decorator:
|
|
|
|
|
```
|
|
|
|
|
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', ModelB,\
|
|
|
|
|
ModelC)
|
|
|
|
|
```
|
|
|
|
|
The view will be called like this:
|
|
|
|
|
```
|
2020-04-30 17:59:42 +00:00
|
|
|
|
view(request, instance_of_A, instance_of_b, *args, **kwargs)
|
2018-05-07 16:57:08 +00:00
|
|
|
|
```
|
|
|
|
|
where `*args` and `**kwargs` are the original view arguments.
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""
|
2018-05-07 15:20:55 +00:00
|
|
|
|
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# 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).
|
|
|
|
|
|
2018-05-07 15:20:55 +00:00
|
|
|
|
def group_targets():
|
2018-05-07 16:57:08 +00:00
|
|
|
|
"""This generator parses the targets of the decorator, yielding
|
|
|
|
|
2-tuples of (model, [fields]).
|
|
|
|
|
"""
|
2018-05-07 15:20:55 +00:00
|
|
|
|
current_target = None
|
|
|
|
|
current_fields = []
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# 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.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
for target in targets:
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# 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.
|
2018-05-07 20:01:32 +00:00
|
|
|
|
if not isinstance(target, str):
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# 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.
|
2018-05-07 15:20:55 +00:00
|
|
|
|
if current_target:
|
|
|
|
|
yield (current_target, current_fields)
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# Then we define the current target and reset its fields.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
current_target = target
|
2018-05-07 15:20:55 +00:00
|
|
|
|
current_fields = []
|
|
|
|
|
else:
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# When we encounter a string, this is not valid target and is
|
|
|
|
|
# thus a field name. We store it for later.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
current_fields.append(target)
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# We need to yield the last pair of target and fields.
|
2018-05-07 15:20:55 +00:00
|
|
|
|
yield (current_target, current_fields)
|
|
|
|
|
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# 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 !
|
2017-12-29 00:51:24 +00:00
|
|
|
|
def decorator(view):
|
2020-12-28 19:59:35 +00:00
|
|
|
|
"""The decorator to use on a specific view"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
def wrapper(request, *args, **kwargs):
|
2018-05-07 15:20:55 +00:00
|
|
|
|
"""The wrapper used for a specific request"""
|
|
|
|
|
instances = []
|
|
|
|
|
|
2020-04-30 17:59:42 +00:00
|
|
|
|
def process_target(target, fields, target_id=None):
|
2018-05-07 16:57:08 +00:00
|
|
|
|
"""This function calls the methods on the target and checks for
|
|
|
|
|
the can_change_`field` method with the given fields. It also
|
|
|
|
|
stores the instances of models in order to avoid duplicate DB
|
|
|
|
|
calls for the view.
|
|
|
|
|
"""
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# When working on instances, retrieve the associated instance
|
|
|
|
|
# and store it to pass it to the view.
|
2018-05-07 15:20:55 +00:00
|
|
|
|
if on_instance:
|
|
|
|
|
try:
|
2020-12-28 19:59:35 +00:00
|
|
|
|
target = target.get_instance(target_id, *args, **kwargs)
|
2018-05-07 15:20:55 +00:00
|
|
|
|
instances.append(target)
|
|
|
|
|
except target.DoesNotExist:
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# A non existing instance is a valid reason to deny
|
|
|
|
|
# access to the view.
|
2019-09-05 23:08:53 +00:00
|
|
|
|
yield False, _("Nonexistent entry."), []
|
2018-05-07 15:20:55 +00:00
|
|
|
|
return
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# Now we can actually make the ACL test, using the right ACL
|
|
|
|
|
# method.
|
2018-05-07 15:20:55 +00:00
|
|
|
|
if hasattr(target, method_name):
|
|
|
|
|
can_fct = getattr(target, method_name)
|
|
|
|
|
yield can_fct(request.user, *args, **kwargs)
|
2020-08-28 18:41:36 +00:00
|
|
|
|
|
|
|
|
|
# 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)
|
2018-05-07 15:20:55 +00:00
|
|
|
|
for field in fields:
|
2019-11-04 16:55:03 +00:00
|
|
|
|
can_change_fct = getattr(target, "can_change_" + field)
|
2018-05-07 15:20:55 +00:00
|
|
|
|
yield can_change_fct(request.user, *args, **kwargs)
|
2018-05-07 20:01:32 +00:00
|
|
|
|
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# 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.
|
2019-09-05 23:08:53 +00:00
|
|
|
|
error_messages = []
|
2019-09-09 10:16:37 +00:00
|
|
|
|
warning_messages = []
|
2020-08-28 18:41:36 +00:00
|
|
|
|
|
|
|
|
|
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.
|
2020-04-30 17:59:42 +00:00
|
|
|
|
if on_instance:
|
2020-08-28 18:41:36 +00:00
|
|
|
|
arg_key, (target, fields) = it
|
2020-04-30 17:59:42 +00:00
|
|
|
|
target_id = int(kwargs[arg_key])
|
|
|
|
|
else:
|
2020-08-28 18:41:36 +00:00
|
|
|
|
target, fields = it
|
2020-04-30 17:59:42 +00:00
|
|
|
|
target_id = None
|
2020-08-28 18:41:36 +00:00
|
|
|
|
|
|
|
|
|
# Store the messages at the right place.
|
2020-04-30 17:59:42 +00:00
|
|
|
|
for can, msg, permissions in process_target(target, fields, target_id):
|
2019-09-05 23:08:53 +00:00
|
|
|
|
if not can:
|
2020-12-28 19:59:35 +00:00
|
|
|
|
error_messages.append(acl_error_message(msg, permissions))
|
2019-09-09 10:16:37 +00:00
|
|
|
|
elif msg:
|
2020-12-28 19:59:35 +00:00
|
|
|
|
warning_messages.append(acl_error_message(msg, permissions))
|
2019-09-09 10:16:37 +00:00
|
|
|
|
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# Display the warning messages
|
2020-11-29 16:17:42 +00:00
|
|
|
|
if not api:
|
|
|
|
|
if warning_messages:
|
|
|
|
|
for msg in warning_messages:
|
|
|
|
|
messages.warning(request, msg)
|
2019-09-06 14:05:45 +00:00
|
|
|
|
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# If there is any error message, then the request must be denied.
|
2018-05-07 15:20:55 +00:00
|
|
|
|
if error_messages:
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# We display the message
|
2020-11-29 16:17:42 +00:00
|
|
|
|
if not api:
|
|
|
|
|
for msg in error_messages:
|
|
|
|
|
messages.error(
|
|
|
|
|
request,
|
2020-12-28 19:59:35 +00:00
|
|
|
|
msg or _("You don't have the right to access this menu."),
|
2020-11-29 16:17:42 +00:00
|
|
|
|
)
|
2020-08-28 18:41:36 +00:00
|
|
|
|
# And redirect the user to the right place.
|
2019-09-05 17:16:37 +00:00
|
|
|
|
if request.user.id is not None:
|
2020-11-29 16:17:42 +00:00
|
|
|
|
if not api:
|
|
|
|
|
return redirect(
|
2020-12-28 19:59:35 +00:00
|
|
|
|
reverse(
|
|
|
|
|
"users:profil", kwargs={"userid": str(request.user.id)}
|
|
|
|
|
)
|
2020-11-29 16:17:42 +00:00
|
|
|
|
)
|
|
|
|
|
else:
|
2020-12-28 19:59:35 +00:00
|
|
|
|
return Response(
|
|
|
|
|
data={
|
|
|
|
|
"errors": error_messages,
|
|
|
|
|
"warning": warning_messages,
|
|
|
|
|
},
|
|
|
|
|
status=403,
|
|
|
|
|
)
|
2019-09-05 17:16:37 +00:00
|
|
|
|
else:
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return redirect(reverse("index"))
|
2018-05-07 15:20:55 +00:00
|
|
|
|
return view(request, *chain(instances, args), **kwargs)
|
2019-11-04 16:55:03 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
return wrapper
|
2019-11-04 16:55:03 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
2018-05-07 15:20:55 +00:00
|
|
|
|
def can_create(*models):
|
2018-05-07 16:57:08 +00:00
|
|
|
|
"""Decorator to check if an user can create the given models. It runs
|
|
|
|
|
`acl_base_decorator` with the flag `on_instance=False` and the method
|
|
|
|
|
'can_create'. See `acl_base_decorator` documentation for further details.
|
2018-05-07 15:20:55 +00:00
|
|
|
|
"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return acl_base_decorator("can_create", *models, on_instance=False)
|
2018-05-07 15:20:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def can_edit(*targets):
|
2018-05-07 16:57:08 +00:00
|
|
|
|
"""Decorator to check if an user can edit the models.
|
|
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=True` and the
|
|
|
|
|
method 'can_edit'. See `acl_base_decorator` documentation for further
|
|
|
|
|
details.
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return acl_base_decorator("can_edit", *targets)
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
|
|
|
|
|
2018-05-07 15:20:55 +00:00
|
|
|
|
def can_change(*targets):
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""Decorator to check if an user can edit a field of a model class.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
Difference with can_edit : takes a class and not an instance
|
|
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=False` and the
|
|
|
|
|
method 'can_change'. See `acl_base_decorator` documentation for further
|
|
|
|
|
details.
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return acl_base_decorator("can_change", *targets, on_instance=False)
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
|
|
|
|
|
2018-05-07 15:20:55 +00:00
|
|
|
|
def can_delete(*targets):
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""Decorator to check if an user can delete a model.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=True` and the
|
|
|
|
|
method 'can_edit'. See `acl_base_decorator` documentation for further
|
|
|
|
|
details.
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return acl_base_decorator("can_delete", *targets)
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def can_delete_set(model):
|
|
|
|
|
"""Decorator which returns a list of detable models by request user.
|
|
|
|
|
If none of them, return an error"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
def decorator(view):
|
2020-12-28 19:59:35 +00:00
|
|
|
|
"""The decorator to use on a specific view"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
def wrapper(request, *args, **kwargs):
|
2020-12-28 19:59:35 +00:00
|
|
|
|
"""The wrapper used for a specific request"""
|
2017-12-29 00:51:24 +00:00
|
|
|
|
all_objects = model.objects.all()
|
|
|
|
|
instances_id = []
|
|
|
|
|
for instance in all_objects:
|
2019-10-14 22:17:38 +00:00
|
|
|
|
can, _msg, _reason = instance.can_delete(request.user)
|
2017-12-29 00:51:24 +00:00
|
|
|
|
if can:
|
|
|
|
|
instances_id.append(instance.id)
|
|
|
|
|
instances = model.objects.filter(id__in=instances_id)
|
|
|
|
|
if not instances:
|
2018-04-14 19:29:16 +00:00
|
|
|
|
messages.error(
|
2018-06-23 17:53:10 +00:00
|
|
|
|
request, _("You don't have the right to access this menu.")
|
|
|
|
|
)
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return redirect(
|
2020-12-28 19:59:35 +00:00
|
|
|
|
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
2019-11-04 16:55:03 +00:00
|
|
|
|
)
|
2017-12-29 00:51:24 +00:00
|
|
|
|
return view(request, instances, *args, **kwargs)
|
2019-11-04 16:55:03 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
return wrapper
|
2019-11-04 16:55:03 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
2018-05-07 15:20:55 +00:00
|
|
|
|
def can_view(*targets):
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""Decorator to check if an user can view a model.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=True` and the
|
|
|
|
|
method 'can_view'. See `acl_base_decorator` documentation for further
|
|
|
|
|
details.
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return acl_base_decorator("can_view", *targets)
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
|
|
|
|
|
2018-05-07 15:20:55 +00:00
|
|
|
|
def can_view_all(*targets):
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""Decorator to check if an user can view a class of model.
|
2018-05-07 16:57:08 +00:00
|
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=False` and the
|
|
|
|
|
method 'can_view_all'. See `acl_base_decorator` documentation for further
|
|
|
|
|
details.
|
2017-12-29 00:51:24 +00:00
|
|
|
|
"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return acl_base_decorator("can_view_all", *targets, on_instance=False)
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
|
|
|
|
|
2020-12-30 18:06:09 +00:00
|
|
|
|
def can_list(*targets):
|
|
|
|
|
"""Decorator to check if an user can list a class of model.
|
|
|
|
|
It runs `acl_base_decorator` with the flag `on_instance=False` and the
|
|
|
|
|
method 'can_list'. See `acl_base_decorator` documentation for further
|
|
|
|
|
details.
|
|
|
|
|
"""
|
|
|
|
|
return acl_base_decorator("can_list", *targets, on_instance=False)
|
|
|
|
|
|
|
|
|
|
|
2018-05-07 17:33:06 +00:00
|
|
|
|
def can_view_app(*apps_name):
|
2020-12-28 19:59:35 +00:00
|
|
|
|
"""Decorator to check if an user can view the applications."""
|
2018-05-07 17:33:06 +00:00
|
|
|
|
for app_name in apps_name:
|
|
|
|
|
assert app_name in sys.modules.keys()
|
2018-05-07 20:01:32 +00:00
|
|
|
|
return acl_base_decorator(
|
2019-11-04 16:55:03 +00:00
|
|
|
|
"can_view",
|
2018-05-07 20:01:32 +00:00
|
|
|
|
*chain(sys.modules[app_name] for app_name in apps_name),
|
|
|
|
|
on_instance=False
|
|
|
|
|
)
|
2017-12-29 00:51:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def can_edit_history(view):
|
|
|
|
|
"""Decorator to check if an user can edit history."""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
|
2017-12-29 00:51:24 +00:00
|
|
|
|
def wrapper(request, *args, **kwargs):
|
2020-12-28 19:59:35 +00:00
|
|
|
|
"""The wrapper used for a specific request"""
|
2019-11-04 16:55:03 +00:00
|
|
|
|
if request.user.has_perm("admin.change_logentry"):
|
2017-12-29 00:51:24 +00:00
|
|
|
|
return view(request, *args, **kwargs)
|
2020-12-28 19:59:35 +00:00
|
|
|
|
messages.error(request, _("You don't have the right to edit the history."))
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return redirect(
|
|
|
|
|
reverse("users:profil", kwargs={"userid": str(request.user.id)})
|
2017-12-29 00:51:24 +00:00
|
|
|
|
)
|
2018-06-23 17:53:10 +00:00
|
|
|
|
|
2019-11-04 16:55:03 +00:00
|
|
|
|
return wrapper
|
2020-11-29 16:17:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|