mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-31 15:50:03 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			240 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			240 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from datetime import date, timedelta
 | |
| 
 | |
| from django.contrib.auth.backends import ModelBackend
 | |
| from django.contrib.auth.models import User
 | |
| from django.contrib.contenttypes.models import ContentType
 | |
| from django.db.models import Q, F
 | |
| from django.utils import timezone
 | |
| from note.models import Note, NoteUser, NoteClub, NoteSpecial
 | |
| from note_kfet.middlewares import get_current_request
 | |
| from member.models import Membership, Club
 | |
| 
 | |
| from .decorators import memoize
 | |
| from .models import Permission
 | |
| 
 | |
| 
 | |
| class PermissionBackend(ModelBackend):
 | |
|     """
 | |
|     Manage permissions of users
 | |
|     """
 | |
|     supports_object_permissions = True
 | |
|     supports_anonymous_user = False
 | |
|     supports_inactive_user = False
 | |
| 
 | |
|     @staticmethod
 | |
|     @memoize
 | |
|     def get_raw_permissions(request, t):
 | |
|         """
 | |
|         Query permissions of a certain type for a user, then memoize it.
 | |
|         :param request: The current request
 | |
|         :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 hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
 | |
|             # OAuth2 Authentication
 | |
|             user = request.auth.user
 | |
| 
 | |
|             def permission_filter(membership_obj):
 | |
|                 query = Q(pk=-1)
 | |
|                 for scope in request.auth.scope.split(' '):
 | |
|                     permission_id, club_id = scope.split('_')
 | |
|                     if int(club_id) == membership_obj.club_id:
 | |
|                         query |= Q(pk=permission_id)
 | |
|                 return query
 | |
|         else:
 | |
|             user = request.user
 | |
| 
 | |
|             def permission_filter(membership_obj):
 | |
|                 return Q(mask__rank__lte=request.session.get("permission_mask", 42))
 | |
| 
 | |
|         if user.is_anonymous:
 | |
|             # Unauthenticated users have no permissions
 | |
|             return Permission.objects.none()
 | |
| 
 | |
|         memberships = Membership.objects.filter(user=user).all()
 | |
| 
 | |
|         perms = []
 | |
| 
 | |
|         for membership in memberships:
 | |
|             for role in membership.roles.all():
 | |
|                 for perm in role.permissions.filter(permission_filter(membership), type=t).all():
 | |
|                     if not perm.permanent:
 | |
|                         if membership.date_start > date.today() or membership.date_end < date.today():
 | |
|                             continue
 | |
|                     perm.membership = membership
 | |
|                     perms.append(perm)
 | |
|         return perms
 | |
| 
 | |
|     @staticmethod
 | |
|     def permissions(request, model, type):
 | |
|         """
 | |
|         List all permissions of the given user that applies to a given model and a give type
 | |
|         :param request: The current request
 | |
|         :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
 | |
|         """
 | |
| 
 | |
|         if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
 | |
|             # OAuth2 Authentication
 | |
|             user = request.auth.user
 | |
|         else:
 | |
|             user = request.user
 | |
| 
 | |
|         for permission in PermissionBackend.get_raw_permissions(request, type):
 | |
|             if not isinstance(model.model_class()(), permission.model.model_class()) or not permission.membership:
 | |
|                 continue
 | |
| 
 | |
|             membership = permission.membership
 | |
|             club = membership.club
 | |
| 
 | |
|             permission = permission.about(
 | |
|                 user=user,
 | |
|                 club=club,
 | |
|                 membership=membership,
 | |
|                 User=User,
 | |
|                 Club=Club,
 | |
|                 Membership=Membership,
 | |
|                 Note=Note,
 | |
|                 NoteUser=NoteUser,
 | |
|                 NoteClub=NoteClub,
 | |
|                 NoteSpecial=NoteSpecial,
 | |
|                 F=F,
 | |
|                 Q=Q,
 | |
|                 now=timezone.now(),
 | |
|                 today=date.today(),
 | |
|                 week=timedelta(days=7),
 | |
|             )
 | |
|             yield permission
 | |
| 
 | |
|     @staticmethod
 | |
|     @memoize
 | |
|     def filter_queryset(request, model, t, field=None):
 | |
|         """
 | |
