Improved permissions, 404 and 403 errors will be more frequent (when we type an invalid URL)

This commit is contained in:
Yohann D'ANELLO 2020-03-31 04:16:30 +02:00
parent c384ee02eb
commit 1aae18e6a6
13 changed files with 272 additions and 105 deletions

View File

@ -1,5 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timezone from datetime import datetime, timezone
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
@ -11,13 +12,14 @@ from django.utils.translation import gettext_lazy as _
from django_tables2.views import SingleTableView from django_tables2.views import SingleTableView
from note.models import NoteUser, Alias, NoteSpecial from note.models import NoteUser, Alias, NoteSpecial
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
from .forms import ActivityForm, GuestForm from .forms import ActivityForm, GuestForm
from .models import Activity, Guest, Entry from .models import Activity, Guest, Entry
from .tables import ActivityTable, GuestTable, EntryTable from .tables import ActivityTable, GuestTable, EntryTable
class ActivityCreateView(LoginRequiredMixin, CreateView): class ActivityCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
model = Activity model = Activity
form_class = ActivityForm form_class = ActivityForm
@ -30,13 +32,12 @@ class ActivityCreateView(LoginRequiredMixin, CreateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk}) return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
class ActivityListView(LoginRequiredMixin, SingleTableView): class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
model = Activity model = Activity
table_class = ActivityTable table_class = ActivityTable
def get_queryset(self): def get_queryset(self):
return super().get_queryset()\ return super().get_queryset().reverse()
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).reverse()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
@ -50,7 +51,7 @@ class ActivityListView(LoginRequiredMixin, SingleTableView):
return ctx return ctx
class ActivityDetailView(LoginRequiredMixin, DetailView): class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
model = Activity model = Activity
context_object_name = "activity" context_object_name = "activity"
@ -66,7 +67,7 @@ class ActivityDetailView(LoginRequiredMixin, DetailView):
return ctx return ctx
class ActivityUpdateView(LoginRequiredMixin, UpdateView): class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = Activity model = Activity
form_class = ActivityForm form_class = ActivityForm
@ -74,18 +75,20 @@ class ActivityUpdateView(LoginRequiredMixin, UpdateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]}) return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]})
class ActivityInviteView(LoginRequiredMixin, CreateView): class ActivityInviteView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
model = Guest model = Guest
form_class = GuestForm form_class = GuestForm
template_name = "activity/activity_invite.html" template_name = "activity/activity_invite.html"
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
form.activity = Activity.objects.get(pk=self.kwargs["pk"]) form.activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
.get(pk=self.kwargs["pk"])
return form return form
def form_valid(self, form): def form_valid(self, form):
form.instance.activity = Activity.objects.get(pk=self.kwargs["pk"]) form.instance.activity = Activity.objects\
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"])
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
@ -98,7 +101,8 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
activity = Activity.objects.get(pk=self.kwargs["pk"]) activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
.get(pk=self.kwargs["pk"])
ctx["activity"] = activity ctx["activity"] = activity
matched = [] matched = []

View File

@ -49,7 +49,13 @@ class ClubForm(forms.ModelForm):
model = Club model = Club
fields = '__all__' fields = '__all__'
widgets = { widgets = {
"membership_fee": AmountInput() "membership_fee": AmountInput(),
"parent_club": Autocomplete(
Club,
attrs={
'api_url': '/api/members/club/',
}
),
} }

View File

