Skip to content
Snippets Groups Projects
Commit 24ea4c0a authored by ynerant's avatar ynerant
Browse files

Comment code

parent 091c4277
No related branches found
No related tags found
1 merge request!10Système de droits
Pipeline #7976 passed with warnings with stages
in 4 minutes and 18 seconds
...@@ -9,7 +9,6 @@ class PermissionConfig(AppConfig): ...@@ -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 ...@@ -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: ...@@ -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: ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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', ) ...@@ -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 ...@@ -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): ...@@ -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:
......
# 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.
# 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 = [ ...@@ -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': [
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment