mirror of
https://gitlab.crans.org/bde/nk20
synced 2024-12-23 07:52:23 +00:00
Protect views from viewing if the user has no right to view an object
This commit is contained in:
parent
e461d70b14
commit
730d37c620
@ -13,7 +13,7 @@ class ReadProtectedModelViewSet(viewsets.ModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
model = ContentType.objects.get_for_model(self.serializer_class.Meta.model)
|
||||
return super().get_queryset().filter(PermissionBackend().filter_queryset(self.request.user, model, "view"))
|
||||
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, model, "view"))
|
||||
|
||||
|
||||
class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
@ -23,4 +23,4 @@ class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
model = ContentType.objects.get_for_model(self.serializer_class.Meta.model)
|
||||
return super().get_queryset().filter(PermissionBackend().filter_queryset(self.request.user, model, "view"))
|
||||
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, model, "view"))
|
||||
|
@ -2,6 +2,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q, F
|
||||
|
||||
@ -15,7 +16,8 @@ class PermissionBackend(ModelBackend):
|
||||
supports_anonymous_user = False
|
||||
supports_inactive_user = False
|
||||
|
||||
def permissions(self, user):
|
||||
@staticmethod
|
||||
def permissions(user):
|
||||
for membership in Membership.objects.filter(user=user).all():
|
||||
if not membership.valid() or membership.roles is None:
|
||||
continue
|
||||
@ -37,12 +39,13 @@ class PermissionBackend(ModelBackend):
|
||||
)
|
||||
yield permission
|
||||
|
||||
def filter_queryset(self, user, model, type, field=None):
|
||||
@staticmethod
|
||||
def filter_queryset(user, 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 model: The concerned model of the queryset
|
||||
:param type: The type of modification (view, add, change, delete)
|
||||
: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
|
||||
"""
|
||||
@ -51,12 +54,15 @@ class PermissionBackend(ModelBackend):
|
||||
# Superusers have all rights
|
||||
return Q()
|
||||
|
||||
if not isinstance(model, ContentType):
|
||||
model = ContentType.objects.get_for_model(model)
|
||||
|
||||
# Never satisfied
|
||||
query = Q(pk=-1)
|
||||
for perm in self.permissions(user):
|
||||
if field and field != perm.field:
|
||||
for perm in PermissionBackend.permissions(user):
|
||||
if perm.field and field != perm.field:
|
||||
continue
|
||||
if perm.model != model or perm.type != type:
|
||||
if perm.model != model or perm.type != t:
|
||||
continue
|
||||
query = query | perm.query
|
||||
return query
|
||||
|
@ -23,6 +23,7 @@ from note.forms import AliasForm, ImageForm
|
||||
from note.models import Alias, NoteUser
|
||||
from note.models.transactions import Transaction
|
||||
from note.tables import HistoryTable, AliasTable
|
||||
from .backends import PermissionBackend
|
||||
|
||||
from .filters import UserFilter, UserFilterFormHelper
|
||||
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
|
||||
@ -120,6 +121,9 @@ 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']
|
||||
@ -147,7 +151,7 @@ class UserListView(LoginRequiredMixin, SingleTableView):
|
||||
formhelper_class = UserFilterFormHelper
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
qs = super().get_queryset()
|
||||
qs = super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, User, "view"))
|
||||
self.filter = self.filter_class(self.request.GET, queryset=qs)
|
||||
self.filter.form.helper = self.formhelper_class()
|
||||
return self.filter.qs
|
||||
@ -296,7 +300,7 @@ class UserAutocomplete(autocomplete.Select2QuerySetView):
|
||||
if not self.request.user.is_authenticated:
|
||||
return User.objects.none()
|
||||
|
||||
qs = User.objects.all()
|
||||
qs = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view")).all()
|
||||
|
||||
if self.q:
|
||||
qs = qs.filter(username__regex="^" + self.q)
|
||||
@ -327,11 +331,17 @@ class ClubListView(LoginRequiredMixin, SingleTableView):
|
||||
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):
|
||||
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"]
|
||||
@ -350,6 +360,11 @@ class ClubAddMemberView(LoginRequiredMixin, CreateView):
|
||||
form_class = MembershipForm
|
||||
template_name = 'member/add_members.html'
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")
|
||||
| PermissionBackend.filter_queryset(self.request.user, Membership,
|
||||
"change"))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['formset'] = MemberFormSet()
|
||||
|
@ -6,6 +6,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
from member.backends import PermissionBackend
|
||||
from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \
|
||||
NoteUserSerializer, AliasSerializer, \
|
||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
||||
@ -70,7 +71,7 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
||||
Parse query and apply filters.
|
||||
:return: The filtered set of requested notes
|
||||
"""
|
||||
queryset = super().get_queryset()
|
||||
queryset = super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Note, "view"))
|
||||
|
||||
alias = self.request.query_params.get("alias", ".*")
|
||||
queryset = queryset.filter(
|
||||
@ -110,7 +111,7 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
:return: The filtered set of requested aliases
|
||||
"""
|
||||
|
||||
queryset = super().get_queryset()
|
||||
queryset = super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
|
||||
|
||||
alias = self.request.query_params.get("alias", ".*")
|
||||
queryset = queryset.filter(
|
||||
|
@ -129,13 +129,14 @@ class Transaction(PolymorphicModel):
|
||||
models.Index(fields=['destination']),
|
||||
]
|
||||
|
||||
def post_save(self, *args, **kwargs):
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
When saving, also transfer money between two notes
|
||||
"""
|
||||
|
||||
if self.source.pk == self.destination.pk:
|
||||
# When source == destination, no money is transfered
|
||||
super().save(*args, **kwargs)
|
||||
return
|
||||
|
||||
created = self.pk is None
|
||||
|
@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import CreateView, ListView, UpdateView
|
||||
from django_tables2 import SingleTableView
|
||||
|
||||
from member.backends import PermissionBackend
|
||||
from .forms import TransactionTemplateForm
|
||||
from .models import Transaction, TransactionTemplate, Alias, TemplateTransaction, NoteSpecial
|
||||
from .models.transactions import SpecialTransaction
|
||||
@ -18,16 +19,18 @@ from .tables import HistoryTable
|
||||
class TransactionCreate(LoginRequiredMixin, SingleTableView):
|
||||
"""
|
||||
Show transfer page
|
||||
|
||||
TODO: If user have sufficient rights, they can transfer from an other note
|
||||
"""
|
||||
queryset = Transaction.objects.order_by("-id").all()[:50]
|
||||
template_name = "note/transaction_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_context_data(self, **kwargs):
|
||||
"""
|
||||
Add some context variables in template such as page title
|
||||
@ -117,21 +120,26 @@ class ConsoView(LoginRequiredMixin, SingleTableView):
|
||||
"""
|
||||
Consume
|
||||
"""
|
||||
queryset = Transaction.objects.order_by("-id").all()[:50]
|
||||
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_context_data(self, **kwargs):
|
||||
"""
|
||||
Add some context variables in template such as page title
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
from django.db.models import Count
|
||||
buttons = TransactionTemplate.objects.filter(display=True) \
|
||||
.annotate(clicks=Count('templatetransaction')).order_by('category__name', 'name')
|
||||
buttons = TransactionTemplate.objects.filter(PermissionBackend()
|
||||
.filter_queryset(self.request.user, TransactionTemplate, "view")) \
|
||||
.filter(display=True).annotate(clicks=Count('templatetransaction')).order_by('category__name', 'name')
|
||||
context['transaction_templates'] = buttons
|
||||
context['most_used'] = buttons.order_by('-clicks', 'name')[:10]
|
||||
context['title'] = _("Consumptions")
|
||||
|
0
apps/permission/templatetags/__init__.py
Normal file
0
apps/permission/templatetags/__init__.py
Normal file
42
apps/permission/templatetags/perms.py
Normal file
42
apps/permission/templatetags/perms.py
Normal file
@ -0,0 +1,42 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.template.defaultfilters import stringfilter
|
||||
|
||||
from logs.middlewares import get_current_authenticated_user
|
||||
from django import template
|
||||
|
||||
from member.backends import PermissionBackend
|
||||
|
||||
|
||||
def has_perm(value):
|
||||
return get_current_authenticated_user().has_perm(value)
|
||||
|
||||
|
||||
@stringfilter
|
||||
def not_empty_model_list(model_name):
|
||||
user = get_current_authenticated_user()
|
||||
if user.is_superuser:
|
||||
return True
|
||||
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, "view"))
|
||||
return qs.exists()
|
||||
|
||||
|
||||
@stringfilter
|
||||
def not_empty_model_change_list(model_name):
|
||||
user = get_current_authenticated_user()
|
||||
if user.is_superuser:
|
||||
return True
|
||||
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, "change"))
|
||||
return qs.exists()
|
||||
|
||||
|
||||
register = template.Library()
|
||||
register.filter('has_perm', has_perm)
|
||||
register.filter('not_empty_model_list', not_empty_model_list)
|
||||
register.filter('not_empty_model_change_list', not_empty_model_change_list)
|
@ -1,4 +1,4 @@
|
||||
{% load static i18n pretty_money static getenv %}
|
||||
{% load static i18n pretty_money static getenv perms %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
@ -74,18 +74,26 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavDropdown">
|
||||
<ul class="navbar-nav">
|
||||
{% if "note.transactiontemplate"|not_empty_model_list %}
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="{% url 'note:consos' %}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if "member.club"|not_empty_model_list %}
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="{% url 'member:club_list' %}"><i class="fa fa-users"></i> {% trans 'Clubs' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if "activity.activity"|not_empty_model_list %}
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if "note.transactiontemplate"|not_empty_model_change_list %}
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="{% url 'note:template_list' %}"><i class="fa fa-coffee"></i> {% trans 'Buttons' %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="{% url 'note:transfer' %}"><i class="fa fa-exchange"></i>{% trans 'Transfer' %} </a>
|
||||
</li>
|
||||
|
Loading…
Reference in New Issue
Block a user