@ -22,6 +22,7 @@ from note.models.notes import NoteActivity
from note.models.transactions import Transaction from note.models.transactions import Transaction
from note.tables import HistoryTable, AliasTable, NoteActivityTable from note.tables import HistoryTable, AliasTable, NoteActivityTable
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
from .filters import UserFilter, UserFilterFormHelper from .filters import UserFilter, UserFilterFormHelper
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper, \ from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper, \
@ -64,7 +65,7 @@ class UserCreateView(CreateView):
return super().form_valid(form) return super().form_valid(form)
class UserUpdateView(LoginRequiredMixin, UpdateView): class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = User model = User
fields = ['first_name', 'last_name', 'username', 'email'] fields = ['first_name', 'last_name', 'username', 'email']
template_name = 'member/profile_update.html' template_name = 'member/profile_update.html'
@ -98,7 +99,8 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
if form.is_valid() and profile_form.is_valid(): if form.is_valid() and profile_form.is_valid():
new_username = form.data['username'] new_username = form.data['username']
alias = Alias.objects.filter(name=new_username) alias = Alias.objects.filter(name=new_username)
# Si le nouveau pseudo n'est pas un de nos alias, on supprime éventuellement un alias similaire pour le remplacer # Si le nouveau pseudo n'est pas un de nos alias,
# on supprime éventuellement un alias similaire pour le remplacer
if not alias.exists(): if not alias.exists():
similar = Alias.objects.filter( similar = Alias.objects.filter(
normalized_name=Alias.normalize(new_username)) normalized_name=Alias.normalize(new_username))
@ -120,7 +122,7 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
return reverse_lazy('member:user_detail', args=(self.object.id,)) return reverse_lazy('member:user_detail', args=(self.object.id,))
class UserDetailView(LoginRequiredMixin, DetailView): class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
""" """
Affiche les informations sur un utilisateur, sa note, ses clubs... Affiche les informations sur un utilisateur, sa note, ses clubs...
""" """
@ -128,9 +130,6 @@ class UserDetailView(LoginRequiredMixin, DetailView):
context_object_name = "user_object" context_object_name = "user_object"
template_name = "member/profile_detail.html" template_name = "member/profile_detail.html"
def get_queryset(self, **kwargs):
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, User, "view"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
user = context['user_object'] user = context['user_object']
@ -138,13 +137,13 @@ class UserDetailView(LoginRequiredMixin, DetailView):
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note)).order_by("-id")\ Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note)).order_by("-id")\
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")) .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))
context['history_list'] = HistoryTable(history_list) context['history_list'] = HistoryTable(history_list)
club_list = \ club_list = Membership.objects.all().filter(user=user)\
Membership.objects.all().filter(user=user).only("club") .filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")).only("club")
context['club_list'] = ClubTable(club_list) context['club_list'] = ClubTable(club_list)
return context return context
class UserListView(LoginRequiredMixin, SingleTableView): class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
Affiche la liste des utilisateurs, avec une fonction de recherche statique Affiche la liste des utilisateurs, avec une fonction de recherche statique
""" """
@ -155,7 +154,7 @@ class UserListView(LoginRequiredMixin, SingleTableView):
formhelper_class = UserFilterFormHelper formhelper_class = UserFilterFormHelper
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
qs = super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, User, "view")) qs = super().get_queryset()
self.filter = self.filter_class(self.request.GET, queryset=qs) self.filter = self.filter_class(self.request.GET, queryset=qs)
self.filter.form.helper = self.formhelper_class() self.filter.form.helper = self.formhelper_class()
return self.filter.qs return self.filter.qs
@ -166,7 +165,7 @@ class UserListView(LoginRequiredMixin, SingleTableView):
return context return context
class ProfileAliasView(LoginRequiredMixin, DetailView): class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
model = User model = User
template_name = 'member/profile_alias.html' template_name = 'member/profile_alias.html'
context_object_name = 'user_object' context_object_name = 'user_object'
@ -178,7 +177,7 @@ class ProfileAliasView(LoginRequiredMixin, DetailView):
return context return context
class PictureUpdateView(LoginRequiredMixin, FormMixin, DetailView): class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, DetailView):
form_class = ImageForm form_class = ImageForm
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -239,8 +238,7 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
template_name = "member/manage_auth_tokens.html" template_name = "member/manage_auth_tokens.html"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'regenerate' in request.GET and Token.objects.filter( if 'regenerate' in request.GET and Token.objects.filter(user=request.user).exists():
user=request.user).exists():
Token.objects.get(user=self.request.user).delete() Token.objects.get(user=self.request.user).delete()
return redirect(reverse_lazy('member:auth_token') + "?show", return redirect(reverse_lazy('member:auth_token') + "?show",
permanent=True) permanent=True)
@ -249,8 +247,7 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['token'] = Token.objects.get_or_create( context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
user=self.request.user)[0]
return context return context
@ -259,7 +256,7 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
# ******************************* # # ******************************* #
class ClubCreateView(LoginRequiredMixin, CreateView): class ClubCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
""" """
Create Club Create Club
""" """
@ -271,38 +268,32 @@ class ClubCreateView(LoginRequiredMixin, CreateView):
return super().form_valid(form) return super().form_valid(form)
class ClubListView(LoginRequiredMixin, SingleTableView): class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
List existing Clubs List existing Clubs
""" """
model = Club model = Club
table_class = ClubTable table_class = ClubTable
def get_queryset(self, **kwargs):
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))
class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
class ClubDetailView(LoginRequiredMixin, DetailView):
model = Club model = Club
context_object_name = "club" context_object_name = "club"
def get_queryset(self, **kwargs):
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
club = context["club"] club = context["club"]
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\ club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")).order_by('-id') .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")).order_by('-id')
context['history_list'] = HistoryTable(club_transactions) context['history_list'] = HistoryTable(club_transactions)
club_member = \ club_member = Membership.objects.filter(club=club)\
Membership.objects.all().filter(club=club) .filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")).all()
# TODO: consider only valid Membership # TODO: consider only valid Membership
context['member_list'] = club_member context['member_list'] = club_member
return context return context
class ClubAliasView(LoginRequiredMixin, DetailView): class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
model = Club model = Club
template_name = 'member/club_alias.html' template_name = 'member/club_alias.html'
context_object_name = 'club' context_object_name = 'club'
@ -314,7 +305,7 @@ class ClubAliasView(LoginRequiredMixin, DetailView):
return context return context
class ClubUpdateView(LoginRequiredMixin, UpdateView): class ClubUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = Club model = Club
context_object_name = "club" context_object_name = "club"
form_class = ClubForm form_class = ClubForm
@ -333,7 +324,7 @@ class ClubPictureUpdateView(PictureUpdateView):
return reverse_lazy('member:club_detail', kwargs={'pk': self.object.id}) return reverse_lazy('member:club_detail', kwargs={'pk': self.object.id})
class ClubAddMemberView(LoginRequiredMixin, CreateView): class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
model = Membership model = Membership
form_class = MembershipForm form_class = MembershipForm
template_name = 'member/add_members.html' template_name = 'member/add_members.html'
@ -344,7 +335,8 @@ class ClubAddMemberView(LoginRequiredMixin, CreateView):
"change")) "change"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
club = Club.objects.get(pk=self.kwargs["pk"]) club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
.get(pk=self.kwargs["pk"])
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['formset'] = MemberFormSet() context['formset'] = MemberFormSet()
context['helper'] = FormSetHelper() context['helper'] = FormSetHelper()
@ -367,36 +359,40 @@ class ClubAddMemberView(LoginRequiredMixin, CreateView):
return super().form_valid(formset) return super().form_valid(formset)
class ClubLinkedNotesView(LoginRequiredMixin, SingleTableView): class ClubLinkedNotesView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
model = NoteActivity model = NoteActivity
table_class = NoteActivityTable table_class = NoteActivityTable
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(club=self.get_object())\ return super().get_queryset().filter(club=self.get_object())
.filter(PermissionBackend.filter_queryset(self.request.user, NoteActivity, "view"))
def get_object(self): def get_object(self):
if hasattr(self, 'object'): if hasattr(self, 'object'):
return self.object return self.object
self.object = Club.objects.get(pk=int(self.kwargs["pk"])) self.object = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
.get(pk=int(self.kwargs["pk"]))
return self.object return self.object
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx["object"] = ctx["club"] = self.get_object() club = ctx["object"] = ctx["club"] = self.get_object()
empty_note = NoteActivity(note_name="", club=club, controller=self.request.user)
ctx["can_create"] = PermissionBackend().has_perm(self.request.user, "note.add_noteactivity", empty_note)
return ctx return ctx
class ClubLinkedNoteCreateView(LoginRequiredMixin, CreateView): class ClubLinkedNoteCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
model = NoteActivity model = NoteActivity
form_class = NoteActivityForm form_class = NoteActivityForm
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
club = Club.objects.get(pk=self.kwargs["club_pk"]) club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
.get(pk=self.kwargs["club_pk"])
ctx["object"] = ctx["club"] = club ctx["object"] = ctx["club"] = club
ctx["form"].fields["club"].initial = club ctx["form"].fields["club"].initial = club
@ -408,14 +404,15 @@ class ClubLinkedNoteCreateView(LoginRequiredMixin, CreateView):
kwargs={"club_pk": self.object.club.pk, "pk": self.object.pk}) kwargs={"club_pk": self.object.club.pk, "pk": self.object.pk})
class ClubLinkedNoteUpdateView(LoginRequiredMixin, UpdateView): class ClubLinkedNoteUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = NoteActivity model = NoteActivity
form_class = NoteActivityForm form_class = NoteActivityForm
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx["club"] = Club.objects.get(pk=self.kwargs["club_pk"]) ctx["club"] = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
.get(pk=self.kwargs["club_pk"])
return ctx return ctx
@ -424,15 +421,15 @@ class ClubLinkedNoteUpdateView(LoginRequiredMixin, UpdateView):
kwargs={"club_pk": self.object.club.pk, "pk": self.object.pk}) kwargs={"club_pk": self.object.club.pk, "pk": self.object.pk})
class ClubLinkedNoteDetailView(LoginRequiredMixin, DetailView): class ClubLinkedNoteDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
model = NoteActivity model = NoteActivity
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
note = NoteActivity.objects.get(pk=self.kwargs["pk"]) note = self.get_queryset().filter(pk=self.kwargs["pk"]).get()
transactions = Transaction.objects.all().filter(Q(source=note) | Q(destination=note))\ transactions = Transaction.objects.filter(Q(source=note) | Q(destination=note))\
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")).order_by("-id") .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")).order_by("-id")
ctx['history_list'] = HistoryTable(transactions) ctx['history_list'] = HistoryTable(transactions)
ctx["note"] = note ctx["note"] = note

