Move core auth mixins to their own file

This commit is contained in:
imperosol 2025-01-10 21:37:12 +01:00
parent cba915c34d
commit 0c01ad1770
34 changed files with 274 additions and 235 deletions

View File

@ -7,7 +7,7 @@ from ninja_extra.schemas import PaginatedResponseSchema
from accounting.models import ClubAccount, Company
from accounting.schemas import ClubAccountSchema, CompanySchema
from core.api_permissions import CanAccessLookup
from core.auth.api_permissions import CanAccessLookup
@api_controller("/lookup", permissions=[CanAccessLookup])

View File

@ -44,8 +44,13 @@ from accounting.widgets.select import (
)
from club.models import Club
from club.widgets.select import AutoCompleteSelectClub
from core.auth.mixins import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
)
from core.models import User
from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin
from core.views.forms import SelectDate, SelectFile
from core.views.mixins import TabedViewMixin
from core.views.widgets.select import AutoCompleteSelectUser

View File

@ -7,7 +7,7 @@ from ninja_extra.schemas import PaginatedResponseSchema
from club.models import Club
from club.schemas import ClubSchema
from core.api_permissions import CanAccessLookup
from core.auth.api_permissions import CanAccessLookup
@api_controller("/club")

View File

@ -50,15 +50,14 @@ from com.views import (
PosterEditBaseView,
PosterListBaseView,
)
from core.models import PageRev
from core.views import (
from core.auth.mixins import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
DetailFormView,
PageEditViewBase,
)
from core.models import PageRev
from core.views import DetailFormView, PageEditViewBase
from core.views.mixins import TabedViewMixin
from counter.models import Selling

View File

@ -44,8 +44,8 @@ from club.models import Club, Mailing
from com.calendar import IcsCalendar
from com.forms import NewsDateForm, NewsForm, PosterForm
from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle
from core.auth.mixins import CanEditPropMixin, CanViewMixin
from core.models import User
from core.views import CanEditPropMixin, CanViewMixin, QuickNotifMixin, TabedViewMixin
from core.views.mixins import QuickNotifMixin, TabedViewMixin
from core.views.widgets.markdown import MarkdownInput

View File

@ -11,10 +11,7 @@ from ninja_extra.pagination import PageNumberPaginationExtra
from ninja_extra.schemas import PaginatedResponseSchema
from club.models import Mailing
from core.api_permissions import (
CanAccessLookup,
CanView,
)
from core.auth.api_permissions import CanAccessLookup, CanView
from core.models import Group, SithFile, User
from core.schemas import (
FamilyGodfatherSchema,

0
core/auth/__init__.py Normal file
View File

212
core/auth/mixins.py Normal file
View File

@ -0,0 +1,212 @@
#
# Copyright 2016,2017
# - Skia <skia@libskia.so>
# - Sli <antoine@bartuccio.fr>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
import types
from typing import Any
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import PermissionDenied
from django.views.generic.base import View
from core.models import User
def can_edit_prop(obj: Any, user: User) -> bool:
"""Can the user edit the properties of the object.
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
Returns:
True if user is authorized to edit object properties else False
Examples:
```python
if not can_edit_prop(self.object ,request.user):
raise PermissionDenied
```
"""
return obj is None or user.is_owner(obj)
def can_edit(obj: Any, user: User) -> bool:
"""Can the user edit the object.
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
Returns:
True if user is authorized to edit object else False
Examples:
```python
if not can_edit(self.object, request.user):
raise PermissionDenied
```
"""
if obj is None or user.can_edit(obj):
return True
return can_edit_prop(obj, user)
def can_view(obj: Any, user: User) -> bool:
"""Can the user see the object.
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
Returns:
True if user is authorized to see object else False
Examples:
```python
if not can_view(self.object ,request.user):
raise PermissionDenied
```
"""
if obj is None or user.can_view(obj):
return True
return can_edit(obj, user)
class GenericContentPermissionMixinBuilder(View):
"""Used to build permission mixins.
This view protect any child view that would be showing an object that is restricted based
on two properties.
Attributes:
raised_error: permission to be raised
"""
raised_error = PermissionDenied
@staticmethod
def permission_function(obj: Any, user: User) -> bool:
"""Function to test permission with."""
return False
@classmethod
def get_permission_function(cls, obj, user):
return cls.permission_function(obj, user)
def dispatch(self, request, *arg, **kwargs):
if hasattr(self, "get_object") and callable(self.get_object):
self.object = self.get_object()
if not self.get_permission_function(self.object, request.user):
raise self.raised_error
return super().dispatch(request, *arg, **kwargs)
# If we get here, it's a ListView
queryset = self.get_queryset()
l_id = [o.id for o in queryset if self.get_permission_function(o, request.user)]
if not l_id and queryset.count() != 0:
raise self.raised_error
self._get_queryset = self.get_queryset
def get_qs(self2):
return self2._get_queryset().filter(id__in=l_id)
self.get_queryset = types.MethodType(get_qs, self)
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 dispatch(self, request, *arg, **kwargs):
res = super().dispatch(request, *arg, **kwargs)
if not request.user.is_authenticated:
raise PermissionDenied
return res
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):
"""Ensure the user has owner permissions on the child view object.
In other word, you can make a view with this view as parent,
and it will be retricted to the users that are in the
object's owner_group or that pass the `obj.can_be_viewed_by` test.
Raises:
PermissionDenied: If the user cannot see the object
"""
permission_function = can_edit_prop
class CanEditMixin(GenericContentPermissionMixinBuilder):
"""Ensure the user has permission to edit this view's object.
Raises:
PermissionDenied: if the user cannot edit this view's object.
"""
permission_function = can_edit
class CanViewMixin(GenericContentPermissionMixinBuilder):
"""Ensure the user has permission to view this view's object.
Raises:
PermissionDenied: if the user cannot edit this view's object.
"""
permission_function = can_view
class FormerSubscriberMixin(AccessMixin):
"""Check if the user was at least an old subscriber.
Raises:
PermissionDenied: if the user never subscribed.
"""
def dispatch(self, request, *args, **kwargs):
if not request.user.was_subscribed:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class SubscriberMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_subscribed:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)

