mirror of https://gitlab.crans.org/bde/nk20
Comment code
This commit is contained in:
parent
091c427707
commit
24ea4c0a52
|
@ -9,7 +9,6 @@ class PermissionConfig(AppConfig):
|
||||||
name = 'permission'
|
name = 'permission'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
# noinspection PyUnresolvedReferences
|
|
||||||
from . import signals
|
from . import signals
|
||||||
pre_save.connect(signals.pre_save_object)
|
pre_save.connect(signals.pre_save_object)
|
||||||
pre_delete.connect(signals.pre_delete_object)
|
pre_delete.connect(signals.pre_delete_object)
|
||||||
|
|
|
@ -13,18 +13,31 @@ from member.models import Membership, Club
|
||||||
|
|
||||||
|
|
||||||
class PermissionBackend(ModelBackend):
|
class PermissionBackend(ModelBackend):
|
||||||
|
"""
|
||||||
|
Manage permissions of users
|
||||||
|
"""
|
||||||
supports_object_permissions = True
|
supports_object_permissions = True
|
||||||
supports_anonymous_user = False
|
supports_anonymous_user = False
|
||||||
supports_inactive_user = False
|
supports_inactive_user = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def permissions(user, model, type):
|
def permissions(user, model, type):
|
||||||
|
"""
|
||||||
|
List all permissions of the given user that applies to a given model and a give type
|
||||||
|
:param user: The owner of the permissions
|
||||||
|
:param model: The model that the permissions shoud apply
|
||||||
|
:param type: The type of the permissions: view, change, add or delete
|
||||||
|
:return: A generator of the requested permissions
|
||||||
|
"""
|
||||||
for permission in Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
|
for permission in Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
|
||||||
.filter(
|
.filter(
|
||||||
rolepermissions__role__membership__user=user,
|
rolepermissions__role__membership__user=user,
|
||||||
model__app_label=model.app_label, # For polymorphic models, we don't filter on model type
|
model__app_label=model.app_label, # For polymorphic models, we don't filter on model type
|
||||||
type=type,
|
type=type,
|
||||||
).all():
|
).all():
|
||||||
|
if not isinstance(model, permission.model.__class__):
|
||||||
|
continue
|
||||||
|
|
||||||
club = Club.objects.get(pk=permission.club)
|
club = Club.objects.get(pk=permission.club)
|
||||||
permission = permission.about(
|
permission = permission.about(
|
||||||
user=user,
|
user=user,
|
||||||
|
|
|
@ -57,6 +57,10 @@ class InstancedPermission:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def update_query(self):
|
def update_query(self):
|
||||||
|
"""
|
||||||
|
The query is not analysed in a first time. It is analysed at most once if needed.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
if not self.query:
|
if not self.query:
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
self.query = Permission._about(self.raw_query, **self.kwargs)
|
self.query = Permission._about(self.raw_query, **self.kwargs)
|
||||||
|
@ -72,6 +76,10 @@ class InstancedPermission:
|
||||||
|
|
||||||
|
|
||||||
class PermissionMask(models.Model):
|
class PermissionMask(models.Model):
|
||||||
|
"""
|
||||||
|
Permissions that are hidden behind a mask
|
||||||
|
"""
|
||||||
|
|
||||||
rank = models.PositiveSmallIntegerField(
|
rank = models.PositiveSmallIntegerField(
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name=_('rank'),
|
verbose_name=_('rank'),
|
||||||
|
@ -106,9 +114,9 @@ class Permission(models.Model):
|
||||||
# query -> {key: value, …} A list of fields and values of a Q object
|
# query -> {key: value, …} A list of fields and values of a Q object
|
||||||
# key -> string A field name
|
# key -> string A field name
|
||||||
# value -> int | string | bool | null Literal values
|
# value -> int | string | bool | null Literal values
|
||||||
# | [parameter] A parameter
|
# | [parameter, …] A parameter. See compute_param for more details.
|
||||||
# | {"F": oper} An F object
|
# | {"F": oper} An F object
|
||||||
# oper -> [string] A parameter
|
# oper -> [string, …] A parameter. See compute_param for more details.
|
||||||
# | ["ADD", oper, …] Sum multiple F objects or literal
|
# | ["ADD", oper, …] Sum multiple F objects or literal
|
||||||
# | ["SUB", oper, oper] Substract two F objects or literal
|
# | ["SUB", oper, oper] Substract two F objects or literal
|
||||||
# | ["MUL", oper, …] Multiply F objects or literals
|
# | ["MUL", oper, …] Multiply F objects or literals
|
||||||
|
@ -164,6 +172,18 @@ class Permission(models.Model):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_param(value, **kwargs):
|
def compute_param(value, **kwargs):
|
||||||
|
"""
|
||||||
|
A parameter is given by a list. The first argument is the name of the parameter.
|
||||||
|
The parameters are the user, the club, and some classes (Note, ...)
|
||||||
|
If there are more arguments in the list, then attributes are queried.
|
||||||
|
For example, ["user", "note", "balance"] will return the balance of the note of the user.
|
||||||
|
If an argument is a list, then this is interpreted with a function call:
|
||||||
|
First argument is the name of the function, next arguments are parameters, and if there is a dict,
|
||||||
|
then the dict is given as kwargs.
|
||||||
|
For example: NoteUser.objects.filter(user__memberships__club__name="Kfet").all() is translated by:
|
||||||
|
["NoteUser", "objects", ["filter", {"user__memberships__club__name": "Kfet"}], ["all"]]
|
||||||
|
"""
|
||||||
|
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -192,6 +212,12 @@ class Permission(models.Model):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _about(query, **kwargs):
|
def _about(query, **kwargs):
|
||||||
|
"""
|
||||||
|
Translate JSON query into a Q query.
|
||||||
|
:param query: The JSON query
|
||||||
|
:param kwargs: Additional params
|
||||||
|
:return: A Q object
|
||||||
|
"""
|
||||||
if len(query) == 0:
|
if len(query) == 0:
|
||||||
# The query is either [] or {} and
|
# The query is either [] or {} and
|
||||||
# applies to all objects of the model
|
# applies to all objects of the model
|
||||||
|
@ -204,6 +230,8 @@ class Permission(models.Model):
|
||||||
return functools.reduce(operator.or_, [Permission._about(query, **kwargs) for query in query[1:]])
|
return functools.reduce(operator.or_, [Permission._about(query, **kwargs) for query in query[1:]])
|
||||||
elif query[0] == 'NOT':
|
elif query[0] == 'NOT':
|
||||||
return ~Permission._about(query[1], **kwargs)
|
return ~Permission._about(query[1], **kwargs)
|
||||||
|
else:
|
||||||
|
return Q(pk=F("pk"))
|
||||||
elif isinstance(query, dict):
|
elif isinstance(query, dict):
|
||||||
q_kwargs = {}
|
q_kwargs = {}
|
||||||
for key in query:
|
for key in query:
|
||||||
|
|
|
@ -7,6 +7,11 @@ SAFE_METHODS = ('HEAD', 'OPTIONS', )
|
||||||
|
|
||||||
|
|
||||||
class StrongDjangoObjectPermissions(DjangoObjectPermissions):
|
class StrongDjangoObjectPermissions(DjangoObjectPermissions):
|
||||||
|
"""
|
||||||
|
Default DjangoObjectPermissions grant view permission to all.
|
||||||
|
This is a simple patch of this class that controls view access.
|
||||||
|
"""
|
||||||
|
|
||||||
perms_map = {
|
perms_map = {
|
||||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
'GET': ['%(app_label)s.view_%(model_name)s'],
|
||||||
'OPTIONS': [],
|
'OPTIONS': [],
|
||||||
|
|
|
@ -12,6 +12,9 @@ from permission.backends import PermissionBackend
|
||||||
|
|
||||||
@stringfilter
|
@stringfilter
|
||||||
def not_empty_model_list(model_name):
|
def not_empty_model_list(model_name):
|
||||||
|
"""
|
||||||
|
Return True if and only if the current user has right to see any object of the given model.
|
||||||
|
"""
|
||||||
user = get_current_authenticated_user()
|
user = get_current_authenticated_user()
|
||||||
session = get_current_session()
|
session = get_current_session()
|
||||||
if user is None:
|
if user is None:
|
||||||
|
@ -29,6 +32,9 @@ def not_empty_model_list(model_name):
|
||||||
|
|
||||||
@stringfilter
|
@stringfilter
|
||||||
def not_empty_model_change_list(model_name):
|
def not_empty_model_change_list(model_name):
|
||||||
|
"""
|
||||||
|
Return True if and only if the current user has right to change any object of the given model.
|
||||||
|
"""
|
||||||
user = get_current_authenticated_user()
|
user = get_current_authenticated_user()
|
||||||
session = get_current_session()
|
session = get_current_session()
|
||||||
if user is None:
|
if user is None:
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,6 +0,0 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
|
@ -127,17 +127,14 @@ PASSWORD_HASHERS = [
|
||||||
'member.hashers.CustomNK15Hasher',
|
'member.hashers.CustomNK15Hasher',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Django Guardian object permissions
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'permission.backends.PermissionBackend',
|
'permission.backends.PermissionBackend', # Custom role-based permission system
|
||||||
'cas.backends.CASBackend',
|
'cas.backends.CASBackend', # For CAS connections
|
||||||
)
|
)
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
# Use Django's standard `django.contrib.auth` permissions,
|
|
||||||
# or allow read-only access for unauthenticated users.
|
|
||||||
'DEFAULT_PERMISSION_CLASSES': [
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
|
# Control API access with our role-based permission system
|
||||||
'permission.permissions.StrongDjangoObjectPermissions',
|
'permission.permissions.StrongDjangoObjectPermissions',
|
||||||
],
|
],
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
|
Loading…
Reference in New Issue