View File

@ -1,13 +1,13 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, NoteActivity
from .transactions import MembershipTransaction, Transaction, \ from .transactions import MembershipTransaction, Transaction, \
TemplateCategory, TransactionTemplate, RecurrentTransaction, SpecialTransaction TemplateCategory, TransactionTemplate, RecurrentTransaction, SpecialTransaction
__all__ = [ __all__ = [
# Notes # Notes
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser', 'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser', 'NoteActivity',
# Transactions # Transactions
'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate', 'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate',
'RecurrentTransaction', 'SpecialTransaction', 'RecurrentTransaction', 'SpecialTransaction',

View File

@ -9,6 +9,7 @@ from django_tables2 import SingleTableView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from note_kfet.inputs import AmountInput from note_kfet.inputs import AmountInput
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
from .forms import TransactionTemplateForm from .forms import TransactionTemplateForm
from .models import Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial from .models import Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial
@ -16,7 +17,7 @@ from .models.transactions import SpecialTransaction
from .tables import HistoryTable, ButtonTable from .tables import HistoryTable, ButtonTable
class TransactionCreateView(LoginRequiredMixin, SingleTableView): class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
View for the creation of Transaction between two note which are not :models:`transactions.RecurrentTransaction`. View for the creation of Transaction between two note which are not :models:`transactions.RecurrentTransaction`.
e.g. for donation/transfer between people and clubs or for credit/debit with :models:`note.NoteSpecial` e.g. for donation/transfer between people and clubs or for credit/debit with :models:`note.NoteSpecial`
@ -26,12 +27,9 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView):
model = Transaction model = Transaction
# Transaction history table # Transaction history table
table_class = HistoryTable table_class = HistoryTable
table_pagination = {"per_page": 50}
def get_queryset(self): def get_queryset(self, **kwargs):
return Transaction.objects.filter(PermissionBackend.filter_queryset( return super().get_queryset(**kwargs).order_by("-id").all()[:50]
self.request.user, Transaction, "view")
).order_by("-id").all()[:50]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" """
@ -42,12 +40,14 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView):
context['amount_widget'] = AmountInput(attrs={"id": "amount"}) context['amount_widget'] = AmountInput(attrs={"id": "amount"})
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk
context['special_types'] = NoteSpecial.objects.order_by("special_type").all() context['special_types'] = NoteSpecial.objects\
.filter(PermissionBackend.filter_queryset(self.request.user, NoteSpecial, "view"))\
.order_by("special_type").all()
return context return context
class TransactionTemplateCreateView(LoginRequiredMixin, CreateView): class TransactionTemplateCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
""" """
Create TransactionTemplate Create TransactionTemplate
""" """
@ -56,7 +56,7 @@ class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
success_url = reverse_lazy('note:template_list') success_url = reverse_lazy('note:template_list')
class TransactionTemplateListView(LoginRequiredMixin, SingleTableView): class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
List TransactionsTemplates List TransactionsTemplates
""" """
@ -64,7 +64,7 @@ class TransactionTemplateListView(LoginRequiredMixin, SingleTableView):
table_class = ButtonTable table_class = ButtonTable
class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView): class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
""" """
""" """
model = TransactionTemplate model = TransactionTemplate
@ -72,21 +72,19 @@ class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
success_url = reverse_lazy('note:template_list') success_url = reverse_lazy('note:template_list')
class ConsoView(LoginRequiredMixin, SingleTableView): class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
The Magic View that make people pay their beer and burgers. The Magic View that make people pay their beer and burgers.
(Most of the magic happens in the dark world of Javascript see consos.js) (Most of the magic happens in the dark world of Javascript see consos.js)
""" """
model = Transaction
template_name = "note/conso_form.html" template_name = "note/conso_form.html"
# Transaction history table # Transaction history table
table_class = HistoryTable table_class = HistoryTable
table_pagination = {"per_page": 50}
def get_queryset(self): def get_queryset(self, **kwargs):
return Transaction.objects.filter( return super().get_queryset(**kwargs).order_by("-id").all()[:50]
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
).order_by("-id").all()[:50]
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" """

View File

@ -5,7 +5,7 @@ from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.models import User, AnonymousUser
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import Q, F from django.db.models import Q, F
from note.models import Note, NoteUser, NoteClub, NoteSpecial from note.models import Note, NoteUser, NoteClub, NoteSpecial, NoteActivity
from note_kfet.middlewares import get_current_session from note_kfet.middlewares import get_current_session
from member.models import Membership, Club from member.models import Membership, Club
@ -35,7 +35,7 @@ class PermissionBackend(ModelBackend):
model__app_label=model.app_label, # For polymorphic models, we don't filter on model type model__app_label=model.app_label, # For polymorphic models, we don't filter on model type
type=type, type=type,
).all(): ).all():
if not isinstance(model, permission.model.__class__): if not isinstance(model, permission.model.__class__) or not permission.club:
continue continue
club = Club.objects.get(pk=permission.club) club = Club.objects.get(pk=permission.club)
@ -49,6 +49,7 @@ class PermissionBackend(ModelBackend):
NoteUser=NoteUser, NoteUser=NoteUser,
NoteClub=NoteClub, NoteClub=NoteClub,
NoteSpecial=NoteSpecial, NoteSpecial=NoteSpecial,
NoteActivity=NoteActivity,
F=F, F=F,
Q=Q Q=Q
) )

