From ea092803d7b6dc3cb4114cc9b9322edf074963f7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 15 Jun 2021 14:40:32 +0200 Subject: [PATCH] Check permissions per request instead of per user Signed-off-by: Yohann D'ANELLO --- apps/activity/forms.py | 4 +-- apps/activity/views.py | 22 ++++++++-------- apps/api/viewsets.py | 11 +++----- apps/logs/signals.py | 36 ++++++++++++++++++++----- apps/member/hashers.py | 21 ++++++++++----- apps/member/tables.py | 18 ++++++------- apps/member/views.py | 37 +++++++++++++------------- apps/note/api/serializers.py | 6 ++--- apps/note/api/views.py | 18 ++++++------- apps/note/tables.py | 12 ++++----- apps/note/views.py | 18 ++++++------- apps/permission/backends.py | 32 +++++++++++++--------- apps/permission/decorators.py | 8 +++--- apps/permission/permissions.py | 2 +- apps/permission/signals.py | 19 +++++++------- apps/permission/tables.py | 8 +++--- apps/permission/templatetags/perms.py | 22 +++++++++------- apps/permission/views.py | 6 ++--- apps/registration/views.py | 6 ++++- apps/treasury/views.py | 18 ++++++------- apps/wei/tables.py | 6 ++--- apps/wei/views.py | 34 ++++++++++++------------ note_kfet/admin.py | 4 +-- note_kfet/middlewares.py | 38 +-------------------------- note_kfet/views.py | 4 +-- 25 files changed, 207 insertions(+), 203 deletions(-) diff --git a/apps/activity/forms.py b/apps/activity/forms.py index 60c18311..b40463c0 100644 --- a/apps/activity/forms.py +++ b/apps/activity/forms.py @@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _ from member.models import Club from note.models import Note, NoteUser from note_kfet.inputs import Autocomplete, DateTimePickerInput -from note_kfet.middlewares import get_current_authenticated_user +from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend from .models import Activity, Guest @@ -24,7 +24,7 @@ class ActivityForm(forms.ModelForm): self.fields["attendees_club"].initial = Club.objects.get(name="Kfet") self.fields["attendees_club"].widget.attrs["placeholder"] = "Kfet" clubs = list(Club.objects.filter(PermissionBackend - .filter_queryset(get_current_authenticated_user(), Club, "view")).all()) + .filter_queryset(get_current_request(), Club, "view")).all()) shuffle(clubs) self.fields["organizer"].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..." diff --git a/apps/activity/views.py b/apps/activity/views.py index 86914caf..a2ae59ab 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -74,12 +74,12 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now()) context['upcoming'] = ActivityTable( - data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")), + data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")), prefix='upcoming-', ) started_activities = Activity.objects\ - .filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\ + .filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\ .filter(open=True, valid=True).all() context["started_activities"] = started_activities @@ -98,7 +98,7 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context = super().get_context_data() table = GuestTable(data=Guest.objects.filter(activity=self.object) - .filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))) + .filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))) context["guests"] = table context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start) @@ -144,7 +144,7 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView): def get_form(self, form_class=None): form = super().get_form(form_class) - form.activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\ + form.activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\ .get(pk=self.kwargs["pk"]) form.fields["inviter"].initial = self.request.user.note return form @@ -152,7 +152,7 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView): @transaction.atomic def form_valid(self, form): form.instance.activity = Activity.objects\ - .filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"]) + .filter(PermissionBackend.filter_queryset(self.request, Activity, "view")).get(pk=self.kwargs["pk"]) return super().form_valid(form) def get_success_url(self, **kwargs): @@ -173,7 +173,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView): activity = Activity.objects.get(pk=self.kwargs["pk"]) sample_entry = Entry(activity=activity, note=self.request.user.note) - if not PermissionBackend.check_perm(self.request.user, "activity.add_entry", sample_entry): + if not PermissionBackend.check_perm(self.request, "activity.add_entry", sample_entry): raise PermissionDenied(_("You are not allowed to display the entry interface for this activity.")) if not activity.activity_type.manage_entries: @@ -191,7 +191,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView): guest_qs = Guest.objects\ .annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\ .filter(activity=activity)\ - .filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\ + .filter(PermissionBackend.filter_queryset(self.request, Guest, "view"))\ .order_by('last_name', 'first_name').distinct() if "search" in self.request.GET and self.request.GET["search"]: @@ -230,7 +230,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView): ) # Filter with permission backend - note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")) + note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")) if "search" in self.request.GET and self.request.GET["search"]: pattern = self.request.GET["search"] @@ -256,7 +256,7 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView): """ context = super().get_context_data(**kwargs) - activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\ + activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\ .distinct().get(pk=self.kwargs["pk"]) context["activity"] = activity @@ -281,9 +281,9 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView): context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk activities_open = Activity.objects.filter(open=True).filter( - PermissionBackend.filter_queryset(self.request.user, Activity, "view")).distinct().all() + PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all() context["activities_open"] = [a for a in activities_open - if PermissionBackend.check_perm(self.request.user, + if PermissionBackend.check_perm(self.request, "activity.add_entry", Entry(activity=a, note=self.request.user.note,))] diff --git a/apps/api/viewsets.py b/apps/api/viewsets.py index 25221cfc..97acac13 100644 --- a/apps/api/viewsets.py +++ b/apps/api/viewsets.py @@ -9,7 +9,6 @@ from django.contrib.auth.models import User from rest_framework.filters import SearchFilter from rest_framework.viewsets import ReadOnlyModelViewSet, ModelViewSet from permission.backends import PermissionBackend -from note_kfet.middlewares import get_current_session from note.models import Alias from .serializers import UserSerializer, ContentTypeSerializer @@ -25,9 +24,8 @@ class ReadProtectedModelViewSet(ModelViewSet): self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class() def get_queryset(self): - user = self.request.user - get_current_session().setdefault("permission_mask", 42) - return self.queryset.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct() + self.request.session.setdefault("permission_mask", 42) + return self.queryset.filter(PermissionBackend.filter_queryset(self.request, self.model, "view")).distinct() class ReadOnlyProtectedModelViewSet(ReadOnlyModelViewSet): @@ -40,9 +38,8 @@ class ReadOnlyProtectedModelViewSet(ReadOnlyModelViewSet): self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class() def get_queryset(self): - user = self.request.user - get_current_session().setdefault("permission_mask", 42) - return self.queryset.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct() + self.request.session.setdefault("permission_mask", 42) + return self.queryset.filter(PermissionBackend.filter_queryset(self.request, self.model, "view")).distinct() class UserViewSet(ReadProtectedModelViewSet): diff --git a/apps/logs/signals.py b/apps/logs/signals.py index 862dbd75..4a6f9015 100644 --- a/apps/logs/signals.py +++ b/apps/logs/signals.py @@ -5,7 +5,7 @@ from django.contrib.contenttypes.models import ContentType from rest_framework.renderers import JSONRenderer from rest_framework.serializers import ModelSerializer from note.models import NoteUser, Alias -from note_kfet.middlewares import get_current_authenticated_user, get_current_ip +from note_kfet.middlewares import get_current_request from .models import Changelog @@ -57,9 +57,9 @@ def save_object(sender, instance, **kwargs): previous = instance._previous # Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP - user, ip = get_current_authenticated_user(), get_current_ip() + request = get_current_request() - if user is None: + if request is None: # Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py` # On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée # IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info @@ -71,9 +71,23 @@ def save_object(sender, instance, **kwargs): # else: if note.exists(): user = note.get().user + else: + user = None + else: + user = request.user + if 'HTTP_X_REAL_IP' in request.META: + ip = request.META.get('HTTP_X_REAL_IP') + elif 'HTTP_X_FORWARDED_FOR' in request.META: + ip = request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0] + else: + ip = request.META.get('REMOTE_ADDR') + + if not user.is_authenticated: + # For registration purposes + user = None # noinspection PyProtectedMember - if user is not None and instance._meta.label_lower == "auth.user" and previous: + if request is not None and instance._meta.label_lower == "auth.user" and previous: # On n'enregistre pas les connexions if instance.last_login != previous.last_login: return @@ -121,9 +135,9 @@ def delete_object(sender, instance, **kwargs): return # Si un utilisateur est connecté, on récupère l'utilisateur courant ainsi que son adresse IP - user, ip = get_current_authenticated_user(), get_current_ip() + request = get_current_request() - if user is None: + if request is None: # Si la modification n'a pas été faite via le client Web, on suppose que c'est du à `manage.py` # On récupère alors l'utilisateur·trice connecté·e à la VM, et on récupère la note associée # IMPORTANT : l'utilisateur dans la VM doit être un des alias note du respo info @@ -135,6 +149,16 @@ def delete_object(sender, instance, **kwargs): # else: if note.exists(): user = note.get().user + else: + user = None + else: + user = request.user + if 'HTTP_X_REAL_IP' in request.META: + ip = request.META.get('HTTP_X_REAL_IP') + elif 'HTTP_X_FORWARDED_FOR' in request.META: + ip = request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0] + else: + ip = request.META.get('REMOTE_ADDR') # On crée notre propre sérialiseur JSON pour pouvoir sauvegarder les modèles class CustomSerializer(ModelSerializer): diff --git a/apps/member/hashers.py b/apps/member/hashers.py index 99b2c30e..69db24b0 100644 --- a/apps/member/hashers.py +++ b/apps/member/hashers.py @@ -6,7 +6,7 @@ import hashlib from django.conf import settings from django.contrib.auth.hashers import PBKDF2PasswordHasher from django.utils.crypto import constant_time_compare -from note_kfet.middlewares import get_current_authenticated_user, get_current_session +from note_kfet.middlewares import get_current_request class CustomNK15Hasher(PBKDF2PasswordHasher): @@ -24,16 +24,22 @@ class CustomNK15Hasher(PBKDF2PasswordHasher): def must_update(self, encoded): if settings.DEBUG: - current_user = get_current_authenticated_user() + # Small hack to let superusers to impersonate people. + # Don't change their password. + request = get_current_request() + current_user = request.user if current_user is not None and current_user.is_superuser: return False return True def verify(self, password, encoded): if settings.DEBUG: - current_user = get_current_authenticated_user() + # Small hack to let superusers to impersonate people. + # If a superuser is already connected, let him/her log in as another person. + request = get_current_request() + current_user = request.user if current_user is not None and current_user.is_superuser\ - and get_current_session().get("permission_mask", -1) >= 42: + and request.session.get("permission_mask", -1) >= 42: return True if '|' in encoded: @@ -51,8 +57,11 @@ class DebugSuperuserBackdoor(PBKDF2PasswordHasher): def verify(self, password, encoded): if settings.DEBUG: - current_user = get_current_authenticated_user() + # Small hack to let superusers to impersonate people. + # If a superuser is already connected, let him/her log in as another person. + request = get_current_request() + current_user = request.user if current_user is not None and current_user.is_superuser\ - and get_current_session().get("permission_mask", -1) >= 42: + and request.session.get("permission_mask", -1) >= 42: return True return super().verify(password, encoded) diff --git a/apps/member/tables.py b/apps/member/tables.py index d97da7ca..1c152526 100644 --- a/apps/member/tables.py +++ b/apps/member/tables.py @@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _ from django.urls import reverse_lazy from django.utils.html import format_html from note.templatetags.pretty_money import pretty_money -from note_kfet.middlewares import get_current_authenticated_user +from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend from .models import Club, Membership @@ -51,19 +51,19 @@ class UserTable(tables.Table): def render_email(self, record, value): # Replace the email by a dash if the user can't see the profile detail # Replace also the URL - if not PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile): + if not PermissionBackend.check_perm(get_current_request(), "member.view_profile", record.profile): value = "—" record.email = value return value def render_section(self, record, value): return value \ - if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_profile", record.profile) \ + if PermissionBackend.check_perm(get_current_request(), "member.view_profile", record.profile) \ else "—" def render_balance(self, record, value): return pretty_money(value)\ - if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", record.note) else "—" + if PermissionBackend.check_perm(get_current_request(), "note.view_note", record.note) else "—" class Meta: attrs = { @@ -93,7 +93,7 @@ class MembershipTable(tables.Table): def render_user(self, value): # If the user has the right, link the displayed user with the page of its detail. s = value.username - if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value): + if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value): s = format_html("{name}", url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s) @@ -102,7 +102,7 @@ class MembershipTable(tables.Table): def render_club(self, value): # If the user has the right, link the displayed club with the page of its detail. s = value.name - if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_club", value): + if PermissionBackend.check_perm(get_current_request(), "member.view_club", value): s = format_html("{name}", url=reverse_lazy('member:club_detail', kwargs={"pk": value.pk}), name=s) @@ -127,7 +127,7 @@ class MembershipTable(tables.Table): date_end=date.today(), fee=0, ) - if PermissionBackend.check_perm(get_current_authenticated_user(), + if PermissionBackend.check_perm(get_current_request(), "member.add_membership", empty_membership): # If the user has right renew_url = reverse_lazy('member:club_renew_membership', kwargs={"pk": record.pk}) @@ -142,7 +142,7 @@ class MembershipTable(tables.Table): # If the user has the right to manage the roles, display the link to manage them roles = record.roles.all() s = ", ".join(str(role) for role in roles) - if PermissionBackend.check_perm(get_current_authenticated_user(), "member.change_membership_roles", record): + if PermissionBackend.check_perm(get_current_request(), "member.change_membership_roles", record): s = format_html("" + s + "") return s @@ -165,7 +165,7 @@ class ClubManagerTable(tables.Table): def render_user(self, value): # If the user has the right, link the displayed user with the page of its detail. s = value.username - if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value): + if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value): s = format_html("{name}", url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s) diff --git a/apps/member/views.py b/apps/member/views.py index 0f288d60..39edcc0b 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -71,7 +71,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): form.fields['email'].required = True form.fields['email'].help_text = _("This address must be valid.") - if PermissionBackend.check_perm(self.request.user, "member.change_profile", context['user_object'].profile): + if PermissionBackend.check_perm(self.request, "member.change_profile", context['user_object'].profile): context['profile_form'] = self.profile_form(instance=context['user_object'].profile, data=self.request.POST if self.request.POST else None) if not self.object.profile.report_frequency: @@ -154,13 +154,13 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): history_list = \ Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))\ .order_by("-created_at")\ - .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")) + .filter(PermissionBackend.filter_queryset(self.request, Transaction, "view")) history_table = HistoryTable(history_list, prefix='transaction-') history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1)) context['history_list'] = history_table club_list = Membership.objects.filter(user=user, date_end__gte=date.today() - timedelta(days=15))\ - .filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\ + .filter(PermissionBackend.filter_queryset(self.request, Membership, "view"))\ .order_by("club__name", "-date_start") # Display only the most recent membership club_list = club_list.distinct("club__name")\ @@ -177,21 +177,20 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): modified_note.is_active = True modified_note.inactivity_reason = 'manual' context["can_lock_note"] = user.note.is_active and PermissionBackend\ - .check_perm(self.request.user, "note.change_noteuser_is_active", - modified_note) + .check_perm(self.request, "note.change_noteuser_is_active", modified_note) old_note = NoteUser.objects.select_for_update().get(pk=user.note.pk) modified_note.inactivity_reason = 'forced' modified_note._force_save = True modified_note.save() context["can_force_lock"] = user.note.is_active and PermissionBackend\ - .check_perm(self.request.user, "note.change_note_is_active", modified_note) + .check_perm(self.request, "note.change_note_is_active", modified_note) old_note._force_save = True old_note._no_signal = True old_note.save() modified_note.refresh_from_db() modified_note.is_active = True context["can_unlock_note"] = not user.note.is_active and PermissionBackend\ - .check_perm(self.request.user, "note.change_note_is_active", modified_note) + .check_perm(self.request, "note.change_note_is_active", modified_note) return context @@ -238,7 +237,7 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view"))\ + pre_registered_users = User.objects.filter(PermissionBackend.filter_queryset(self.request, User, "view"))\ .filter(profile__registration_valid=False) context["can_manage_registrations"] = pre_registered_users.exists() return context @@ -257,8 +256,8 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context = super().get_context_data(**kwargs) note = context['object'].note context["aliases"] = AliasTable( - note.alias.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all()) - context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias( + note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all()) + context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias( note=context["object"].note, name="", normalized_name="", @@ -383,7 +382,7 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["can_add_club"] = PermissionBackend.check_perm(self.request.user, "member.add_club", Club( + context["can_add_club"] = PermissionBackend.check_perm(self.request, "member.add_club", Club( name="", email="club@example.com", )) @@ -405,7 +404,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context = super().get_context_data(**kwargs) club = context["club"] - if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club): + if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club): club.update_membership_dates() # managers list managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club", @@ -414,7 +413,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context["managers"] = ClubManagerTable(data=managers, prefix="managers-") # transaction history club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\ - .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\ + .filter(PermissionBackend.filter_queryset(self.request, Transaction, "view"))\ .order_by('-created_at') history_table = HistoryTable(club_transactions, prefix="history-") history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1)) @@ -423,7 +422,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): club_member = Membership.objects.filter( club=club, date_end__gte=date.today() - timedelta(days=15), - ).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\ + ).filter(PermissionBackend.filter_queryset(self.request, Membership, "view"))\ .order_by("user__username", "-date_start") # Display only the most recent membership club_member = club_member.distinct("user__username")\ @@ -460,8 +459,8 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context = super().get_context_data(**kwargs) note = context['object'].note context["aliases"] = AliasTable(note.alias.filter( - PermissionBackend.filter_queryset(self.request.user, Alias, "view")).distinct().all()) - context["can_create"] = PermissionBackend.check_perm(self.request.user, "note.add_alias", Alias( + PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all()) + context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias( note=context["object"].note, name="", normalized_name="", @@ -536,7 +535,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): form = context['form'] if "club_pk" in self.kwargs: # We create a new membership. - club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\ + club = Club.objects.filter(PermissionBackend.filter_queryset(self.request, Club, "view"))\ .get(pk=self.kwargs["club_pk"], weiclub=None) form.fields['credit_amount'].initial = club.membership_fee_paid # Ensure that the user is member of the parent club and all its the family tree. @@ -684,7 +683,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): """ # Get the club that is concerned by the membership if "club_pk" in self.kwargs: # get from url of new membership - club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \ + club = Club.objects.filter(PermissionBackend.filter_queryset(self.request, Club, "view")) \ .get(pk=self.kwargs["club_pk"]) user = form.instance.user old_membership = None @@ -868,7 +867,7 @@ class ClubMembersListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) club = Club.objects.filter( - PermissionBackend.filter_queryset(self.request.user, Club, "view") + PermissionBackend.filter_queryset(self.request, Club, "view") ).get(pk=self.kwargs["pk"]) context["club"] = club diff --git a/apps/note/api/serializers.py b/apps/note/api/serializers.py index f4905103..7dda6dba 100644 --- a/apps/note/api/serializers.py +++ b/apps/note/api/serializers.py @@ -8,7 +8,7 @@ from rest_framework.exceptions import ValidationError from rest_polymorphic.serializers import PolymorphicSerializer from member.api.serializers import MembershipSerializer from member.models import Membership -from note_kfet.middlewares import get_current_authenticated_user +from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend from rest_framework.utils import model_meta @@ -126,7 +126,7 @@ class ConsumerSerializer(serializers.ModelSerializer): """ # If the user has no right to see the note, then we only display the note identifier return NotePolymorphicSerializer().to_representation(obj.note)\ - if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", obj.note)\ + if PermissionBackend.check_perm(get_current_request(), "note.view_note", obj.note)\ else dict( id=obj.note.id, name=str(obj.note), @@ -142,7 +142,7 @@ class ConsumerSerializer(serializers.ModelSerializer): def get_membership(self, obj): if isinstance(obj.note, NoteUser): memberships = Membership.objects.filter( - PermissionBackend.filter_queryset(get_current_authenticated_user(), Membership, "view")).filter( + PermissionBackend.filter_queryset(get_current_request(), Membership, "view")).filter( user=obj.note.user, club=2, # Kfet ).order_by("-date_start") diff --git a/apps/note/api/views.py b/apps/note/api/views.py index 594b2b9c..e4f7404c 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -10,7 +10,6 @@ from rest_framework import viewsets from rest_framework.response import Response from rest_framework import status from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet -from note_kfet.middlewares import get_current_session from permission.backends import PermissionBackend from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\ @@ -40,12 +39,12 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet): Parse query and apply filters. :return: The filtered set of requested notes """ - user = self.request.user - get_current_session().setdefault("permission_mask", 42) - queryset = self.queryset.filter(PermissionBackend.filter_queryset(user, Note, "view") - | PermissionBackend.filter_queryset(user, NoteUser, "view") - | PermissionBackend.filter_queryset(user, NoteClub, "view") - | PermissionBackend.filter_queryset(user, NoteSpecial, "view")).distinct() + self.request.session.setdefault("permission_mask", 42) + queryset = self.queryset.filter(PermissionBackend.filter_queryset(self.request, Note, "view") + | PermissionBackend.filter_queryset(self.request, NoteUser, "view") + | PermissionBackend.filter_queryset(self.request, NoteClub, "view") + | PermissionBackend.filter_queryset(self.request, NoteSpecial, "view"))\ + .distinct() alias = self.request.query_params.get("alias", ".*") queryset = queryset.filter( @@ -205,7 +204,6 @@ class TransactionViewSet(ReadProtectedModelViewSet): ordering_fields = ['created_at', 'amount', ] def get_queryset(self): - user = self.request.user - get_current_session().setdefault("permission_mask", 42) - return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))\ + self.request.session.setdefault("permission_mask", 42) + return self.model.objects.filter(PermissionBackend.filter_queryset(self.request, self.model, "view"))\ .order_by("created_at", "id") diff --git a/apps/note/tables.py b/apps/note/tables.py index e98a9b0b..518173c6 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -7,7 +7,7 @@ import django_tables2 as tables from django.utils.html import format_html from django_tables2.utils import A from django.utils.translation import gettext_lazy as _ -from note_kfet.middlewares import get_current_authenticated_user +from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend from .models.notes import Alias @@ -88,16 +88,16 @@ class HistoryTable(tables.Table): "class": lambda record: str(record.valid).lower() + (' validate' if record.source.is_active and record.destination.is_active and PermissionBackend - .check_perm(get_current_authenticated_user(), "note.change_transaction_invalidity_reason", record) + .check_perm(get_current_request(), "note.change_transaction_invalidity_reason", record) else ''), "data-toggle": "tooltip", "title": lambda record: (_("Click to invalidate") if record.valid else _("Click to validate")) - if PermissionBackend.check_perm(get_current_authenticated_user(), + if PermissionBackend.check_perm(get_current_request(), "note.change_transaction_invalidity_reason", record) and record.source.is_active and record.destination.is_active else None, "onclick": lambda record: 'de_validate(' + str(record.id) + ', ' + str(record.valid).lower() + ', "' + str(record.__class__.__name__) + '")' - if PermissionBackend.check_perm(get_current_authenticated_user(), + if PermissionBackend.check_perm(get_current_request(), "note.change_transaction_invalidity_reason", record) and record.source.is_active and record.destination.is_active else None, "onmouseover": lambda record: '$("#invalidity_reason_' @@ -126,7 +126,7 @@ class HistoryTable(tables.Table): When the validation status is hovered, an input field is displayed to let the user specify an invalidity reason """ has_perm = PermissionBackend \ - .check_perm(get_current_authenticated_user(), "note.change_transaction_invalidity_reason", record) + .check_perm(get_current_request(), "note.change_transaction_invalidity_reason", record) val = "✔" if value else "✖" @@ -165,7 +165,7 @@ class AliasTable(tables.Table): extra_context={"delete_trans": _('delete')}, attrs={'td': {'class': lambda record: 'col-sm-1' + ( ' d-none' if not PermissionBackend.check_perm( - get_current_authenticated_user(), "note.delete_alias", + get_current_request(), "note.delete_alias", record) else '')}}, verbose_name=_("Delete"), ) diff --git a/apps/note/views.py b/apps/note/views.py index 73e5a084..279eadc6 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -38,7 +38,7 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl def get_queryset(self, **kwargs): # retrieves only Transaction that user has the right to see. return Transaction.objects.filter( - PermissionBackend.filter_queryset(self.request.user, Transaction, "view") + PermissionBackend.filter_queryset(self.request, Transaction, "view") ).order_by("-created_at").all()[:20] def get_context_data(self, **kwargs): @@ -47,16 +47,16 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk context['special_types'] = NoteSpecial.objects\ - .filter(PermissionBackend.filter_queryset(self.request.user, NoteSpecial, "view"))\ + .filter(PermissionBackend.filter_queryset(self.request, NoteSpecial, "view"))\ .order_by("special_type").all() # Add a shortcut for entry page for open activities if "activity" in settings.INSTALLED_APPS: from activity.models import Activity activities_open = Activity.objects.filter(open=True).filter( - PermissionBackend.filter_queryset(self.request.user, Activity, "view")).distinct().all() + PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all() context["activities_open"] = [a for a in activities_open - if PermissionBackend.check_perm(self.request.user, + if PermissionBackend.check_perm(self.request, "activity.add_entry", Entry(activity=a, note=self.request.user.note, ))] @@ -159,7 +159,7 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): return self.handle_no_permission() templates = TransactionTemplate.objects.filter( - PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view") + PermissionBackend().filter_queryset(self.request, TransactionTemplate, "view") ) if not templates.exists(): raise PermissionDenied(_("You can't see any button.")) @@ -170,7 +170,7 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): restrict to the transaction history the user can see. """ return Transaction.objects.filter( - PermissionBackend.filter_queryset(self.request.user, Transaction, "view") + PermissionBackend.filter_queryset(self.request, Transaction, "view") ).order_by("-created_at").all()[:20] def get_context_data(self, **kwargs): @@ -180,13 +180,13 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): # for each category, find which transaction templates the user can see. for category in categories: category.templates_filtered = category.templates.filter( - PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view") + PermissionBackend().filter_queryset(self.request, TransactionTemplate, "view") ).filter(display=True).order_by('name').all() context['categories'] = [cat for cat in categories if cat.templates_filtered] # some transactiontemplate are put forward to find them easily context['highlighted'] = TransactionTemplate.objects.filter(highlighted=True).filter( - PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view") + PermissionBackend().filter_queryset(self.request, TransactionTemplate, "view") ).order_by('name').all() context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk @@ -209,7 +209,7 @@ class TransactionSearchView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView data = form.cleaned_data if form.is_valid() else {} transactions = Transaction.objects.annotate(total_amount=F("quantity") * F("amount")).filter( - PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\ + PermissionBackend.filter_queryset(self.request, Transaction, "view"))\ .filter(Q(source=self.object) | Q(destination=self.object)).order_by('-created_at') if "source" in data and data["source"]: diff --git a/apps/permission/backends.py b/apps/permission/backends.py index b43340f0..b4c08efd 100644 --- a/apps/permission/backends.py +++ b/apps/permission/backends.py @@ -4,12 +4,12 @@ from datetime import date from django.contrib.auth.backends import ModelBackend -from django.contrib.auth.models import User, AnonymousUser +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_session +from note_kfet.middlewares import get_current_request from member.models import Membership, Club from .decorators import memoize @@ -33,7 +33,7 @@ class PermissionBackend(ModelBackend): :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): + if not user.is_authenticated: # Unauthenticated users have no permissions return Permission.objects.none() @@ -43,7 +43,8 @@ class PermissionBackend(ModelBackend): for membership in memberships: for role in membership.roles.all(): - for perm in role.permissions.filter(type=t, mask__rank__lte=get_current_session().get("permission_mask", -1)).all(): + for perm in role.permissions.filter( + type=t, mask__rank__lte=get_current_request().session.get("permission_mask", -1)).all(): if not perm.permanent: if membership.date_start > date.today() or membership.date_end < date.today(): continue @@ -88,20 +89,22 @@ class PermissionBackend(ModelBackend): @staticmethod @memoize - def filter_queryset(user, model, t, field=None): + def filter_queryset(request, 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 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 user is None or isinstance(user, AnonymousUser): + user = request.user + + if user is None or not user.is_authenticated: # Anonymous users can't do anything return Q(pk=-1) - if user.is_superuser and get_current_session().get("permission_mask", -1) >= 42: + if user.is_superuser and get_current_request().session.get("permission_mask", -1) >= 42: # Superusers have all rights return Q() @@ -122,7 +125,7 @@ class PermissionBackend(ModelBackend): @staticmethod @memoize - def check_perm(user_obj, perm, obj=None): + def check_perm(request, perm, obj=None): """ Check is the given user has the permission over a given object. The result is then memoized. @@ -130,10 +133,12 @@ class PermissionBackend(ModelBackend): primary key, the result is not memoized. Moreover, the right could change (e.g. for a transaction, the balance of the user could change) """ - if user_obj is None or isinstance(user_obj, AnonymousUser): + user_obj = request.user + + if user_obj is None or not user_obj.is_authenticated: return False - sess = get_current_session() + sess = request.session if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42: return True @@ -152,7 +157,10 @@ class PermissionBackend(ModelBackend): return False def has_perm(self, user_obj, perm, obj=None): - return PermissionBackend.check_perm(user_obj, perm, obj) + # 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 diff --git a/apps/permission/decorators.py b/apps/permission/decorators.py index 7f5b48b0..0e79df90 100644 --- a/apps/permission/decorators.py +++ b/apps/permission/decorators.py @@ -5,7 +5,7 @@ from functools import lru_cache from time import time from django.contrib.sessions.models import Session -from note_kfet.middlewares import get_current_session +from note_kfet.middlewares import get_current_request def memoize(f): @@ -48,11 +48,11 @@ def memoize(f): last_collect = time() # If there is no session, then we don't memoize anything. - sess = get_current_session() - if sess is None or sess.session_key is None: + request = get_current_request() + if request is None or request.session is None or request.session.session_key is None: return f(*args, **kwargs) - sess_key = sess.session_key + sess_key = request.session.session_key if sess_key not in sess_funs: # lru_cache makes the job of memoization # We store only the 512 latest data per session. It has to be enough. diff --git a/apps/permission/permissions.py b/apps/permission/permissions.py index 9a0c1e12..1a5f51e5 100644 --- a/apps/permission/permissions.py +++ b/apps/permission/permissions.py @@ -45,7 +45,7 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions): perms = self.get_required_object_permissions(request.method, model_cls) # if not user.has_perms(perms, obj): - if not all(PermissionBackend.check_perm(user, perm, obj) for perm in perms): + if not all(PermissionBackend.check_perm(request, perm, obj) for perm in perms): # If the user does not have permissions we need to determine if # they have read permissions to see 403, or not, and simply see # a 404 response. diff --git a/apps/permission/signals.py b/apps/permission/signals.py index b419ce09..2e4d6ea9 100644 --- a/apps/permission/signals.py +++ b/apps/permission/signals.py @@ -3,7 +3,7 @@ from django.core.exceptions import PermissionDenied from django.utils.translation import gettext_lazy as _ -from note_kfet.middlewares import get_current_authenticated_user +from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend @@ -31,8 +31,8 @@ def pre_save_object(sender, instance, **kwargs): if hasattr(instance, "_force_save") or hasattr(instance, "_no_signal"): return - user = get_current_authenticated_user() - if user is None: + request = get_current_request() + if request is None: # Action performed on shell is always granted return @@ -45,7 +45,7 @@ def pre_save_object(sender, instance, **kwargs): # We check if the user can change the model # If the user has all right on a model, then OK - if PermissionBackend.check_perm(user, app_label + ".change_" + model_name, instance): + if PermissionBackend.check_perm(request, app_label + ".change_" + model_name, instance): return # In the other case, we check if he/she has the right to change one field @@ -58,7 +58,8 @@ def pre_save_object(sender, instance, **kwargs): # If the field wasn't modified, no need to check the permissions if old_value == new_value: continue - if not PermissionBackend.check_perm(user, app_label + ".change_" + model_name + "_" + field_name, instance): + if not PermissionBackend.check_perm(request, app_label + ".change_" + model_name + "_" + field_name, + instance): raise PermissionDenied( _("You don't have the permission to change the field {field} on this instance of model" " {app_label}.{model_name}.") @@ -66,7 +67,7 @@ def pre_save_object(sender, instance, **kwargs): ) else: # We check if the user has right to add the object - has_perm = PermissionBackend.check_perm(user, app_label + ".add_" + model_name, instance) + has_perm = PermissionBackend.check_perm(request, app_label + ".add_" + model_name, instance) if not has_perm: raise PermissionDenied( @@ -87,8 +88,8 @@ def pre_delete_object(instance, **kwargs): # Don't check permissions on force-deleted objects return - user = get_current_authenticated_user() - if user is None: + request = get_current_request() + if request is None: # Action performed on shell is always granted return @@ -97,7 +98,7 @@ def pre_delete_object(instance, **kwargs): model_name = model_name_full[1] # We check if the user has rights to delete the object - if not PermissionBackend.check_perm(user, app_label + ".delete_" + model_name, instance): + if not PermissionBackend.check_perm(request, app_label + ".delete_" + model_name, instance): raise PermissionDenied( _("You don't have the permission to delete this instance of model {app_label}.{model_name}.") .format(app_label=app_label, model_name=model_name)) diff --git a/apps/permission/tables.py b/apps/permission/tables.py index 9e82fa8e..eaec5138 100644 --- a/apps/permission/tables.py +++ b/apps/permission/tables.py @@ -8,7 +8,7 @@ from django.urls import reverse_lazy from django.utils.html import format_html from django_tables2 import A from member.models import Membership -from note_kfet.middlewares import get_current_authenticated_user +from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend @@ -20,7 +20,7 @@ class RightsTable(tables.Table): def render_user(self, value): # If the user has the right, link the displayed user with the page of its detail. s = value.username - if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value): + if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value): s = format_html("{name}", url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s) return s @@ -28,7 +28,7 @@ class RightsTable(tables.Table): def render_club(self, value): # If the user has the right, link the displayed user with the page of its detail. s = value.name - if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_club", value): + if PermissionBackend.check_perm(get_current_request(), "member.view_club", value): s = format_html("{name}", url=reverse_lazy('member:club_detail', kwargs={"pk": value.pk}), name=s) @@ -42,7 +42,7 @@ class RightsTable(tables.Table): | Q(name="Bureau de club")) & Q(weirole__isnull=True))).all() s = ", ".join(str(role) for role in roles) - if PermissionBackend.check_perm(get_current_authenticated_user(), "member.change_membership_roles", record): + if PermissionBackend.check_perm(get_current_request(), "member.change_membership_roles", record): s = format_html("" + s + "") return s diff --git a/apps/permission/templatetags/perms.py b/apps/permission/templatetags/perms.py index 2fb376d4..17f336f4 100644 --- a/apps/permission/templatetags/perms.py +++ b/apps/permission/templatetags/perms.py @@ -1,12 +1,12 @@ # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from django.contrib.auth.models import AnonymousUser from django.contrib.contenttypes.models import ContentType from django.template.defaultfilters import stringfilter from django import template -from note_kfet.middlewares import get_current_authenticated_user, get_current_session -from permission.backends import PermissionBackend +from note_kfet.middlewares import get_current_request + +from ..backends import PermissionBackend @stringfilter @@ -14,9 +14,10 @@ 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() - session = get_current_session() - if user is None or isinstance(user, AnonymousUser): + request = get_current_request() + user = request.user + session = request.session + if user is None or not user.is_authenticated: return False elif user.is_superuser and session.get("permission_mask", -1) >= 42: return True @@ -29,11 +30,12 @@ def model_list(model_name, t="view", fetch=True): """ Return the queryset of all visible instances of the given model. """ - user = get_current_authenticated_user() + request = get_current_request() + user = request.user spl = model_name.split(".") ct = ContentType.objects.get(app_label=spl[0], model=spl[1]) - qs = ct.model_class().objects.filter(PermissionBackend.filter_queryset(user, ct, t)) - if user is None or isinstance(user, AnonymousUser): + qs = ct.model_class().objects.filter(PermissionBackend.filter_queryset(request, ct, t)) + if user is None or not user.is_authenticated: return qs.none() if fetch: qs = qs.all() @@ -49,7 +51,7 @@ def model_list_length(model_name, t="view"): def has_perm(perm, obj): - return PermissionBackend.check_perm(get_current_authenticated_user(), perm, obj) + return PermissionBackend.check_perm(get_current_request(), perm, obj) register = template.Library() diff --git a/apps/permission/views.py b/apps/permission/views.py index c48215a0..25066731 100644 --- a/apps/permission/views.py +++ b/apps/permission/views.py @@ -28,7 +28,7 @@ class ProtectQuerysetMixin: """ def get_queryset(self, filter_permissions=True, **kwargs): qs = super().get_queryset(**kwargs) - return qs.filter(PermissionBackend.filter_queryset(self.request.user, qs.model, "view")).distinct()\ + return qs.filter(PermissionBackend.filter_queryset(self.request, qs.model, "view")).distinct()\ if filter_permissions else qs def get_object(self, queryset=None): @@ -53,7 +53,7 @@ class ProtectQuerysetMixin: # We could also delete the field, but some views might be affected. meta = form.instance._meta for key in form.base_fields: - if not PermissionBackend.check_perm(self.request.user, + if not PermissionBackend.check_perm(self.request, f"{meta.app_label}.change_{meta.model_name}_" + key, self.object): form.fields[key].widget = HiddenInput() @@ -101,7 +101,7 @@ class ProtectedCreateView(LoginRequiredMixin, CreateView): # noinspection PyProtectedMember app_label, model_name = model_class._meta.app_label, model_class._meta.model_name.lower() perm = app_label + ".add_" + model_name - if not PermissionBackend.check_perm(request.user, perm, self.get_sample_object()): + if not PermissionBackend.check_perm(request, perm, self.get_sample_object()): raise PermissionDenied(_("You don't have the permission to add an instance of model " "{app_label}.{model_name}.").format(app_label=app_label, model_name=model_name)) return super().dispatch(request, *args, **kwargs) diff --git a/apps/registration/views.py b/apps/registration/views.py index a680996d..9b385324 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -66,9 +66,11 @@ class UserCreateView(CreateView): profile_form.instance.user = user profile = profile_form.save(commit=False) user.profile = profile + user._force_save = True user.save() user.refresh_from_db() profile.user = user + profile._force_save = True profile.save() user.profile.send_email_validation_link() @@ -110,7 +112,9 @@ class UserValidateView(TemplateView): self.validlink = True user.is_active = user.profile.registration_valid or user.is_superuser user.profile.email_confirmed = True + user._force_save = True user.save() + user.profile._force_save = True user.profile.save() return self.render_to_response(self.get_context_data(), status=200 if self.validlink else 400) @@ -384,7 +388,7 @@ class FutureUserInvalidateView(ProtectQuerysetMixin, LoginRequiredMixin, View): Delete the pre-registered user which id is given in the URL. """ user = User.objects.filter(profile__registration_valid=False)\ - .filter(PermissionBackend.filter_queryset(request.user, User, "change", "is_valid"))\ + .filter(PermissionBackend.filter_queryset(request, User, "change", "is_valid"))\ .get(pk=self.kwargs["pk"]) # Delete associated soge credits before SogeCredit.objects.filter(user=user).delete() diff --git a/apps/treasury/views.py b/apps/treasury/views.py index 47544cc1..b9a7fe7c 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -107,7 +107,7 @@ class InvoiceListView(LoginRequiredMixin, SingleTableView): name="", address="", ) - if not PermissionBackend.check_perm(self.request.user, "treasury.add_invoice", sample_invoice): + if not PermissionBackend.check_perm(self.request, "treasury.add_invoice", sample_invoice): raise PermissionDenied(_("You are not able to see the treasury interface.")) return super().dispatch(request, *args, **kwargs) @@ -194,7 +194,7 @@ class InvoiceRenderView(LoginRequiredMixin, View): def get(self, request, **kwargs): pk = kwargs["pk"] - invoice = Invoice.objects.filter(PermissionBackend.filter_queryset(request.user, Invoice, "view")).get(pk=pk) + invoice = Invoice.objects.filter(PermissionBackend.filter_queryset(request, Invoice, "view")).get(pk=pk) tex = invoice.tex try: @@ -259,7 +259,7 @@ class RemittanceCreateView(ProtectQuerysetMixin, ProtectedCreateView): context["table"] = RemittanceTable( data=Remittance.objects.filter( - PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all()) + PermissionBackend.filter_queryset(self.request, Remittance, "view")).all()) context["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none()) return context @@ -281,7 +281,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): remittance_type_id=1, comment="", ) - if not PermissionBackend.check_perm(self.request.user, "treasury.add_remittance", sample_remittance): + if not PermissionBackend.check_perm(self.request, "treasury.add_remittance", sample_remittance): raise PermissionDenied(_("You are not able to see the treasury interface.")) return super().dispatch(request, *args, **kwargs) @@ -290,7 +290,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): opened_remittances = RemittanceTable( data=Remittance.objects.filter(closed=False).filter( - PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(), + PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), prefix="opened-remittances-", ) opened_remittances.paginate(page=self.request.GET.get("opened-remittances-page", 1), per_page=10) @@ -298,7 +298,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): closed_remittances = RemittanceTable( data=Remittance.objects.filter(closed=True).filter( - PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(), + PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), prefix="closed-remittances-", ) closed_remittances.paginate(page=self.request.GET.get("closed-remittances-page", 1), per_page=10) @@ -307,7 +307,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): no_remittance_tr = SpecialTransactionTable( data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), specialtransactionproxy__remittance=None).filter( - PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(), + PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), exclude=('remittance_remove', ), prefix="no-remittance-", ) @@ -317,7 +317,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): with_remittance_tr = SpecialTransactionTable( data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), specialtransactionproxy__remittance__closed=False).filter( - PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(), + PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), exclude=('remittance_add', ), prefix="with-remittance-", ) @@ -342,7 +342,7 @@ class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView) context = super().get_context_data(**kwargs) data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter( - PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all() + PermissionBackend.filter_queryset(self.request, Remittance, "view")).all() context["special_transactions"] = SpecialTransactionTable( data=data, exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', )) diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 274e8dbd..b2e55508 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -8,7 +8,7 @@ from django.urls import reverse_lazy from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from django_tables2 import A -from note_kfet.middlewares import get_current_authenticated_user +from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend from .models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership @@ -85,7 +85,7 @@ class WEIRegistrationTable(tables.Table): def render_validate(self, record): hasperm = PermissionBackend.check_perm( - get_current_authenticated_user(), "wei.add_weimembership", WEIMembership( + get_current_request(), "wei.add_weimembership", WEIMembership( club=record.wei, user=record.user, date_start=date.today(), @@ -110,7 +110,7 @@ class WEIRegistrationTable(tables.Table): f"title=\"{tooltip}\" href=\"{url}\">{text}") def render_delete(self, record): - hasperm = PermissionBackend.check_perm(get_current_authenticated_user(), "wei.delete_weimembership", record) + hasperm = PermissionBackend.check_perm(get_current_request(), "wei.delete_weimembership", record) return _("Delete") if hasperm else format_html("") class Meta: diff --git a/apps/wei/views.py b/apps/wei/views.py index 04efe954..cf7c3911 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -57,7 +57,7 @@ class WEIListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["can_create_wei"] = PermissionBackend.check_perm(self.request.user, "wei.add_weiclub", WEIClub( + context["can_create_wei"] = PermissionBackend.check_perm(self.request, "wei.add_weiclub", WEIClub( name="", email="weiclub@example.com", year=0, @@ -112,7 +112,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): club = context["club"] club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note)) \ - .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")) \ + .filter(PermissionBackend.filter_queryset(self.request, Transaction, "view")) \ .order_by('-created_at', '-id') history_table = HistoryTable(club_transactions, prefix="history-") history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1)) @@ -121,13 +121,13 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): club_member = WEIMembership.objects.filter( club=club, date_end__gte=date.today(), - ).filter(PermissionBackend.filter_queryset(self.request.user, WEIMembership, "view")) + ).filter(PermissionBackend.filter_queryset(self.request, WEIMembership, "view")) membership_table = WEIMembershipTable(data=club_member, prefix="membership-") membership_table.paginate(per_page=20, page=self.request.GET.get('membership-page', 1)) context['member_list'] = membership_table pre_registrations = WEIRegistration.objects.filter( - PermissionBackend.filter_queryset(self.request.user, WEIRegistration, "view")).filter( + PermissionBackend.filter_queryset(self.request, WEIRegistration, "view")).filter( membership=None, wei=club ) @@ -142,7 +142,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): my_registration = None context["my_registration"] = my_registration - buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request.user, Bus, "view")) \ + buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \ .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name") bus_table = BusTable(data=buses, prefix="bus-") context['buses'] = bus_table @@ -167,7 +167,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): emergency_contact_phone="No", ) context["can_add_first_year_member"] = PermissionBackend \ - .check_perm(self.request.user, "wei.add_weiregistration", empty_fy_registration) + .check_perm(self.request, "wei.add_weiregistration", empty_fy_registration) # Check if the user has the right to create a registration of a random old member. empty_old_registration = WEIRegistration( @@ -180,13 +180,13 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): emergency_contact_phone="No", ) context["can_add_any_member"] = PermissionBackend \ - .check_perm(self.request.user, "wei.add_weiregistration", empty_old_registration) + .check_perm(self.request, "wei.add_weiregistration", empty_old_registration) empty_bus = Bus( wei=club, name="", ) - context["can_add_bus"] = PermissionBackend.check_perm(self.request.user, "wei.add_bus", empty_bus) + context["can_add_bus"] = PermissionBackend.check_perm(self.request, "wei.add_bus", empty_bus) context["not_first_year"] = WEIMembership.objects.filter(user=self.request.user).exists() @@ -370,13 +370,13 @@ class BusManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context["club"] = self.object.wei bus = self.object - teams = BusTeam.objects.filter(PermissionBackend.filter_queryset(self.request.user, BusTeam, "view")) \ + teams = BusTeam.objects.filter(PermissionBackend.filter_queryset(self.request, BusTeam, "view")) \ .filter(bus=bus).annotate(count=Count("memberships")).order_by("name") teams_table = BusTeamTable(data=teams, prefix="team-") context["teams"] = teams_table memberships = WEIMembership.objects.filter(PermissionBackend.filter_queryset( - self.request.user, WEIMembership, "view")).filter(bus=bus) + self.request, WEIMembership, "view")).filter(bus=bus) memberships_table = WEIMembershipTable(data=memberships, prefix="membership-") memberships_table.paginate(per_page=20, page=self.request.GET.get("membership-page", 1)) context["memberships"] = memberships_table @@ -469,7 +469,7 @@ class BusTeamManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context["club"] = self.object.bus.wei memberships = WEIMembership.objects.filter(PermissionBackend.filter_queryset( - self.request.user, WEIMembership, "view")).filter(team=self.object) + self.request, WEIMembership, "view")).filter(team=self.object) memberships_table = WEIMembershipTable(data=memberships, prefix="membership-") memberships_table.paginate(per_page=20, page=self.request.GET.get("membership-page", 1)) context["memberships"] = memberships_table @@ -659,7 +659,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update data=self.request.POST if self.request.POST else None) for field_name, field in membership_form.fields.items(): if not PermissionBackend.check_perm( - self.request.user, "wei.change_membership_" + field_name, self.object.membership): + self.request, "wei.change_membership_" + field_name, self.object.membership): field.widget = HiddenInput() del membership_form.fields["credit_type"] del membership_form.fields["credit_amount"] @@ -668,7 +668,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update del membership_form.fields["bank"] context["membership_form"] = membership_form elif not self.object.first_year and PermissionBackend.check_perm( - self.request.user, "wei.change_weiregistration_information_json", self.object): + self.request, "wei.change_weiregistration_information_json", self.object): choose_bus_form = WEIChooseBusForm( self.request.POST if self.request.POST else dict( bus=Bus.objects.filter(pk__in=self.object.information["preferred_bus_pk"]).all(), @@ -704,7 +704,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update membership_form.save() # If it is not validated and if this is an old member, then we update the choices elif not form.instance.first_year and PermissionBackend.check_perm( - self.request.user, "wei.change_weiregistration_information_json", self.object): + self.request, "wei.change_weiregistration_information_json", self.object): choose_bus_form = WEIChooseBusForm(self.request.POST) if not choose_bus_form.is_valid(): return self.form_invalid(form) @@ -726,7 +726,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update survey = CurrentSurvey(self.object) if not survey.is_complete(): return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk}) - if PermissionBackend.check_perm(self.request.user, "wei.add_weimembership", WEIMembership( + if PermissionBackend.check_perm(self.request, "wei.add_weimembership", WEIMembership( club=self.object.wei, user=self.object.user, date_start=date.today(), @@ -753,7 +753,7 @@ class WEIDeleteRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Delete if today > wei.membership_end: return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) - if not PermissionBackend.check_perm(self.request.user, "wei.delete_weiregistration", object): + if not PermissionBackend.check_perm(self.request, "wei.delete_weiregistration", object): raise PermissionDenied(_("You don't have the right to delete this WEI registration.")) return super().dispatch(request, *args, **kwargs) @@ -1049,7 +1049,7 @@ class MemberListRenderView(LoginRequiredMixin, View): """ def get_queryset(self, **kwargs): - qs = WEIMembership.objects.filter(PermissionBackend.filter_queryset(self.request.user, WEIMembership, "view")) + qs = WEIMembership.objects.filter(PermissionBackend.filter_queryset(self.request, WEIMembership, "view")) qs = qs.filter(club__pk=self.kwargs["wei_pk"]).order_by( Lower('bus__name'), Lower('team__name'), diff --git a/note_kfet/admin.py b/note_kfet/admin.py index 375122d8..dc209c67 100644 --- a/note_kfet/admin.py +++ b/note_kfet/admin.py @@ -6,7 +6,6 @@ from django.contrib.admin import AdminSite from django.contrib.sites.admin import Site, SiteAdmin from member.views import CustomLoginView -from .middlewares import get_current_session class StrongAdminSite(AdminSite): @@ -14,8 +13,7 @@ class StrongAdminSite(AdminSite): """ Authorize only staff that have the correct permission mask """ - session = get_current_session() - return request.user.is_active and request.user.is_staff and session.get("permission_mask", -1) >= 42 + return request.user.is_active and request.user.is_staff and request.session.get("permission_mask", -1) >= 42 def login(self, request, extra_context=None): return CustomLoginView.as_view()(request) diff --git a/note_kfet/middlewares.py b/note_kfet/middlewares.py index 75a86bf2..e763a571 100644 --- a/note_kfet/middlewares.py +++ b/note_kfet/middlewares.py @@ -2,13 +2,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later from threading import local -from typing import Optional from django.conf import settings from django.contrib.auth import login from django.contrib.auth.models import User -from django.contrib.sessions.backends.db import SessionStore -from django.http import HttpRequest REQUEST_ATTR_NAME = getattr(settings, 'LOCAL_REQUEST_ATTR_NAME', '_current_request') @@ -19,43 +16,10 @@ def _set_current_request(request=None): setattr(_thread_locals, REQUEST_ATTR_NAME, request) -def get_current_request() -> Optional[HttpRequest]: +def get_current_request(): return getattr(_thread_locals, REQUEST_ATTR_NAME, None) -def get_current_user() -> Optional[User]: - request = get_current_request() - if request is None: - return None - return request.user - - -def get_current_session() -> Optional[SessionStore]: - request = get_current_request() - if request is None: - return None - return request.session - - -def get_current_ip() -> Optional[str]: - request = get_current_request() - - if request is None: - return None - elif 'HTTP_X_REAL_IP' in request.META: - return request.META.get('HTTP_X_REAL_IP') - elif 'HTTP_X_FORWARDED_FOR' in request.META: - return request.META.get('HTTP_X_FORWARDED_FOR').split(', ')[0] - return request.META.get('REMOTE_ADDR') - - -def get_current_authenticated_user(): - current_user = get_current_user() - if not current_user or not current_user.is_authenticated: - return None - return current_user - - class SessionMiddleware(object): """ This middleware get the current user with his or her IP address on each request. diff --git a/note_kfet/views.py b/note_kfet/views.py index 797de4ef..b65bf761 100644 --- a/note_kfet/views.py +++ b/note_kfet/views.py @@ -19,11 +19,11 @@ class IndexView(LoginRequiredMixin, RedirectView): user = self.request.user # The account note will have the consumption page as default page - if not PermissionBackend.check_perm(user, "auth.view_user", user): + if not PermissionBackend.check_perm(self.request, "auth.view_user", user): return reverse("note:consos") # People that can see the alias BDE are Kfet members - if PermissionBackend.check_perm(user, "alias.view_alias", Alias.objects.get(name="BDE")): + if PermissionBackend.check_perm(self.request, "alias.view_alias", Alias.objects.get(name="BDE")): return reverse("note:transfer") # Non-Kfet members will don't see the transfer page, but their profile page