Aller au contenu

Auth

Backend

SithModelBackend

Bases: 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.

with_perm(perm, is_active=True, include_superusers=False, obj=None)

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.

Source code in core/auth/backends.py
@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)

Mixins

CanEditMixin

Bases: GenericContentPermissionMixinBuilder

Ensure the user has permission to edit this view's object.

Raises:

Type Description
PermissionDenied

if the user cannot edit this view's object.

CanViewMixin

Bases: GenericContentPermissionMixinBuilder

Ensure the user has permission to view this view's object.

Raises:

Type Description
PermissionDenied

if the user cannot edit this view's object.

CanEditPropMixin

Bases: 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:

Type Description
PermissionDenied

If the user cannot see the object

FormerSubscriberMixin

Bases: AccessMixin

Check if the user was at least an old subscriber.

Raises:

Type Description
PermissionDenied

if the user never subscribed.

PermissionOrAuthorRequiredMixin

Bases: PermissionRequiredMixin

Require that the user has the required perm or is the object author.

This mixin can be used in combination with DetailView, or another base class that implements the get_object method.

Example

In the following code, a user will be able to edit news if he has the com.change_news permission or if he tries to edit his own news :

class NewsEditView(PermissionOrAuthorRequiredMixin, DetailView):
    model = News
    author_field = "author"
    permission_required = "com.change_news"

This is more or less equivalent to :

class NewsEditView(PermissionOrAuthorRequiredMixin, DetailView):
    model = News

    def dispatch(self, request, *args, **kwargs):
        self.object = self.get_object()
        if not (
            user.has_perm("com.change_news")
            or self.object.author == request.user
        ):
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)

can_edit_prop(obj, user)

Can the user edit the properties of the object.

Parameters:

Name Type Description Default
obj Any

Object to test for permission

required
user User

core.models.User to test permissions against

required

Returns:

Type Description
bool

True if user is authorized to edit object properties else False

Example
if not can_edit_prop(self.object ,request.user):
    raise PermissionDenied
Source code in core/auth/mixins.py
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

    Example:
        ```python
        if not can_edit_prop(self.object ,request.user):
            raise PermissionDenied
        ```
    """
    return obj is None or user.is_owner(obj)

can_edit(obj, user)

Can the user edit the object.

Parameters:

Name Type Description Default
obj Any

Object to test for permission

required
user User

core.models.User to test permissions against

required

Returns:

Type Description
bool

True if user is authorized to edit object else False

Example
if not can_edit(self.object, request.user):
    raise PermissionDenied
Source code in core/auth/mixins.py
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

    Example:
        ```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)

can_view(obj, user)

Can the user see the object.

Parameters:

Name Type Description Default
obj Any

Object to test for permission

required
user User

core.models.User to test permissions against

required

Returns:

Type Description
bool

True if user is authorized to see object else False

Example
if not can_view(self.object ,request.user):
    raise PermissionDenied
Source code in core/auth/mixins.py
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

    Example:
        ```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)