authentik.rbac.permissions

RBAC Permissions

 1"""RBAC Permissions"""
 2
 3from django.contrib.contenttypes.models import ContentType
 4from django.db.models import Model
 5from guardian.shortcuts import assign_perm
 6from rest_framework.permissions import BasePermission, DjangoObjectPermissions
 7from rest_framework.request import Request
 8from structlog.stdlib import get_logger
 9
10from authentik.rbac.models import InitialPermissions
11
12LOGGER = get_logger()
13
14
15class ObjectPermissions(DjangoObjectPermissions):
16    """RBAC Permissions"""
17
18    def has_permission(self, request: Request, view) -> bool:
19        """Always grant permission for object-specific requests
20        as view permission checking is done by `ObjectFilter`,
21        and write permission checking is done by `has_object_permission`"""
22        lookup = getattr(view, "lookup_url_kwarg", None) or getattr(view, "lookup_field", None)
23        if lookup and lookup in view.kwargs:
24            return True
25        # Legacy behaviour:
26        # Allow creation of objects even without explicit permission
27        queryset = self._queryset(view)
28        required_perms = self.get_required_permissions(request.method, queryset.model)
29        if (
30            len(required_perms) == 1
31            and f"{queryset.model._meta.app_label}.add_{queryset.model._meta.model_name}"
32            in required_perms
33            and getattr(view, "rbac_allow_create_without_perm", False)
34        ):
35            return True
36        return super().has_permission(request, view)
37
38    def has_object_permission(self, request: Request, view, obj: Model) -> bool:
39        queryset = self._queryset(view)
40        model_cls = queryset.model
41        perms = self.get_required_object_permissions(request.method, model_cls)
42        # Rank global permissions higher than per-object permissions
43        if request.user.has_perms(perms):
44            return True
45        # Allow access for owners if configured
46        if owner_field := getattr(view, "owner_field", None):
47            if getattr(obj, owner_field) == request.user:
48                return True
49        return super().has_object_permission(request, view, obj)
50
51
52def HasPermission(*perm: str) -> type[BasePermission]:
53    """Permission checker for any non-object permissions, returns
54    a BasePermission class that can be used with rest_framework"""
55
56    class checker(BasePermission):
57        def has_permission(self, request: Request, view):
58            return bool(request.user and request.user.has_perms(perm))
59
60    return checker
61
62
63# TODO: add `user: User` type annotation without circular dependencies.
64# The author of this function isn't proficient/patient enough to do it.
65def assign_initial_permissions(user, instance: Model):
66    # Performance here should not be an issue, but if needed, there are many optimization routes
67    initial_permissions_list = InitialPermissions.objects.filter(role__in=user.all_roles())
68    for initial_permissions in initial_permissions_list:
69        for permission in initial_permissions.permissions.all():
70            if permission.content_type != ContentType.objects.get_for_model(instance):
71                continue
72            LOGGER.debug(
73                "Adding initial permission",
74                initial_permission=permission,
75                subject=initial_permissions.role,
76                object=instance,
77            )
78            assign_perm(permission, initial_permissions.role, instance)
LOGGER = <BoundLoggerLazyProxy(logger=None, wrapper_class=None, processors=None, context_class=None, initial_values={}, logger_factory_args=())>
class ObjectPermissions(rest_framework.permissions.DjangoObjectPermissions):
16class ObjectPermissions(DjangoObjectPermissions):
17    """RBAC Permissions"""
18
19    def has_permission(self, request: Request, view) -> bool:
20        """Always grant permission for object-specific requests
21        as view permission checking is done by `ObjectFilter`,
22        and write permission checking is done by `has_object_permission`"""
23        lookup = getattr(view, "lookup_url_kwarg", None) or getattr(view, "lookup_field", None)
24        if lookup and lookup in view.kwargs:
25            return True
26        # Legacy behaviour:
27        # Allow creation of objects even without explicit permission
28        queryset = self._queryset(view)
29        required_perms = self.get_required_permissions(request.method, queryset.model)
30        if (
31            len(required_perms) == 1
32            and f"{queryset.model._meta.app_label}.add_{queryset.model._meta.model_name}"
33            in required_perms
34            and getattr(view, "rbac_allow_create_without_perm", False)
35        ):
36            return True
37        return super().has_permission(request, view)
38
39    def has_object_permission(self, request: Request, view, obj: Model) -> bool:
40        queryset = self._queryset(view)
41        model_cls = queryset.model
42        perms = self.get_required_object_permissions(request.method, model_cls)
43        # Rank global permissions higher than per-object permissions
44        if request.user.has_perms(perms):
45            return True
46        # Allow access for owners if configured
47        if owner_field := getattr(view, "owner_field", None):
48            if getattr(obj, owner_field) == request.user:
49                return True
50        return super().has_object_permission(request, view, obj)