View File

@ -22,23 +22,17 @@
#
#
import types
from typing import Any
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import PermissionDenied
from django.http import (
HttpResponseForbidden,
HttpResponseNotFound,
HttpResponseServerError,
)
from django.shortcuts import render
from django.utils.functional import cached_property
from django.views.generic.base import View
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import FormView
from sentry_sdk import last_event_id
from core.models import User
from core.views.forms import LoginForm
@ -60,186 +54,6 @@ def internal_servor_error(request):
return HttpResponseServerError(render(request, "core/500.jinja"))
def can_edit_prop(obj: Any, user: User) -> bool:
"""Can the user edit the properties of the object.
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
Returns:
True if user is authorized to edit object properties else False
Examples:
```python
if not can_edit_prop(self.object ,request.user):
raise PermissionDenied
```
"""
return obj is None or user.is_owner(obj)
def can_edit(obj: Any, user: User) -> bool:
"""Can the user edit the object.
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
Returns:
True if user is authorized to edit object else False
Examples:
```python
if not can_edit(self.object, request.user):
raise PermissionDenied
```
"""
if obj is None or user.can_edit(obj):
return True
return can_edit_prop(obj, user)
def can_view(obj: Any, user: User) -> bool:
"""Can the user see the object.
Args:
obj: Object to test for permission
user: core.models.User to test permissions against
Returns:
True if user is authorized to see object else False
Examples:
```python
if not can_view(self.object ,request.user):
raise PermissionDenied
```
"""
if obj is None or user.can_view(obj):
return True
return can_edit(obj, user)
class GenericContentPermissionMixinBuilder(View):
"""Used to build permission mixins.
This view protect any child view that would be showing an object that is restricted based
on two properties.
Attributes:
raised_error: permission to be raised
"""
raised_error = PermissionDenied
@staticmethod
def permission_function(obj: Any, user: User) -> bool:
"""Function to test permission with."""
return False
@classmethod
def get_permission_function(cls, obj, user):
return cls.permission_function(obj, user)
def dispatch(self, request, *arg, **kwargs):
if hasattr(self, "get_object") and callable(self.get_object):
self.object = self.get_object()
if not self.get_permission_function(self.object, request.user):
raise self.raised_error
return super().dispatch(request, *arg, **kwargs)
# If we get here, it's a ListView
queryset = self.get_queryset()
l_id = [o.id for o in queryset if self.get_permission_function(o, request.user)]
if not l_id and queryset.count() != 0:
raise self.raised_error
self._get_queryset = self.get_queryset
def get_qs(self2):
return self2._get_queryset().filter(id__in=l_id)
self.get_queryset = types.MethodType(get_qs, self)
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 dispatch(self, request, *arg, **kwargs):
res = super().dispatch(request, *arg, **kwargs)
if not request.user.is_authenticated:
raise PermissionDenied
return res
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):
"""Ensure the user has owner permissions on the child view object.
In other word, you can make a view with this view as parent,
and it will be retricted to the users that are in the
object's owner_group or that pass the `obj.can_be_viewed_by` test.
Raises:
PermissionDenied: If the user cannot see the object
"""
permission_function = can_edit_prop
class CanEditMixin(GenericContentPermissionMixinBuilder):
"""Ensure the user has permission to edit this view's object.
Raises:
PermissionDenied: if the user cannot edit this view's object.
"""
permission_function = can_edit
class CanViewMixin(GenericContentPermissionMixinBuilder):
"""Ensure the user has permission to view this view's object.
Raises:
PermissionDenied: if the user cannot edit this view's object.
"""
permission_function = can_view
class FormerSubscriberMixin(AccessMixin):
"""Check if the user was at least an old subscriber.
Raises:
PermissionDenied: if the user never subscribed.
"""
def dispatch(self, request, *args, **kwargs):
if not request.user.was_subscribed:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class SubscriberMixin(AccessMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_subscribed:
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class DetailFormView(SingleObjectMixin, FormView):
"""Class that allow both a detail view and a form view."""

View File

@ -33,13 +33,13 @@ from django.views.generic import DetailView, ListView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import DeleteView, FormMixin, UpdateView
from core.models import Notification, SithFile, User
from core.views import (
from core.auth.mixins import (
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
can_view,
)
from core.models import Notification, SithFile, User
from core.views.mixins import AllowFragment
from core.views.widgets.select import (
AutoCompleteSelectMultipleGroup,

View File

@ -21,8 +21,9 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from core.auth.mixins import CanCreateMixin, CanEditMixin
from core.models import Group, User
from core.views import CanCreateMixin, CanEditMixin, DetailFormView
from core.views import DetailFormView
from core.views.widgets.select import AutoCompleteSelectMultipleUser
# Forms

View File

@ -21,8 +21,13 @@ from django.urls import reverse_lazy
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from core.auth.mixins import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
)
from core.models import LockError, Page, PageRev
from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin
from core.views.forms import PageForm, PagePropForm
from core.views.widgets.markdown import MarkdownInput

View File

@ -54,8 +54,8 @@ from django.views.generic.dates import MonthMixin, YearMixin
from django.views.generic.edit import FormView, UpdateView
from honeypot.decorators import check_honeypot
from core.auth.mixins import CanEditMixin, CanEditPropMixin, CanViewMixin
from core.models import Gift, Preferences, User
from core.views import CanEditMixin, CanEditPropMixin, CanViewMixin
from core.views.forms import (
GiftForm,
LoginForm,

View File

@ -20,7 +20,7 @@ from ninja_extra import ControllerBase, api_controller, paginate, route
from ninja_extra.pagination import PageNumberPaginationExtra
from ninja_extra.schemas import PaginatedResponseSchema
from core.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoot
from core.auth.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoot
from counter.models import Counter, Product, ProductType
from counter.schemas import (
CounterFilterSchema,

View File

@ -24,8 +24,8 @@ from django.utils import timezone
from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from core.auth.mixins import CanEditMixin, CanViewMixin
from core.utils import get_semester_code, get_start_of_semester
from core.views import CanEditMixin, CanViewMixin
from counter.forms import CounterEditForm, ProductEditForm
from counter.models import Counter, Product, ProductType, Refilling, Selling
from counter.utils import is_logged_in_counter

View File

@ -23,7 +23,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
from django.views.generic.edit import UpdateView
from core.views import CanViewMixin
from core.auth.mixins import CanViewMixin
from counter.forms import CashSummaryFormBase
from counter.models import (
CashRegisterSummary,

View File

@ -31,9 +31,9 @@ from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin
from ninja.main import HttpRequest
from core.auth.mixins import CanViewMixin
from core.models import User
from core.utils import FormFragmentTemplateData
from core.views import CanViewMixin
from counter.forms import RefillForm
from counter.models import Counter, Customer, Product, Selling
from counter.utils import is_logged_in_counter

View File

@ -18,7 +18,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, UpdateView
from core.views import CanViewMixin
from core.auth.mixins import CanViewMixin
from counter.forms import EticketForm
from counter.models import Eticket, Selling
from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin

View File

@ -22,7 +22,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView
from django.views.generic.edit import FormMixin, ProcessFormView
from core.views import CanViewMixin
from core.auth.mixins import CanViewMixin
from core.views.forms import LoginForm
from counter.forms import GetUserForm
from counter.models import Counter

View File

@ -21,8 +21,8 @@ from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.generic.edit import DeleteView, FormView
from core.auth.mixins import can_edit
from core.utils import FormFragmentTemplateData
from core.views import can_edit
from counter.forms import StudentCardForm
from counter.models import Customer, StudentCard
from counter.utils import is_logged_in_counter

View File

@ -1 +1 @@
::: core.api_permissions
::: core.auth.api_permissions

View File

@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from core.views import CanCreateMixin, CanEditMixin, CanViewMixin
from core.auth.mixins import CanCreateMixin, CanEditMixin, CanViewMixin
from core.views.forms import SelectDateTime
from core.views.widgets.markdown import MarkdownInput
from core.views.widgets.select import (

View File

@ -43,7 +43,7 @@ from haystack.query import RelatedSearchQuerySet
from honeypot.decorators import check_honeypot
from club.widgets.select import AutoCompleteSelectClub
from core.views import (
from core.auth.mixins import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,

View File

@ -27,12 +27,9 @@ from django.http import Http404, JsonResponse
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, View
from core.auth.mixins import CanViewMixin, FormerSubscriberMixin
from core.models import User
from core.views import (
CanViewMixin,
FormerSubscriberMixin,
UserTabsMixin,
)
from core.views import UserTabsMixin
from galaxy.models import Galaxy, GalaxyLane

View File

@ -28,8 +28,13 @@ from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic.edit import BaseFormView, CreateView, DeleteView, UpdateView
from club.models import Club
from core.auth.mixins import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
)
from core.models import Page, User
from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin
from counter.forms import GetUserForm
from counter.models import Counter, Customer, Selling
from launderette.models import Launderette, Machine, Slot, Token

View File

@ -32,8 +32,9 @@ from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import FormView
from phonenumber_field.widgets import RegionalPhoneNumberWidget
from core.auth.mixins import FormerSubscriberMixin
from core.models import User
from core.views import FormerSubscriberMixin, search_user
from core.views import search_user
from core.views.forms import SelectDate
# Enum to select search type

View File

@ -7,7 +7,7 @@ from ninja_extra import ControllerBase, api_controller, paginate, route
from ninja_extra.exceptions import NotFound
from ninja_extra.pagination import PageNumberPaginationExtra, PaginatedResponseSchema
from core.api_permissions import IsInGroup, IsRoot, IsSubscriber
from core.auth.api_permissions import IsInGroup, IsRoot, IsSubscriber
from pedagogy.models import UV
from pedagogy.schemas import SimpleUvSchema, UvFilterSchema, UvSchema
from pedagogy.utbm_api import find_uv

View File

@ -35,13 +35,14 @@ from django.views.generic import (
UpdateView,
)
from core.models import Notification, User
from core.views import (
from core.auth.mixins import (
CanCreateMixin,
CanEditPropMixin,
DetailFormView,
CanViewMixin,
FormerSubscriberMixin,
)
from core.models import Notification, User
from core.views import DetailFormView
from pedagogy.forms import (
UVCommentForm,
UVCommentModerationForm,
@ -50,8 +51,6 @@ from pedagogy.forms import (
)
from pedagogy.models import UV, UVComment, UVCommentReport
# Acutal views
class UVDetailFormView(CanViewMixin, DetailFormView):
"""Display every comment of an UV and detailed infos about it.

View File

@ -12,7 +12,7 @@ from ninja_extra.permissions import IsAuthenticated
from ninja_extra.schemas import PaginatedResponseSchema
from pydantic import NonNegativeInt
from core.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoot
from core.auth.api_permissions import CanAccessLookup, CanView, IsInGroup, IsRoot
from core.models import Notification, User
from sas.models import Album, PeoplePictureRelation, Picture
from sas.schemas import (

View File

@ -23,8 +23,8 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, TemplateView
from django.views.generic.edit import FormMixin, FormView, UpdateView
from core.auth.mixins import CanEditMixin, CanViewMixin
from core.models import SithFile, User
from core.views import CanEditMixin, CanViewMixin
from core.views.files import FileView, send_file
from sas.forms import (
AlbumEditForm,

View File

@ -155,13 +155,12 @@ TEMPLATES = [
"add_attr": "core.templatetags.renderer.add_attr",
},
"globals": {
"can_edit_prop": "core.views.can_edit_prop",
"can_edit": "core.views.can_edit",
"can_view": "core.views.can_view",
"can_edit_prop": "core.auth.mixins.can_edit_prop",
"can_edit": "core.auth.mixins.can_edit",
"can_view": "core.auth.mixins.can_view",
"settings": "sith.settings",
"Launderette": "launderette.models.Launderette",
"Counter": "counter.models.Counter",
"ProductType": "counter.models.ProductType",
"timezone": "django.utils.timezone",
"get_sith": "com.views.sith",
"get_language": "django.utils.translation.get_language",
@ -292,7 +291,7 @@ STORAGES = {
# Auth configuration
AUTH_USER_MODEL = "core.User"
AUTH_ANONYMOUS_MODEL = "core.models.AnonymousUser"
AUTHENTICATION_BACKENDS = ["core.auth_backends.SithModelBackend"]
AUTHENTICATION_BACKENDS = ["core.auth.backends.SithModelBackend"]
LOGIN_URL = "/login"
LOGOUT_URL = "/logout"
LOGIN_REDIRECT_URL = "/"

View File

@ -38,8 +38,13 @@ from django.views.generic import DetailView, RedirectView, TemplateView, View
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from club.models import Club
from core.auth.mixins import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
)
from core.models import User
from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin
from core.views.forms import SelectDate
from core.views.mixins import QuickNotifMixin, TabedViewMixin
from core.views.widgets.select import AutoCompleteSelectUser