View File

@ -176,7 +176,7 @@
"note", "note",
"alias" "alias"
], ],
"query": "[\"OR\", {\"note__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club__name\": \"Kfet\"}], [\"all\"]]}, {\"note__in\": [\"NoteClub\", \"objects\", [\"all\"]]}]", "query": "[\"OR\", {\"note__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club__name\": \"Kfet\"}], [\"all\"]]}, {\"note__in\": [\"NoteClub\", \"objects\", [\"all\"]]}, {\"note__in\": [\"NoteActivity\", \"objects\", [\"all\"]]}]",
"type": "view", "type": "view",
"mask": 1, "mask": 1,
"field": "", "field": "",
@ -386,7 +386,7 @@
"note", "note",
"transaction" "transaction"
], ],
"query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}]", "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}, {\"valid\": false}]]",
"type": "add", "type": "add",
"mask": 2, "mask": 2,
"field": "", "field": "",
@ -783,6 +783,111 @@
"description": "Validate invitation transactions" "description": "Validate invitation transactions"
} }
}, },
{
"model": "permission.permission",
"pk": 47,
"fields": {
"model": [
"member",
"club"
],
"query": "{\"pk\": [\"club\", \"pk\"]}",
"type": "change",
"mask": 1,
"field": "",
"description": "Update club"
}
},
{
"model": "permission.permission",
"pk": 48,
"fields": {
"model": [
"note",
"noteactivity"
],
"query": "{\"club\": [\"club\"]}",
"type": "change",
"mask": 1,
"field": "",
"description": "Manage notes that are linked to a club"
}
},
{
"model": "permission.permission",
"pk": 49,
"fields": {
"model": [
"note",
"noteactivity"
],
"query": "{\"club\": [\"club\"]}",
"type": "view",
"mask": 1,
"field": "",
"description": "View notes that are linked to a club"
}
},
{
"model": "permission.permission",
"pk": 50,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"AND\", [\"OR\", {\"source__noteactivity__controller\": [\"user\"]}, {\"destination__noteactivity__controller\": [\"user\"]}], [\"OR\", {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}, {\"valid\": false}]]",
"type": "add",
"mask": 2,
"field": "",
"description": "Add transactions linked to a noteactivity"
}
},
{
"model": "permission.permission",
"pk": 51,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"AND\", [\"OR\", {\"source__noteactivity__controller\": [\"user\"]}, {\"destination__noteactivity__controller\": [\"user\"]}]]",
"type": "view",
"mask": 1,
"field": "",
"description": "View transactions linked to a noteactivity"
}
},
{
"model": "permission.permission",
"pk": 52,
"fields": {
"model": [
"note",
"note"
],
"query": "{\"noteactivity__controller\": [\"user\"]}",
"type": "view",
"mask": 1,
"field": "",
"description": "View note activity"
}
},
{
"model": "permission.permission",
"pk": 53,
"fields": {
"model": [
"note",
"noteactivity"
],
"query": "{\"controller\": [\"user\"]}",
"type": "view",
"mask": 1,
"field": "",
"description": "View note activity"
}
},
{ {
"model": "permission.rolepermissions", "model": "permission.rolepermissions",
"pk": 1, "pk": 1,
@ -810,7 +915,6 @@
3, 3,
4, 4,
5, 5,
6,
7, 7,
8, 8,
9, 9,
@ -827,7 +931,12 @@
35, 35,
36, 36,
39, 39,
40 40,
6,
52,
53,
51,
50
] ]
} }
}, },
@ -838,9 +947,9 @@
"role": 8, "role": 8,
"permissions": [ "permissions": [
19, 19,
20,
21, 21,
22 22,
20
] ]
} }
}, },
@ -880,5 +989,18 @@
46 46
] ]
} }
},
{
"model": "permission.rolepermissions",
"pk": 6,
"fields": {
"role": 7,
"permissions": [
22,
47,
48,
49
]
}
} }
] ]

