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
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timezone
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 note.models import NoteUser, Alias, NoteSpecial
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
from .forms import ActivityForm, GuestForm
from .models import Activity, Guest, Entry
from .tables import ActivityTable, GuestTable, EntryTable
class ActivityCreateView(LoginRequiredMixin, CreateView):
class ActivityCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
model = Activity
form_class = ActivityForm
@ -30,13 +32,12 @@ class ActivityCreateView(LoginRequiredMixin, CreateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
class ActivityListView(LoginRequiredMixin, SingleTableView):
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
model = Activity
table_class = ActivityTable
def get_queryset(self):
return super().get_queryset()\
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).reverse()
return super().get_queryset().reverse()
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
@ -50,7 +51,7 @@ class ActivityListView(LoginRequiredMixin, SingleTableView):
return ctx
class ActivityDetailView(LoginRequiredMixin, DetailView):
class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
model = Activity
context_object_name = "activity"
@ -66,7 +67,7 @@ class ActivityDetailView(LoginRequiredMixin, DetailView):
return ctx
class ActivityUpdateView(LoginRequiredMixin, UpdateView):
class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = Activity
form_class = ActivityForm
@ -74,18 +75,20 @@ class ActivityUpdateView(LoginRequiredMixin, UpdateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]})
class ActivityInviteView(LoginRequiredMixin, CreateView):
class ActivityInviteView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
model = Guest
form_class = GuestForm
template_name = "activity/activity_invite.html"
def get_form(self, form_class=None):
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
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)
def get_success_url(self, **kwargs):
@ -98,7 +101,8 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **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
matched = []

View File

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

View File