|         Filter a queryset by considering the permissions of a given user.
 | |
|         :param request: The current request
 | |
|         :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
 | |
|         """
 | |
|         if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
 | |
|             # OAuth2 Authentication
 | |
|             user = request.auth.user
 | |
|         else:
 | |
|             user = request.user
 | |
| 
 | |
|         if user is None or user.is_anonymous:
 | |
|             # Anonymous users can't do anything
 | |
|             return Q(pk=-1)
 | |
| 
 | |
|         if user.is_superuser and request.session.get("permission_mask", -1) >= 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)
 | |
|         perms = PermissionBackend.permissions(request, model, t)
 | |
|         for perm in perms:
 | |
|             if perm.field and field != perm.field:
 | |
|                 continue
 | |
|             if perm.type != t or perm.model != model:
 | |
|                 continue
 | |
|             perm.update_query()
 | |
|             query = query | perm.query
 | |
|         return query
 | |
| 
 | |
|     @staticmethod
 | |
|     @memoize
 | |
|     def check_perm(request, 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)
 | |
|         """
 | |
|         # Requested by a shell
 | |
|         if request is None:
 | |
|             return False
 | |
| 
 | |
|         user_obj = request.user
 | |
|         sess = request.session
 | |
| 
 | |
|         if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
 | |
|             # OAuth2 Authentication
 | |
|             user_obj = request.auth.user
 | |
| 
 | |
|         if user_obj is None or user_obj.is_anonymous:
 | |
|             return False
 | |
| 
 | |
|         if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
 | |
|             return True
 | |
| 
 | |
|         if obj is None:
 | |
|             return True
 | |
| 
 | |
|         perm = perm.split('.')[-1].split('_', 2)
 | |
|         perm_type = perm[0]
 | |
|         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(request, ct, perm_type)):
 | |
|             return True
 | |
|         return False
 | |
| 
 | |
|     def has_perm(self, user_obj, perm, obj=None):
 | |
|         # Warning: this does not check that user_obj has the permission,
 | |
|         # but if the current request has the permission.
 | |
|         # This function is implemented for backward compatibility, and should not be used.
 | |
|         return PermissionBackend.check_perm(get_current_request(), perm, obj)
 | |
| 
 | |
|     def has_module_perms(self, user_obj, app_label):
 | |
|         return False
 | |
| 
 | |
|     @staticmethod
 | |
|     @memoize
 | |
|     def has_model_perm(request, model, type):
 | |
|         """
 | |
|         Check is the given user has the permission over a given model for a given action.
 | |
|         The result is then memoized.
 | |
|         :param request: The current request
 | |
|         :param model: The model that the permissions shoud apply
 | |
|         :param type: The type of the permissions: view, change, add or delete
 | |
|         For view action, it is consider possible if user can view or change the model
 | |
|         """
 | |
|         # Requested by a shell
 | |
|         if request is None:
 | |
|             return False
 | |
| 
 | |
|         user_obj = request.user
 | |
|         sess = request.session
 | |
| 
 | |
|         if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
 | |
|             # OAuth2 Authentication
 | |
|             user_obj = request.auth.user
 | |
| 
 | |
|         if user_obj is None or user_obj.is_anonymous:
 | |
|             return False
 | |
| 
 | |
|         if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
 | |
|             return True
 | |
| 
 | |
|         ct = ContentType.objects.get_for_model(model)
 | |
|         if any(PermissionBackend.permissions(request, ct, type)):
 | |
|             return True
 | |
|         if type == "view" and any(PermissionBackend.permissions(request, ct, "change")):
 | |
|             return True
 | |
|         return False
 | |
| 
 | |
|     def get_all_permissions(self, user_obj, obj=None):
 | |
|         ct = ContentType.objects.get_for_model(obj)
 | |
|         return list(self.permissions(get_current_request(), ct, "view"))
 |