View File

@ -38,20 +38,29 @@ class InstancedPermission:
if permission_type == self.type: if permission_type == self.type:
self.update_query() self.update_query()
# Don't increase indexes # Don't increase indexes, if the primary key is an AutoField
obj.pk = 0 if not hasattr(obj, "pk") or not obj.pk:
obj.pk = 0
oldpk = None
else:
oldpk = obj.pk
# Ensure previous models are deleted
self.model.model_class().objects.filter(pk=obj.pk).delete()
# Force insertion, no data verification, no trigger # Force insertion, no data verification, no trigger
Model.save(obj, force_insert=True) Model.save(obj, force_insert=True)
ret = obj in self.model.model_class().objects.filter(self.query).all() ret = self.model.model_class().objects.filter(self.query & Q(pk=obj.pk)).exists()
# Delete testing object # Delete testing object
Model.delete(obj) Model.delete(obj)
# If the primary key was specified, we restore it
obj.pk = oldpk
return ret return ret
if permission_type == self.type: if permission_type == self.type:
if self.field and field_name != self.field: if self.field and field_name != self.field:
return False return False
self.update_query() self.update_query()
return obj in self.model.model_class().objects.filter(self.query).all() return self.model.model_class().objects.filter(self.query & Q(pk=obj.pk)).exists()
else: else:
return False return False