RBAC Permissions

def has_permission(self, request: rest_framework.request.Request, view) -> bool:
19    def has_permission(self, request: Request, view) -> bool:
20        """Always grant permission for object-specific requests
21        as view permission checking is done by `ObjectFilter`,
22        and write permission checking is done by `has_object_permission`"""
23        lookup = getattr(view, "lookup_url_kwarg", None) or getattr(view, "lookup_field", None)
24        if lookup and lookup in view.kwargs:
25            return True
26        # Legacy behaviour:
27        # Allow creation of objects even without explicit permission
28        queryset = self._queryset(view)
29        required_perms = self.get_required_permissions(request.method, queryset.model)
30        if (
31            len(required_perms) == 1
32            and f"{queryset.model._meta.app_label}.add_{queryset.model._meta.model_name}"
33            in required_perms
34            and getattr(view, "rbac_allow_create_without_perm", False)
35        ):
36            return True
37        return super().has_permission(request, view)

Always grant permission for object-specific requests as view permission checking is done by ObjectFilter, and write permission checking is done by has_object_permission

def has_object_permission( self, request: rest_framework.request.Request, view, obj: django.db.models.base.Model) -> bool:
39    def has_object_permission(self, request: Request, view, obj: Model) -> bool:
40        queryset = self._queryset(view)
41        model_cls = queryset.model
42        perms = self.get_required_object_permissions(request.method, model_cls)
43        # Rank global permissions higher than per-object permissions
44        if request.user.has_perms(perms):
45            return True
46        # Allow access for owners if configured
47        if owner_field := getattr(view, "owner_field", None):
48            if getattr(obj, owner_field) == request.user:
49                return True
50        return super().has_object_permission(request, view, obj)

Return True if permission is granted, False otherwise.

def HasPermission(*perm: str) -> type[rest_framework.permissions.BasePermission]:
53def HasPermission(*perm: str) -> type[BasePermission]:
54    """Permission checker for any non-object permissions, returns
55    a BasePermission class that can be used with rest_framework"""
56
57    class checker(BasePermission):
58        def has_permission(self, request: Request, view):
59            return bool(request.user and request.user.has_perms(perm))
60
61    return checker

Permission checker for any non-object permissions, returns a BasePermission class that can be used with rest_framework

def assign_initial_permissions(user, instance: django.db.models.base.Model):
66def assign_initial_permissions(user, instance: Model):
67    # Performance here should not be an issue, but if needed, there are many optimization routes
68    initial_permissions_list = InitialPermissions.objects.filter(role__in=user.all_roles())
69    for initial_permissions in initial_permissions_list:
70        for permission in initial_permissions.permissions.all():
71            if permission.content_type != ContentType.objects.get_for_model(instance):
72                continue
73            LOGGER.debug(
74                "Adding initial permission",
75                initial_permission=permission,
76                subject=initial_permissions.role,
77                object=instance,
78            )
79            assign_perm(permission, initial_permissions.role, instance)