@ -1,13 +1,13 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# 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, \
TemplateCategory, TransactionTemplate, RecurrentTransaction, SpecialTransaction
__all__ = [
# Notes
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser', 'NoteActivity',
# Transactions
'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate',
'RecurrentTransaction', 'SpecialTransaction',

View File

@ -9,6 +9,7 @@ from django_tables2 import SingleTableView
from django.urls import reverse_lazy
from note_kfet.inputs import AmountInput
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin
from .forms import TransactionTemplateForm
from .models import Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial
@ -16,7 +17,7 @@ from .models.transactions import SpecialTransaction
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`.
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
# Transaction history table
table_class = HistoryTable
table_pagination = {"per_page": 50}
def get_queryset(self):
return Transaction.objects.filter(PermissionBackend.filter_queryset(
self.request.user, Transaction, "view")
).order_by("-id").all()[:50]
def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).order_by("-id").all()[:50]
def get_context_data(self, **kwargs):
"""
@ -42,12 +40,14 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView):
context['amount_widget'] = AmountInput(attrs={"id": "amount"})
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.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
class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
class TransactionTemplateCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
"""
Create TransactionTemplate
"""
@ -56,7 +56,7 @@ class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
success_url = reverse_lazy('note:template_list')
class TransactionTemplateListView(LoginRequiredMixin, SingleTableView):
class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
List TransactionsTemplates
"""
@ -64,7 +64,7 @@ class TransactionTemplateListView(LoginRequiredMixin, SingleTableView):
table_class = ButtonTable
class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
"""
model = TransactionTemplate
@ -72,21 +72,19 @@ class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
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.
(Most of the magic happens in the dark world of Javascript see consos.js)
"""
model = Transaction
template_name = "note/conso_form.html"
# Transaction history table
table_class = HistoryTable
table_pagination = {"per_page": 50}
def get_queryset(self):
return Transaction.objects.filter(
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
).order_by("-id").all()[:50]
def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).order_by("-id").all()[:50]
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.contenttypes.models import ContentType
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 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
type=type,
).all():
if not isinstance(model, permission.model.__class__):
if not isinstance(model, permission.model.__class__) or not permission.club:
continue
club = Club.objects.get(pk=permission.club)
@ -49,6 +49,7 @@ class PermissionBackend(ModelBackend):
NoteUser=NoteUser,
NoteClub=NoteClub,
NoteSpecial=NoteSpecial,
NoteActivity=NoteActivity,
F=F,
Q=Q
)

View File

@ -176,7 +176,7 @@
"note",
"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",
"mask": 1,
"field": "",
@ -386,7 +386,7 @@
"note",
"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",
"mask": 2,
"field": "",
@ -783,6 +783,111 @@
"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",
"pk": 1,
@ -810,7 +915,6 @@
3,
4,
5,
6,
7,
8,
9,
@ -827,7 +931,12 @@
35,
36,
39,
40
40,
6,
52,
53,
51,
50
]
}
},
@ -838,9 +947,9 @@
"role": 8,
"permissions": [
19,
20,
21,
22
22,
20
]
}
},
@ -880,5 +989,18 @@
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:
self.update_query()
# Don't increase indexes
# Don't increase indexes, if the primary key is an AutoField
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
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
Model.delete(obj)
# If the primary key was specified, we restore it
obj.pk = oldpk
return ret
if permission_type == self.type:
if self.field and field_name != self.field:
return False
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:
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.utils.translation import gettext_lazy as _
from note_kfet.inputs import DatePickerInput, AmountInput
from permission.backends import PermissionBackend
from .models import Invoice, Product, Remittance, SpecialTransactionProxy
@ -131,7 +132,8 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
# Add submit button
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):
"""

View File

@ -19,13 +19,15 @@ from django.views.generic.base import View, TemplateView
from django_tables2 import SingleTableView
from note.models import SpecialTransaction, NoteSpecial
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 .models import Invoice, Product, Remittance, SpecialTransactionProxy
from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable
class InvoiceCreateView(LoginRequiredMixin, CreateView):
class InvoiceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
"""
Create Invoice
"""
@ -67,7 +69,7 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView):
return reverse_lazy('treasury:invoice_list')
class InvoiceListView(LoginRequiredMixin, SingleTableView):
class InvoiceListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
List existing Invoices
"""
@ -75,7 +77,7 @@ class InvoiceListView(LoginRequiredMixin, SingleTableView):
table_class = InvoiceTable
class InvoiceUpdateView(LoginRequiredMixin, UpdateView):
class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Create Invoice
"""
@ -130,7 +132,7 @@ class InvoiceRenderView(LoginRequiredMixin, View):
def get(self, request, **kwargs):
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()
# Informations of the BDE. Should be updated when the school will move.
@ -188,7 +190,7 @@ class InvoiceRenderView(LoginRequiredMixin, View):
return response
class RemittanceCreateView(LoginRequiredMixin, CreateView):
class RemittanceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
"""
Create Remittance
"""
@ -201,7 +203,9 @@ class RemittanceCreateView(LoginRequiredMixin, CreateView):
def get_context_data(self, **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())
return ctx
@ -216,22 +220,28 @@ class RemittanceListView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["opened_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=False).all())
ctx["closed_remittances"] = RemittanceTable(data=Remittance.objects.filter(closed=True).reverse().all())
ctx["opened_remittances"] = RemittanceTable(
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(
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', ))
ctx["special_transactions_with_remittance"] = SpecialTransactionTable(
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', ))
return ctx
class RemittanceUpdateView(LoginRequiredMixin, UpdateView):
class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Update Remittance
"""
@ -244,8 +254,10 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["table"] = RemittanceTable(data=Remittance.objects.all())
data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).all()
ctx["table"] = RemittanceTable(data=Remittance.objects.filter(
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(
data=data,
exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', ))
@ -253,7 +265,7 @@ class RemittanceUpdateView(LoginRequiredMixin, UpdateView):
return ctx
class LinkTransactionToRemittanceView(LoginRequiredMixin, UpdateView):
class LinkTransactionToRemittanceView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Attach a special transaction to a remittance
"""

View File

@ -3,6 +3,7 @@
{% load i18n %}
{% load render_table from django_tables2 %}
{% load pretty_money %}
{% load perms %}
{% block profile_info %}
{% include "member/club_info.html" %}
@ -25,9 +26,11 @@
<dd class="col-xl-6">{{ note.balance|pretty_money }}</dd>
</dl>
{% if "change_"|has_perm:note %}
<div class="card-footer text-center">
<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>

View File

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