11
apps/permission/views.py Normal file
View File

@ -0,0 +1,11 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from permission.backends import PermissionBackend
class ProtectQuerysetMixin:
def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs)
return qs.filter(PermissionBackend.filter_queryset(self.request.user, qs.model, "view"))

View File

@ -8,6 +8,7 @@ from crispy_forms.layout import Submit
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import DatePickerInput, AmountInput from note_kfet.inputs import DatePickerInput, AmountInput
from permission.backends import PermissionBackend
from .models import Invoice, Product, Remittance, SpecialTransactionProxy from .models import Invoice, Product, Remittance, SpecialTransactionProxy
@ -131,7 +132,8 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
# Add submit button # Add submit button
self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'})) self.helper.add_input(Submit('submit', _("Submit"), attr={'class': 'btn btn-block btn-primary'}))
self.fields["remittance"].queryset = Remittance.objects.filter(closed=False) self.fields["remittance"].queryset = Remittance.objects.filter(closed=False)\
.filter(PermissionBackend.filter_queryset(self.request.user, Remittance, "view"))
def clean_last_name(self): def clean_last_name(self):
""" """

View File

@ -19,13 +19,15 @@ from django.views.generic.base import View, TemplateView
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from note.models import SpecialTransaction, NoteSpecial from note.models import SpecialTransaction, NoteSpecial
from note_kfet.settings.base import BASE_DIR from note_kfet.settings.base import BASE_DIR
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm
from .models import Invoice, Product, Remittance, SpecialTransactionProxy from .models import Invoice, Product, Remittance, SpecialTransactionProxy
from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable
class InvoiceCreateView(LoginRequiredMixin, CreateView): class InvoiceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
""" """
Create Invoice Create Invoice
""" """
@ -67,7 +69,7 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView):
return reverse_lazy('treasury:invoice_list') return reverse_lazy('treasury:invoice_list')
class InvoiceListView(LoginRequiredMixin, SingleTableView): class InvoiceListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
List existing Invoices List existing Invoices
""" """
@ -75,7 +77,7 @@ class InvoiceListView(LoginRequiredMixin, SingleTableView):
table_class = InvoiceTable table_class = InvoiceTable
class InvoiceUpdateView(LoginRequiredMixin, UpdateView): class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
""" """
Create Invoice Create Invoice
""" """
@ -130,7 +132,7 @@ class InvoiceRenderView(LoginRequiredMixin, View):
def get(self, request, **kwargs): def get(self, request, **kwargs):
pk = kwargs["pk"] pk = kwargs["pk"]
invoice = Invoice.objects.get(pk=pk) invoice = Invoice.objects.filter(PermissionBackend.filter_queryset(request.user, Invoice, "view")).get(pk=pk)
products = Product.objects.filter(invoice=invoice).all() products = Product.objects.filter(invoice=invoice).all()
# Informations of the BDE. Should be updated when the school will move. # Informations of the BDE. Should be updated when the school will move.
@ -188,7 +190,7 @@ class InvoiceRenderView(LoginRequiredMixin, View):
return response return response
class RemittanceCreateView(LoginRequiredMixin, CreateView): class RemittanceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
""" """
Create Remittance Create Remittance
""" """
@ -201,7 +203,9 @@ class RemittanceCreateView(LoginRequiredMixin, CreateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx["table"] = RemittanceTable(data=Remittance.objects.all()) ctx["table"] = RemittanceTable(data=Remittance.objects
.filter(PermissionBackend.filter_queryset(self.request.user, Remittance, "view"))
.all())
ctx["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none()) ctx["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none())
return ctx return ctx
@ -216,22 +220,28 @@ class RemittanceListView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx["opened_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=False).all()) ctx["opened_remittances"] = RemittanceTable(
ctx["closed_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=True).reverse().all()) data=Remittance.objects.filter(closed=False).filter(
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all())
ctx["closed_remittances"] = RemittanceTable(
data=Remittance.objects.filter(closed=True).filter(
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).reverse().all())
ctx["special_transactions_no_remittance"] = SpecialTransactionTable( ctx["special_transactions_no_remittance"] = SpecialTransactionTable(
data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
specialtransactionproxy__remittance=None).all(), specialtransactionproxy__remittance=None).filter(
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(),
exclude=('remittance_remove', )) exclude=('remittance_remove', ))
ctx["special_transactions_with_remittance"] = SpecialTransactionTable( ctx["special_transactions_with_remittance"] = SpecialTransactionTable(
data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
specialtransactionproxy__remittance__closed=False).all(), specialtransactionproxy__remittance__closed=False).filter(
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(),
exclude=('remittance_add', )) exclude=('remittance_add', ))
return ctx return ctx
class RemittanceUpdateView(LoginRequiredMixin, UpdateView): class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
""" """
Update Remittance Update Remittance
""" """
@ -244,8 +254,10 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx["table"] = RemittanceTable(data=Remittance.objects.all()) ctx["table"] = RemittanceTable(data=Remittance.objects.filter(
data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).all() PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all())
data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter(
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all()
ctx["special_transactions"] = SpecialTransactionTable( ctx["special_transactions"] = SpecialTransactionTable(
data=data, data=data,
exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', )) exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', ))
@ -253,7 +265,7 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView):
return ctx return ctx
class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView): class LinkTransactionToRemittanceView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
""" """
Attach a special transaction to a remittance Attach a special transaction to a remittance
""" """

