mirror of
https://github.com/ae-utbm/sith.git
synced 2025-11-10 14:03:12 +00:00
Compare commits
2 Commits
dependabot
...
taiste
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7284c936b | ||
|
|
53f7bf08d3 |
@@ -700,7 +700,7 @@ class PosterModerateView(PermissionRequiredMixin, ComTabsMixin, View):
|
|||||||
parsed = urlparse(referer)
|
parsed = urlparse(referer)
|
||||||
if parsed.netloc == settings.SITH_URL:
|
if parsed.netloc == settings.SITH_URL:
|
||||||
return redirect(parsed.path)
|
return redirect(parsed.path)
|
||||||
return redirect(reverse("com:poster_list"))
|
return redirect("com:poster_list")
|
||||||
|
|
||||||
|
|
||||||
class ScreenListView(PermissionRequiredMixin, ComTabsMixin, ListView):
|
class ScreenListView(PermissionRequiredMixin, ComTabsMixin, ListView):
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import types
|
import types
|
||||||
import warnings
|
|
||||||
from typing import TYPE_CHECKING, Any, LiteralString
|
from typing import TYPE_CHECKING, Any, LiteralString
|
||||||
|
|
||||||
from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin
|
from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin
|
||||||
@@ -147,45 +146,6 @@ class GenericContentPermissionMixinBuilder(View):
|
|||||||
return super().dispatch(request, *arg, **kwargs)
|
return super().dispatch(request, *arg, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CanCreateMixin(View):
|
|
||||||
"""Protect any child view that would create an object.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
PermissionDenied:
|
|
||||||
If the user has not the necessary permission
|
|
||||||
to create the object of the view.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init_subclass__(cls, **kwargs):
|
|
||||||
warnings.warn(
|
|
||||||
f"{cls.__name__} is deprecated and should be replaced "
|
|
||||||
"by other permission verification mecanism.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
super().__init_subclass__(**kwargs)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
warnings.warn(
|
|
||||||
f"{self.__class__.__name__} is deprecated and should be replaced "
|
|
||||||
"by other permission verification mecanism.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def dispatch(self, request, *arg, **kwargs):
|
|
||||||
if not request.user.is_authenticated:
|
|
||||||
raise PermissionDenied
|
|
||||||
return super().dispatch(request, *arg, **kwargs)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
obj = form.instance
|
|
||||||
if can_edit_prop(obj, self.request.user):
|
|
||||||
return super().form_valid(form)
|
|
||||||
raise PermissionDenied
|
|
||||||
|
|
||||||
|
|
||||||
class CanEditPropMixin(GenericContentPermissionMixinBuilder):
|
class CanEditPropMixin(GenericContentPermissionMixinBuilder):
|
||||||
"""Ensure the user has owner permissions on the child view object.
|
"""Ensure the user has owner permissions on the child view object.
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
- can_edit_prop
|
- can_edit_prop
|
||||||
- can_edit
|
- can_edit
|
||||||
- can_view
|
- can_view
|
||||||
- CanCreateMixin
|
|
||||||
- CanEditMixin
|
- CanEditMixin
|
||||||
- CanViewMixin
|
- CanViewMixin
|
||||||
- CanEditPropMixin
|
- CanEditPropMixin
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ Pour les vues sous forme de fonction, il y a le décorateur
|
|||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
obj.is_moderated = True
|
obj.is_moderated = True
|
||||||
obj.save()
|
obj.save()
|
||||||
return redirect(reverse("com:news_list"))
|
return redirect("com:news_list")
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Function-based view"
|
=== "Function-based view"
|
||||||
@@ -233,7 +233,7 @@ Pour les vues sous forme de fonction, il y a le décorateur
|
|||||||
news = get_object_or_404(News, id=news_id)
|
news = get_object_or_404(News, id=news_id)
|
||||||
news.is_moderated = True
|
news.is_moderated = True
|
||||||
news.save()
|
news.save()
|
||||||
return redirect(reverse("com:news_list"))
|
return redirect("com:news_list")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Accès à des éléments en particulier
|
## Accès à des éléments en particulier
|
||||||
@@ -447,10 +447,9 @@ l'utilisateur recevra une liste vide d'objet.
|
|||||||
Voici un exemple d'utilisation en reprenant l'objet Article crée précédemment :
|
Voici un exemple d'utilisation en reprenant l'objet Article crée précédemment :
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from django.views.generic import CreateView, DetailView
|
from django.views.generic import DetailView
|
||||||
|
|
||||||
from core.auth.mixins import CanViewMixin, CanCreateMixin
|
|
||||||
|
|
||||||
|
from core.auth.mixins import CanViewMixin
|
||||||
from com.models import WeekmailArticle
|
from com.models import WeekmailArticle
|
||||||
|
|
||||||
|
|
||||||
@@ -459,48 +458,15 @@ from com.models import WeekmailArticle
|
|||||||
# d'une classe de base pour fonctionner correctement.
|
# d'une classe de base pour fonctionner correctement.
|
||||||
class ArticlesDetailView(CanViewMixin, DetailView):
|
class ArticlesDetailView(CanViewMixin, DetailView):
|
||||||
model = WeekmailArticle
|
model = WeekmailArticle
|
||||||
|
|
||||||
|
|
||||||
# Même chose pour une vue de création de l'objet Article
|
|
||||||
class ArticlesCreateView(CanCreateMixin, CreateView):
|
|
||||||
model = WeekmailArticle
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Les mixins suivants sont implémentés :
|
Les mixins suivants sont implémentés :
|
||||||
|
|
||||||
- [CanCreateMixin][core.auth.mixins.CanCreateMixin] : l'utilisateur peut-il créer l'objet ?
|
|
||||||
Ce mixin existe, mais est déprécié et ne doit plus être utilisé !
|
|
||||||
- [CanEditPropMixin][core.auth.mixins.CanEditPropMixin] : l'utilisateur peut-il éditer les propriétés de l'objet ?
|
- [CanEditPropMixin][core.auth.mixins.CanEditPropMixin] : l'utilisateur peut-il éditer les propriétés de l'objet ?
|
||||||
- [CanEditMixin][core.auth.mixins.CanEditMixin] : L'utilisateur peut-il éditer l'objet ?
|
- [CanEditMixin][core.auth.mixins.CanEditMixin] : L'utilisateur peut-il éditer l'objet ?
|
||||||
- [CanViewMixin][core.auth.mixins.CanViewMixin] : L'utilisateur peut-il voir l'objet ?
|
- [CanViewMixin][core.auth.mixins.CanViewMixin] : L'utilisateur peut-il voir l'objet ?
|
||||||
- [FormerSubscriberMixin][core.auth.mixins.FormerSubscriberMixin] : L'utilisateur a-t-il déjà été cotisant ?
|
- [FormerSubscriberMixin][core.auth.mixins.FormerSubscriberMixin] : L'utilisateur a-t-il déjà été cotisant ?
|
||||||
|
|
||||||
!!!danger "CanCreateMixin"
|
|
||||||
|
|
||||||
L'usage de `CanCreateMixin` est dangereux et ne doit en aucun cas être
|
|
||||||
étendu.
|
|
||||||
La façon dont ce mixin marche est qu'il valide le formulaire
|
|
||||||
de création et crée l'objet sans le persister en base de données, puis
|
|
||||||
vérifie les droits sur cet objet non-persisté.
|
|
||||||
Le danger de ce système vient de multiples raisons :
|
|
||||||
|
|
||||||
- Les vérifications se faisant sur un objet non persisté,
|
|
||||||
l'utilisation de mécanismes nécessitant une persistance préalable
|
|
||||||
peut mener à des comportements indésirés, voire à des erreurs.
|
|
||||||
- Les développeurs de django ayant tendance à restreindre progressivement
|
|
||||||
les actions qui peuvent être faites sur des objets non-persistés,
|
|
||||||
les mises-à-jour de django deviennent plus compliquées.
|
|
||||||
- La vérification des droits ne se fait que dans les requêtes POST,
|
|
||||||
à la toute fin de la requête.
|
|
||||||
Tout ce qui arrive avant n'est absolument pas protégé.
|
|
||||||
Toute opération (même les suppressions et les créations) qui ont
|
|
||||||
lieu avant la persistance de l'objet seront appliquées,
|
|
||||||
même sans permission.
|
|
||||||
- Si un développeur du site fait l'erreur de surcharger
|
|
||||||
la méthode `form_valid` (ce qui est plutôt courant,
|
|
||||||
lorsqu'on veut accomplir certaines actions
|
|
||||||
quand un formulaire est valide), on peut se retrouver
|
|
||||||
dans une situation où l'objet est persisté sans aucune protection.
|
|
||||||
|
|
||||||
!!!danger "Performance"
|
!!!danger "Performance"
|
||||||
|
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ from functools import partial
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import html, timezone
|
from django.utils import html, timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, ListView, RedirectView
|
from django.views.generic import DetailView, ListView, RedirectView
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
@@ -44,7 +44,6 @@ from honeypot.decorators import check_honeypot
|
|||||||
|
|
||||||
from club.widgets.ajax_select import AutoCompleteSelectClub
|
from club.widgets.ajax_select import AutoCompleteSelectClub
|
||||||
from core.auth.mixins import (
|
from core.auth.mixins import (
|
||||||
CanCreateMixin,
|
|
||||||
CanEditMixin,
|
CanEditMixin,
|
||||||
CanEditPropMixin,
|
CanEditPropMixin,
|
||||||
CanViewMixin,
|
CanViewMixin,
|
||||||
@@ -180,11 +179,19 @@ class ForumForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ForumCreateView(CanCreateMixin, CreateView):
|
class ForumCreateView(UserPassesTestMixin, CreateView):
|
||||||
model = Forum
|
model = Forum
|
||||||
form_class = ForumForm
|
form_class = ForumForm
|
||||||
template_name = "core/create.jinja"
|
template_name = "core/create.jinja"
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
if self.request.user.has_perm("forum.add_forum"):
|
||||||
|
return True
|
||||||
|
parent = Forum.objects.filter(id=self.request.GET["parent"]).first()
|
||||||
|
if parent is not None:
|
||||||
|
return self.request.user.is_owner(parent)
|
||||||
|
return False
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = super().get_initial()
|
init = super().get_initial()
|
||||||
parent = Forum.objects.filter(id=self.request.GET["parent"]).first()
|
parent = Forum.objects.filter(id=self.request.GET["parent"]).first()
|
||||||
@@ -258,18 +265,19 @@ class TopicForm(forms.ModelForm):
|
|||||||
@method_decorator(
|
@method_decorator(
|
||||||
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
||||||
)
|
)
|
||||||
class ForumTopicCreateView(CanCreateMixin, CreateView):
|
class ForumTopicCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
|
||||||
model = ForumMessage
|
model = ForumMessage
|
||||||
form_class = TopicForm
|
form_class = TopicForm
|
||||||
template_name = "forum/reply.jinja"
|
template_name = "forum/reply.jinja"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
@cached_property
|
||||||
self.forum = get_object_or_404(
|
def forum(self):
|
||||||
Forum, id=self.kwargs["forum_id"], is_category=False
|
return get_object_or_404(Forum, id=self.kwargs["forum_id"], is_category=False)
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.has_perm("forum.add_forumtopic") or (
|
||||||
|
self.request.user.can_view(self.forum)
|
||||||
)
|
)
|
||||||
if not request.user.can_view(self.forum):
|
|
||||||
raise PermissionDenied
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
topic = ForumTopic(
|
topic = ForumTopic(
|
||||||
@@ -404,7 +412,7 @@ class ForumMessageUndeleteView(SingleObjectMixin, RedirectView):
|
|||||||
@method_decorator(
|
@method_decorator(
|
||||||
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
||||||
)
|
)
|
||||||
class ForumMessageCreateView(CanCreateMixin, CreateView):
|
class ForumMessageCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
|
||||||
model = ForumMessage
|
model = ForumMessage
|
||||||
form_class = forms.modelform_factory(
|
form_class = forms.modelform_factory(
|
||||||
model=ForumMessage,
|
model=ForumMessage,
|
||||||
@@ -413,11 +421,14 @@ class ForumMessageCreateView(CanCreateMixin, CreateView):
|
|||||||
)
|
)
|
||||||
template_name = "forum/reply.jinja"
|
template_name = "forum/reply.jinja"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
@cached_property
|
||||||
self.topic = get_object_or_404(ForumTopic, id=self.kwargs["topic_id"])
|
def topic(self):
|
||||||
if not request.user.can_view(self.topic):
|
return get_object_or_404(ForumTopic, id=self.kwargs["topic_id"])
|
||||||
raise PermissionDenied
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
def test_func(self):
|
||||||
|
return self.request.user.has_perm(
|
||||||
|
"forum.add_forummessage"
|
||||||
|
) or self.request.user.can_view(self.topic)
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
init = super().get_initial()
|
init = super().get_initial()
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ dev = [
|
|||||||
]
|
]
|
||||||
tests = [
|
tests = [
|
||||||
"freezegun>=1.5.5,<2.0.0",
|
"freezegun>=1.5.5,<2.0.0",
|
||||||
"pytest>=8.4.2,<10.0.0",
|
"pytest>=8.4.2,<9.0.0",
|
||||||
"pytest-cov>=7.0.0,<8.0.0",
|
"pytest-cov>=7.0.0,<8.0.0",
|
||||||
"pytest-django<5.0.0,>=4.10.0",
|
"pytest-django<5.0.0,>=4.10.0",
|
||||||
"model-bakery<2.0.0,>=1.20.4",
|
"model-bakery<2.0.0,>=1.20.4",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from datetime import date
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
@@ -35,17 +35,13 @@ from django.forms.models import modelform_factory
|
|||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, RedirectView, TemplateView, View
|
from django.views.generic import DetailView, RedirectView, TemplateView, View
|
||||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||||
|
|
||||||
from club.models import Club
|
from club.models import Club
|
||||||
from core.auth.mixins import (
|
from core.auth.mixins import CanEditMixin, CanEditPropMixin, CanViewMixin
|
||||||
CanCreateMixin,
|
|
||||||
CanEditMixin,
|
|
||||||
CanEditPropMixin,
|
|
||||||
CanViewMixin,
|
|
||||||
)
|
|
||||||
from core.models import User
|
from core.models import User
|
||||||
from core.views.forms import SelectDate
|
from core.views.forms import SelectDate
|
||||||
from core.views.mixins import TabedViewMixin
|
from core.views.mixins import TabedViewMixin
|
||||||
@@ -117,19 +113,25 @@ class TrombiForm(forms.ModelForm):
|
|||||||
widgets = {"subscription_deadline": SelectDate, "comments_deadline": SelectDate}
|
widgets = {"subscription_deadline": SelectDate, "comments_deadline": SelectDate}
|
||||||
|
|
||||||
|
|
||||||
class TrombiCreateView(CanCreateMixin, CreateView):
|
class TrombiCreateView(UserPassesTestMixin, CreateView):
|
||||||
"""Create a trombi for a club."""
|
"""Create a trombi for a club."""
|
||||||
|
|
||||||
model = Trombi
|
model = Trombi
|
||||||
form_class = TrombiForm
|
form_class = TrombiForm
|
||||||
template_name = "core/create.jinja"
|
template_name = "core/create.jinja"
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def club(self):
|
||||||
|
return get_object_or_404(Club, id=self.kwargs["club_id"])
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.can_edit(self.club)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Affect club."""
|
"""Affect club."""
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
club = get_object_or_404(Club, id=self.kwargs["club_id"])
|
form.instance.club = self.club
|
||||||
form.instance.club = club
|
|
||||||
ret = self.form_valid(form)
|
ret = self.form_valid(form)
|
||||||
return ret
|
return ret
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user