1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2024-11-29 20:02:59 +00:00
nk20/apps/permission/backends.py

166 lines
6.3 KiB
Python
Raw Normal View History

2020-03-07 12:12:17 +00:00
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
2020-04-01 01:42:19 +00:00
import datetime
2020-03-20 01:14:43 +00:00
from django.contrib.auth.backends import ModelBackend
2020-03-20 17:22:20 +00:00
from django.contrib.auth.models import User, AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q, F
2020-03-31 12:57:44 +00:00
from note.models import Note, NoteUser, NoteClub, NoteSpecial
from note_kfet import settings
2020-03-19 15:12:52 +00:00
from note_kfet.middlewares import get_current_session
2020-03-20 13:43:35 +00:00
from member.models import Membership, Club
2019-09-18 12:26:42 +00:00
2020-04-01 22:42:00 +00:00
from .decorators import memoize
2020-03-20 17:22:20 +00:00
from .models import Permission
2019-09-18 12:26:42 +00:00
2020-03-07 12:12:17 +00:00
class PermissionBackend(ModelBackend):
2020-03-20 14:58:14 +00:00
"""
Manage permissions of users
"""
2019-09-18 12:26:42 +00:00
supports_object_permissions = True
supports_anonymous_user = False
supports_inactive_user = False
2020-04-01 22:30:22 +00:00
@staticmethod
@memoize
def get_raw_permissions(user, t):
"""
Query permissions of a certain type for a user, then memoize it.
:param user: The owner of the permissions
:param t: The type of the permissions: view, change, add or delete
:return: The queryset of the permissions of the user (memoized) grouped by clubs
"""
if isinstance(user, AnonymousUser):
# Unauthenticated users have no permissions
return Permission.objects.none()
perms = Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
2020-04-01 22:30:22 +00:00
.filter(
rolepermissions__role__membership__user=user,
rolepermissions__role__membership__date_start__lte=datetime.date.today(),
rolepermissions__role__membership__date_end__gte=datetime.date.today(),
type=t,
mask__rank__lte=get_current_session().get("permission_mask", 42),
)
if settings.DATABASES[perms.db]["ENGINE"] == 'django.db.backends.postgresql_psycopg2':
# We want one permission per club, and per permission type.
# SQLite does not support this kind of filter, that's why we don't filter the permissions with this
# kind of DB. This only increases performances (we can check multiple times the same permission)
# but don't have any semantic influence.
perms = perms.distinct('club', 'pk')
return perms
2020-04-01 22:30:22 +00:00
@staticmethod
def permissions(user, model, type):
2020-03-20 14:58:14 +00:00
"""
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
"""
2020-04-01 22:30:22 +00:00
clubs = {}
for permission in PermissionBackend.get_raw_permissions(user, type):
if not isinstance(model.model_class()(), permission.model.model_class()) or not permission.club:
2020-03-20 14:58:14 +00:00
continue
2020-04-01 22:30:22 +00:00
if permission.club not in clubs:
clubs[permission.club] = club = Club.objects.get(pk=permission.club)
else:
club = clubs[permission.club]
2020-03-20 00:46:59 +00:00
permission = permission.about(
user=user,
club=club,
User=User,
Club=Club,
Membership=Membership,
Note=Note,
NoteUser=NoteUser,
NoteClub=NoteClub,
NoteSpecial=NoteSpecial,
F=F,
Q=Q
)
2020-04-01 22:30:22 +00:00
yield permission
2019-09-18 12:26:42 +00:00
@staticmethod
2020-04-01 22:30:22 +00:00
@memoize
def filter_queryset(user, model, t, field=None):
"""
Filter a queryset by considering the permissions of a given user.
:param user: The owner of the permissions that are fetched
:param model: The concerned model of the queryset
:param t: The type of modification (view, add, change, delete)
:param field: The field of the model to test, if concerned
:return: A query that corresponds to the filter to give to a queryset
"""
2020-03-20 17:22:20 +00:00
if user is None or isinstance(user, AnonymousUser):
# Anonymous users can't do anything
return Q(pk=-1)
if user.is_superuser and get_current_session().get("permission_mask", 42) >= 42:
# Superusers have all rights
return Q()
if not isinstance(model, ContentType):
model = ContentType.objects.get_for_model(model)
# Never satisfied
query = Q(pk=-1)
2020-03-20 00:46:59 +00:00
perms = PermissionBackend.permissions(user, model, t)
for perm in perms:
if perm.field and field != perm.field:
continue
if perm.type != t or perm.model != model:
continue
2020-03-20 00:46:59 +00:00
perm.update_query()
query = query | perm.query
return query
@staticmethod
2020-04-01 22:30:22 +00:00
@memoize
def check_perm(user_obj, perm, obj=None):
"""
Check is the given user has the permission over a given object.
The result is then memoized.
Exception: for add permissions, since the object is not hashable since it doesn't have any
primary key, the result is not memoized. Moreover, the right could change
(e.g. for a transaction, the balance of the user could change)
"""
2020-03-20 17:22:20 +00:00
if user_obj is None or isinstance(user_obj, AnonymousUser):
return False
2020-04-01 22:30:22 +00:00
sess = get_current_session()
if sess is not None and sess.session_key is None:
return False
2020-04-01 22:30:22 +00:00
if user_obj.is_superuser and get_current_session().get("permission_mask", 42) >= 42:
2020-03-07 12:12:17 +00:00
return True
2019-09-18 12:26:42 +00:00
if obj is None:
return True
perm = perm.split('.')[-1].split('_', 2)
perm_type = perm[0]
2019-09-18 12:26:42 +00:00
perm_field = perm[2] if len(perm) == 3 else None
ct = ContentType.objects.get_for_model(obj)
if any(permission.applies(obj, perm_type, perm_field)
for permission in PermissionBackend.permissions(user_obj, ct, perm_type)):
return True
return False
2020-03-07 12:12:17 +00:00
def has_perm(self, user_obj, perm, obj=None):
return PermissionBackend.check_perm(user_obj, perm, obj)
2020-03-07 12:12:17 +00:00
def has_module_perms(self, user_obj, app_label):
return False
2019-09-18 12:26:42 +00:00
def get_all_permissions(self, user_obj, obj=None):
ct = ContentType.objects.get_for_model(obj)
return list(self.permissions(user_obj, ct, "view"))