mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-31 15:50:03 +01:00 
			
		
		
		
	Implements permission masks
This commit is contained in:
		| @@ -1,55 +0,0 @@ | ||||
| # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.models import AnonymousUser | ||||
|  | ||||
| from threading import local | ||||
|  | ||||
|  | ||||
| USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user') | ||||
| IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip') | ||||
|  | ||||
| _thread_locals = local() | ||||
|  | ||||
|  | ||||
| def _set_current_user_and_ip(user=None, ip=None): | ||||
|     setattr(_thread_locals, USER_ATTR_NAME, user) | ||||
|     setattr(_thread_locals, IP_ATTR_NAME, ip) | ||||
|  | ||||
|  | ||||
| def get_current_user(): | ||||
|     return getattr(_thread_locals, USER_ATTR_NAME, None) | ||||
|  | ||||
|  | ||||
| def get_current_ip(): | ||||
|     return getattr(_thread_locals, IP_ATTR_NAME, None) | ||||
|  | ||||
|  | ||||
| def get_current_authenticated_user(): | ||||
|     current_user = get_current_user() | ||||
|     if isinstance(current_user, AnonymousUser): | ||||
|         return None | ||||
|     return current_user | ||||
|  | ||||
|  | ||||
| class LogsMiddleware(object): | ||||
|     """ | ||||
|     This middleware get the current user with his or her IP address on each request. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, get_response): | ||||
|         self.get_response = get_response | ||||
|  | ||||
|     def __call__(self, request): | ||||
|         user = request.user | ||||
|         if 'HTTP_X_FORWARDED_FOR' in request.META: | ||||
|             ip = request.META.get('HTTP_X_FORWARDED_FOR') | ||||
|         else: | ||||
|             ip = request.META.get('REMOTE_ADDR') | ||||
|  | ||||
|         _set_current_user_and_ip(user, ip) | ||||
|         response = self.get_response(request) | ||||
|         _set_current_user_and_ip(None, None) | ||||
|  | ||||
|         return response | ||||
| @@ -9,7 +9,7 @@ import getpass | ||||
|  | ||||
| from note.models import NoteUser, Alias | ||||
|  | ||||
| from .middlewares import get_current_authenticated_user, get_current_ip | ||||
| from note_kfet.middlewares import get_current_authenticated_user, get_current_ip | ||||
| from .models import Changelog | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -3,10 +3,10 @@ | ||||
|  | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.core.exceptions import PermissionDenied | ||||
| from django.db.models import Q, F | ||||
|  | ||||
| from note.models import Note, NoteUser, NoteClub, NoteSpecial | ||||
| from note_kfet.middlewares import get_current_session | ||||
| from .models import Membership, RolePermissions, Club | ||||
| from django.contrib.auth.backends import ModelBackend | ||||
|  | ||||
| @@ -37,7 +37,8 @@ class PermissionBackend(ModelBackend): | ||||
|                         F=F, | ||||
|                         Q=Q | ||||
|                     ) | ||||
|                     yield permission | ||||
|                     if permission.mask.rank <= get_current_session().get("permission_mask", 0): | ||||
|                         yield permission | ||||
|  | ||||
|     @staticmethod | ||||
|     def filter_queryset(user, model, t, field=None): | ||||
| @@ -50,7 +51,7 @@ class PermissionBackend(ModelBackend): | ||||
|         :return: A query that corresponds to the filter to give to a queryset | ||||
|         """ | ||||
|  | ||||
|         if user.is_superuser: | ||||
|         if user.is_superuser and get_current_session().get("permission_mask", 0) >= 42: | ||||
|             # Superusers have all rights | ||||
|             return Q() | ||||
|  | ||||
| @@ -68,7 +69,7 @@ class PermissionBackend(ModelBackend): | ||||
|         return query | ||||
|  | ||||
|     def has_perm(self, user_obj, perm, obj=None): | ||||
|         if user_obj.is_superuser: | ||||
|         if user_obj.is_superuser and get_current_session().get("permission_mask", 0) >= 42: | ||||
|             return True | ||||
|  | ||||
|         if obj is None: | ||||
|   | ||||
| @@ -6,12 +6,21 @@ from crispy_forms.helper import FormHelper | ||||
| from crispy_forms.layout import Layout | ||||
| from dal import autocomplete | ||||
| from django import forms | ||||
| from django.contrib.auth.forms import UserCreationForm | ||||
| from django.contrib.auth.forms import UserCreationForm, AuthenticationForm | ||||
| from django.contrib.auth.models import User | ||||
|  | ||||
| from permission.models import PermissionMask | ||||
| from .models import Profile, Club, Membership | ||||
|  | ||||
|  | ||||
| class CustomAuthenticationForm(AuthenticationForm): | ||||
|     permission_mask = forms.ModelChoiceField( | ||||
|         label="Masque de permissions", | ||||
|         queryset=PermissionMask.objects.order_by("rank"), | ||||
|         empty_label=None, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| class SignUpForm(UserCreationForm): | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super().__init__(*args, **kwargs) | ||||
|   | ||||
| @@ -9,6 +9,7 @@ from django.conf import settings | ||||
| from django.contrib import messages | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.contrib.auth.models import User | ||||
| from django.contrib.auth.views import LoginView | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.db.models import Q | ||||
| from django.http import HttpResponseRedirect | ||||
| @@ -26,11 +27,20 @@ from note.tables import HistoryTable, AliasTable | ||||
| from .backends import PermissionBackend | ||||
|  | ||||
| from .filters import UserFilter, UserFilterFormHelper | ||||
| from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper | ||||
| from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper, \ | ||||
|     CustomAuthenticationForm | ||||
| from .models import Club, Membership | ||||
| from .tables import ClubTable, UserTable | ||||
|  | ||||
|  | ||||
| class CustomLoginView(LoginView): | ||||
|     form_class = CustomAuthenticationForm | ||||
|  | ||||
|     def form_valid(self, form): | ||||
|         self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank | ||||
|         return super().form_valid(form) | ||||
|  | ||||
|  | ||||
| class UserCreateView(CreateView): | ||||
|     """ | ||||
|     Une vue pour inscrire un utilisateur et lui créer un profile | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| from rest_framework import serializers | ||||
| from rest_polymorphic.serializers import PolymorphicSerializer | ||||
|  | ||||
| from logs.middlewares import get_current_authenticated_user | ||||
| from note_kfet.middlewares import get_current_authenticated_user | ||||
| from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias | ||||
| from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ | ||||
|     TemplateTransaction, SpecialTransaction | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|         "model": "note.note", | ||||
|         "pk": 1, | ||||
|         "fields": { | ||||
|             "polymorphic_ctype": 40, | ||||
|             "polymorphic_ctype": 41, | ||||
|             "balance": 0, | ||||
|             "is_active": true, | ||||
|             "display_image": "", | ||||
| @@ -14,7 +14,7 @@ | ||||
|         "model": "note.note", | ||||
|         "pk": 2, | ||||
|         "fields": { | ||||
|             "polymorphic_ctype": 40, | ||||
|             "polymorphic_ctype": 41, | ||||
|             "balance": 0, | ||||
|             "is_active": true, | ||||
|             "display_image": "", | ||||
| @@ -25,7 +25,7 @@ | ||||
|         "model": "note.note", | ||||
|         "pk": 3, | ||||
|         "fields": { | ||||
|             "polymorphic_ctype": 40, | ||||
|             "polymorphic_ctype": 41, | ||||
|             "balance": 0, | ||||
|             "is_active": true, | ||||
|             "display_image": "", | ||||
| @@ -36,7 +36,7 @@ | ||||
|         "model": "note.note", | ||||
|         "pk": 4, | ||||
|         "fields": { | ||||
|             "polymorphic_ctype": 40, | ||||
|             "polymorphic_ctype": 41, | ||||
|             "balance": 0, | ||||
|             "is_active": true, | ||||
|             "display_image": "", | ||||
| @@ -47,7 +47,7 @@ | ||||
|         "model": "note.note", | ||||
|         "pk": 5, | ||||
|         "fields": { | ||||
|             "polymorphic_ctype": 39, | ||||
|             "polymorphic_ctype": 40, | ||||
|             "balance": 0, | ||||
|             "is_active": true, | ||||
|             "display_image": "", | ||||
| @@ -58,7 +58,7 @@ | ||||
|         "model": "note.note", | ||||
|         "pk": 6, | ||||
|         "fields": { | ||||
|             "polymorphic_ctype": 39, | ||||
|             "polymorphic_ctype": 40, | ||||
|             "balance": 0, | ||||
|             "is_active": true, | ||||
|             "display_image": "", | ||||
|   | ||||
| @@ -3,7 +3,15 @@ | ||||
|  | ||||
| from django.contrib import admin | ||||
|  | ||||
| from .models import Permission | ||||
| from .models import Permission, PermissionMask | ||||
|  | ||||
|  | ||||
| @admin.register(PermissionMask) | ||||
| class PermissionMaskAdmin(admin.ModelAdmin): | ||||
|     """ | ||||
|     Admin customisation for Permission | ||||
|     """ | ||||
|     list_display = ('rank', 'description') | ||||
|  | ||||
|  | ||||
| @admin.register(Permission) | ||||
|   | ||||
| @@ -50,6 +50,20 @@ class InstancedPermission: | ||||
|         return self.__repr__() | ||||
|  | ||||
|  | ||||
| class PermissionMask(models.Model): | ||||
|     rank = models.PositiveSmallIntegerField( | ||||
|         verbose_name=_('rank'), | ||||
|     ) | ||||
|  | ||||
|     description = models.CharField( | ||||
|         max_length=255, | ||||
|         verbose_name=_('description'), | ||||
|     ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.description | ||||
|  | ||||
|  | ||||
| class Permission(models.Model): | ||||
|  | ||||
|     PERMISSION_TYPES = [ | ||||
| @@ -85,6 +99,11 @@ class Permission(models.Model): | ||||
|  | ||||
|     type = models.CharField(max_length=15, choices=PERMISSION_TYPES) | ||||
|  | ||||
|     mask = models.ForeignKey( | ||||
|         PermissionMask, | ||||
|         on_delete=models.PROTECT, | ||||
|     ) | ||||
|  | ||||
|     field = models.CharField(max_length=255, blank=True) | ||||
|  | ||||
|     description = models.CharField(max_length=255, blank=True) | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.core.exceptions import PermissionDenied | ||||
| from logs.middlewares import get_current_authenticated_user | ||||
| from note_kfet.middlewares import get_current_authenticated_user | ||||
|  | ||||
|  | ||||
| EXCLUDED = [ | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
| from django.contrib.contenttypes.models import ContentType | ||||
| from django.template.defaultfilters import stringfilter | ||||
|  | ||||
| from logs.middlewares import get_current_authenticated_user | ||||
| from note_kfet.middlewares import get_current_authenticated_user, get_current_session | ||||
| from django import template | ||||
|  | ||||
| from member.backends import PermissionBackend | ||||
| @@ -19,7 +19,7 @@ def not_empty_model_list(model_name): | ||||
|     user = get_current_authenticated_user() | ||||
|     if user is None: | ||||
|         return False | ||||
|     elif user.is_superuser: | ||||
|     elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42: | ||||
|         return True | ||||
|     spl = model_name.split(".") | ||||
|     ct = ContentType.objects.get(app_label=spl[0], model=spl[1]) | ||||
| @@ -32,7 +32,7 @@ def not_empty_model_change_list(model_name): | ||||
|     user = get_current_authenticated_user() | ||||
|     if user is None: | ||||
|         return False | ||||
|     elif user.is_superuser: | ||||
|     elif user.is_superuser and get_current_session().get("permission_mask", 0) >= 42: | ||||
|         return True | ||||
|     spl = model_name.split(".") | ||||
|     ct = ContentType.objects.get(app_label=spl[0], model=spl[1]) | ||||
|   | ||||
| @@ -7,7 +7,7 @@ if [ -z ${NOTE_URL+x} ]; then | ||||
| else | ||||
|   sed -i -e "s/example.com/$DOMAIN/g" /code/apps/member/fixtures/initial.json | ||||
|   sed -i -e "s/localhost/$NOTE_URL/g" /code/note_kfet/fixtures/initial.json | ||||
|   sed -i -e "s/\.\*/https?:\/\/$NOTE_URL\/.*/g" /code/note_kfet/fixtures/cas.json | ||||
|   sed -i -e "s/\"\.\*\"/\"https?:\/\/$NOTE_URL\/.*\"/g" /code/note_kfet/fixtures/cas.json | ||||
|   sed -i -e "s/REPLACEME/La Note Kfet \\\\ud83c\\\\udf7b/g" /code/note_kfet/fixtures/cas.json | ||||
| fi | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,66 @@ | ||||
| # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.models import AnonymousUser, User | ||||
|  | ||||
| from threading import local | ||||
|  | ||||
| from django.contrib.sessions.backends.db import SessionStore | ||||
|  | ||||
| USER_ATTR_NAME = getattr(settings, 'LOCAL_USER_ATTR_NAME', '_current_user') | ||||
| SESSION_ATTR_NAME = getattr(settings, 'LOCAL_SESSION_ATTR_NAME', '_current_session') | ||||
| IP_ATTR_NAME = getattr(settings, 'LOCAL_IP_ATTR_NAME', '_current_ip') | ||||
|  | ||||
| _thread_locals = local() | ||||
|  | ||||
|  | ||||
| def _set_current_user_and_ip(user=None, session=None, ip=None): | ||||
|     setattr(_thread_locals, USER_ATTR_NAME, user) | ||||
|     setattr(_thread_locals, SESSION_ATTR_NAME, session) | ||||
|     setattr(_thread_locals, IP_ATTR_NAME, ip) | ||||
|  | ||||
|  | ||||
| def get_current_user() -> User: | ||||
|     return getattr(_thread_locals, USER_ATTR_NAME, None) | ||||
|  | ||||
|  | ||||
| def get_current_session() -> SessionStore: | ||||
|     return getattr(_thread_locals, SESSION_ATTR_NAME, None) | ||||
|  | ||||
|  | ||||
| def get_current_ip() -> str: | ||||
|     return getattr(_thread_locals, IP_ATTR_NAME, None) | ||||
|  | ||||
|  | ||||
| def get_current_authenticated_user(): | ||||
|     current_user = get_current_user() | ||||
|     if isinstance(current_user, AnonymousUser): | ||||
|         return None | ||||
|     return current_user | ||||
|  | ||||
|  | ||||
| class SessionMiddleware(object): | ||||
|     """ | ||||
|     This middleware get the current user with his or her IP address on each request. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, get_response): | ||||
|         self.get_response = get_response | ||||
|  | ||||
|     def __call__(self, request): | ||||
|         user = request.user | ||||
|         if 'HTTP_X_FORWARDED_FOR' in request.META: | ||||
|             ip = request.META.get('HTTP_X_FORWARDED_FOR') | ||||
|         else: | ||||
|             ip = request.META.get('REMOTE_ADDR') | ||||
|  | ||||
|         _set_current_user_and_ip(user, request.session, ip) | ||||
|         response = self.get_response(request) | ||||
|         _set_current_user_and_ip(None, None, None) | ||||
|  | ||||
|         return response | ||||
|  | ||||
|  | ||||
| class TurbolinksMiddleware(object): | ||||
|     """ | ||||
|   | ||||
| @@ -74,7 +74,7 @@ if "cas" in INSTALLED_APPS: | ||||
|  | ||||
|  | ||||
| if "logs" in INSTALLED_APPS: | ||||
|     MIDDLEWARE += ('logs.middlewares.LogsMiddleware',) | ||||
|     MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',) | ||||
|  | ||||
| if "debug_toolbar" in INSTALLED_APPS: | ||||
|     MIDDLEWARE.insert(1, "debug_toolbar.middleware.DebugToolbarMiddleware") | ||||
|   | ||||
| @@ -7,6 +7,8 @@ from django.contrib import admin | ||||
| from django.urls import path, include | ||||
| from django.views.generic import RedirectView | ||||
|  | ||||
| from member.views import CustomLoginView | ||||
|  | ||||
| urlpatterns = [ | ||||
|     # Dev so redirect to something random | ||||
|     path('', RedirectView.as_view(pattern_name='note:transfer'), name='index'), | ||||
| @@ -16,10 +18,11 @@ urlpatterns = [ | ||||
|  | ||||
|     # Include Django Contrib and Core routers | ||||
|     path('i18n/', include('django.conf.urls.i18n')), | ||||
|     path('accounts/', include('member.urls')), | ||||
|     path('accounts/', include('django.contrib.auth.urls')), | ||||
|     path('admin/doc/', include('django.contrib.admindocs.urls')), | ||||
|     path('admin/', admin.site.urls), | ||||
|     path('accounts/', include('member.urls')), | ||||
|     path('accounts/login/', CustomLoginView.as_view()), | ||||
|     path('accounts/', include('django.contrib.auth.urls')), | ||||
|     path('api/', include('api.urls')), | ||||
| ] | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user