mirror of
https://github.com/ae-utbm/sith.git
synced 2026-09-17 17:54:22 +00:00
94 lines
3.6 KiB
Python
94 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import typing
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth.backends import ModelBackend
|
|
from django.contrib.auth.models import Permission
|
|
from django.db.models import Exists, OuterRef, Q, QuerySet
|
|
|
|
from core.models import Group, User
|
|
|
|
if typing.TYPE_CHECKING:
|
|
from django.db.models.base import Model
|
|
|
|
|
|
class SithModelBackend(ModelBackend):
|
|
"""Custom auth backend for the Sith.
|
|
|
|
In fact, it's the exact same backend as `django.contrib.auth.backend.ModelBackend`,
|
|
with the exception that group permissions are fetched slightly differently.
|
|
Indeed, django tries by default to fetch the permissions associated
|
|
with all the `django.contrib.auth.models.Group` of a user ;
|
|
however, our User model overrides that, so the actual linked group model
|
|
is [core.models.Group][].
|
|
Instead of having the relation `auth_perm --> auth_group <-- core_user`,
|
|
we have `auth_perm --> auth_group <-- core_group <-- core_user`.
|
|
|
|
Thus, this backend make the small tweaks necessary to make
|
|
our custom models interact with the django auth.
|
|
"""
|
|
|
|
def _get_group_permissions(self, user_obj: User):
|
|
# union of querysets doesn't work if the queryset is ordered.
|
|
# The empty `order_by` here are actually there to *remove*
|
|
# any default ordering defined in managers or model Meta
|
|
groups = user_obj.groups.order_by()
|
|
if user_obj.is_subscribed:
|
|
groups = groups.union(
|
|
Group.objects.filter(pk=settings.SITH_GROUP_SUBSCRIBERS_ID).order_by()
|
|
)
|
|
return Permission.objects.filter(
|
|
group__group__in=groups.values_list("pk", flat=True)
|
|
)
|
|
|
|
@typing.override
|
|
def with_perm(
|
|
self,
|
|
perm: str | Permission,
|
|
is_active: bool | None = True,
|
|
include_superusers: bool = False,
|
|
obj: Model | None = None,
|
|
) -> QuerySet[User]:
|
|
"""Return users that have permission "perm".
|
|
|
|
Contrary to the base django method, superusers aren't included in the
|
|
result.
|
|
This is because the OR operation to include superusers in the query result
|
|
utterly destroy the query performances on postgres
|
|
(it makes it like 1000x slower, and I'm not even kidding).
|
|
To overcome that, we could use a UNION instead, but then we wouldn't
|
|
be able to perform further filter operations on the queryset.
|
|
Thus, the `include_superusers` argument is not used at all.
|
|
|
|
Because of that, it is useless to set `include_superusers`,
|
|
as it will be silently ignored.
|
|
The only reason it's still there is not to break the interface
|
|
of the base class.
|
|
"""
|
|
if isinstance(perm, str):
|
|
try:
|
|
app_label, codename = perm.split(".")
|
|
except ValueError as e:
|
|
raise ValueError(
|
|
"Permission name should be in the form "
|
|
"app_label.permission_codename."
|
|
) from e
|
|
permission_q = Q(codename=codename, content_type__app_label=app_label)
|
|
elif isinstance(perm, Permission):
|
|
permission_q = Q(pk=perm.pk)
|
|
else:
|
|
raise TypeError(
|
|
"The `perm` argument must be a string or a permission instance."
|
|
)
|
|
|
|
user_q = Exists(
|
|
Permission.objects.filter(
|
|
Q(group__group__users=OuterRef("pk")) | Q(user=OuterRef("pk")),
|
|
permission_q,
|
|
)
|
|
)
|
|
if is_active is not None:
|
|
user_q &= Q(is_active=is_active)
|
|
return User.objects.filter(user_q)
|