View File

@ -3,6 +3,7 @@
{% load i18n %} {% load i18n %}
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% load pretty_money %} {% load pretty_money %}
{% load perms %}
{% block profile_info %} {% block profile_info %}
{% include "member/club_info.html" %} {% include "member/club_info.html" %}
@ -25,9 +26,11 @@
<dd class="col-xl-6">{{ note.balance|pretty_money }}</dd> <dd class="col-xl-6">{{ note.balance|pretty_money }}</dd>
</dl> </dl>
<div class="card-footer text-center"> {% if "change_"|has_perm:note %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_linked_note_update' club_pk=club.pk pk=note.pk %}"> {% trans "Edit" %}</a> <div class="card-footer text-center">
</div> <a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_linked_note_update' club_pk=club.pk pk=note.pk %}"> {% trans "Edit" %}</a>
</div>
{% endif %}
</div> </div>

View File

@ -19,9 +19,11 @@
</div> </div>
</div> </div>
<a href="{% url 'member:club_linked_note_create' club_pk=club.pk %}"> {% if can_create %}
<button class="btn btn-primary btn-block">{% trans "Add new note" %}</button> <a href="{% url 'member:club_linked_note_create' club_pk=club.pk %}">
</a> <button class="btn btn-primary btn-block">{% trans "Add new note" %}</button>
</a>
{% endif %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}