authentik.rbac.filters
RBAC API Filter
1"""RBAC API Filter""" 2 3from django.conf import settings 4from django.db.models import QuerySet 5from django_filters.rest_framework import DjangoFilterBackend 6from rest_framework.authentication import get_authorization_header 7from rest_framework.exceptions import PermissionDenied 8from rest_framework.filters import BaseFilterBackend 9from rest_framework.request import Request 10from rest_framework.views import APIView 11 12from authentik.api.authentication import validate_auth 13from authentik.core.models import UserTypes 14 15 16# Inline fork of https://github.com/rpkilby/django-rest-framework-guardian 17# BSD 3-Clause License 18# 19# Copyright (c) 2018, Ryan P Kilby 20# All rights reserved. 21# 22# Redistribution and use in source and binary forms, with or without 23# modification, are permitted provided that the following conditions are met: 24# 25# * Redistributions of source code must retain the above copyright notice, this 26# list of conditions and the following disclaimer. 27# 28# * Redistributions in binary form must reproduce the above copyright notice, 29# this list of conditions and the following disclaimer in the documentation 30# and/or other materials provided with the distribution. 31# 32# * Neither the name of the copyright holder nor the names of its 33# contributors may be used to endorse or promote products derived from 34# this software without specific prior written permission. 35# 36# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 37# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 38# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 39# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 40# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 41# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 42# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 43# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 44# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46class ObjectPermissionsFilter(BaseFilterBackend): 47 """ 48 A filter backend that limits results to those where the requesting user 49 has read object level permissions. 50 """ 51 52 perm_format = "%(app_label)s.view_%(model_name)s" 53 54 def filter_queryset(self, request, queryset, view): 55 # We want to defer this import until runtime, rather than import-time. 56 # See https://github.com/encode/django-rest-framework/issues/4608 57 # (Also see #1624 for why we need to make this import explicitly) 58 from guardian.shortcuts import get_objects_for_user 59 60 user = request.user 61 permission = self.perm_format % { 62 "app_label": queryset.model._meta.app_label, 63 "model_name": queryset.model._meta.model_name, 64 } 65 66 return get_objects_for_user(user, permission, queryset) 67 68 69class ObjectFilter(ObjectPermissionsFilter): 70 """Object permission filter that grants global permission higher priority than 71 per-object permissions""" 72 73 def filter_queryset(self, request: Request, queryset: QuerySet, view: APIView) -> QuerySet: 74 permission = self.perm_format % { 75 "app_label": queryset.model._meta.app_label, 76 "model_name": queryset.model._meta.model_name, 77 } 78 # having the global permission set on a user has higher priority than 79 # per-object permissions 80 if request.user.has_perm(permission): 81 return queryset 82 # User does not have permissions, but we have an owner field defined, so filter by that 83 if owner_field := getattr(view, "owner_field", None): 84 return queryset.filter(**{owner_field: request.user}) 85 queryset = super().filter_queryset(request, queryset, view) 86 # Outposts (which are the only objects using internal service accounts) 87 # except requests to return an empty list when they have no objects 88 # assigned 89 if getattr(request.user, "type", None) == UserTypes.INTERNAL_SERVICE_ACCOUNT: 90 return queryset 91 if not queryset.exists(): 92 # User doesn't have direct permission to all objects 93 # and also no object permissions assigned (directly or via role) 94 raise PermissionDenied() 95 return queryset 96 97 98class SecretKeyFilter(DjangoFilterBackend): 99 """Allow access to all objects when authenticated with secret key as token. 100 101 Replaces both DjangoFilterBackend and ObjectFilter""" 102 103 def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet: 104 auth_header = get_authorization_header(request) 105 token = validate_auth(auth_header) 106 if token and token == settings.SECRET_KEY: 107 return queryset 108 queryset = ObjectFilter().filter_queryset(request, queryset, view) 109 return super().filter_queryset(request, queryset, view)
class
ObjectPermissionsFilter(rest_framework.filters.BaseFilterBackend):
47class ObjectPermissionsFilter(BaseFilterBackend): 48 """ 49 A filter backend that limits results to those where the requesting user 50 has read object level permissions. 51 """ 52 53 perm_format = "%(app_label)s.view_%(model_name)s" 54 55 def filter_queryset(self, request, queryset, view): 56 # We want to defer this import until runtime, rather than import-time. 57 # See https://github.com/encode/django-rest-framework/issues/4608 58 # (Also see #1624 for why we need to make this import explicitly) 59 from guardian.shortcuts import get_objects_for_user 60 61 user = request.user 62 permission = self.perm_format % { 63 "app_label": queryset.model._meta.app_label, 64 "model_name": queryset.model._meta.model_name, 65 } 66 67 return get_objects_for_user(user, permission, queryset)
A filter backend that limits results to those where the requesting user has read object level permissions.
def
filter_queryset(self, request, queryset, view):
55 def filter_queryset(self, request, queryset, view): 56 # We want to defer this import until runtime, rather than import-time. 57 # See https://github.com/encode/django-rest-framework/issues/4608 58 # (Also see #1624 for why we need to make this import explicitly) 59 from guardian.shortcuts import get_objects_for_user 60 61 user = request.user 62 permission = self.perm_format % { 63 "app_label": queryset.model._meta.app_label, 64 "model_name": queryset.model._meta.model_name, 65 } 66 67 return get_objects_for_user(user, permission, queryset)
Return a filtered queryset.
70class ObjectFilter(ObjectPermissionsFilter): 71 """Object permission filter that grants global permission higher priority than 72 per-object permissions""" 73 74 def filter_queryset(self, request: Request, queryset: QuerySet, view: APIView) -> QuerySet: 75 permission = self.perm_format % { 76 "app_label": queryset.model._meta.app_label, 77 "model_name": queryset.model._meta.model_name, 78 } 79 # having the global permission set on a user has higher priority than 80 # per-object permissions 81 if request.user.has_perm(permission): 82 return queryset 83 # User does not have permissions, but we have an owner field defined, so filter by that 84 if owner_field := getattr(view, "owner_field", None): 85 return queryset.filter(**{owner_field: request.user}) 86 queryset = super().filter_queryset(request, queryset, view) 87 # Outposts (which are the only objects using internal service accounts) 88 # except requests to return an empty list when they have no objects 89 # assigned 90 if getattr(request.user, "type", None) == UserTypes.INTERNAL_SERVICE_ACCOUNT: 91 return queryset 92 if not queryset.exists(): 93 # User doesn't have direct permission to all objects 94 # and also no object permissions assigned (directly or via role) 95 raise PermissionDenied() 96 return queryset
Object permission filter that grants global permission higher priority than per-object permissions
def
filter_queryset( self, request: rest_framework.request.Request, queryset: django.db.models.query.QuerySet, view: rest_framework.views.APIView) -> django.db.models.query.QuerySet:
74 def filter_queryset(self, request: Request, queryset: QuerySet, view: APIView) -> QuerySet: 75 permission = self.perm_format % { 76 "app_label": queryset.model._meta.app_label, 77 "model_name": queryset.model._meta.model_name, 78 } 79 # having the global permission set on a user has higher priority than 80 # per-object permissions 81 if request.user.has_perm(permission): 82 return queryset 83 # User does not have permissions, but we have an owner field defined, so filter by that 84 if owner_field := getattr(view, "owner_field", None): 85 return queryset.filter(**{owner_field: request.user}) 86 queryset = super().filter_queryset(request, queryset, view) 87 # Outposts (which are the only objects using internal service accounts) 88 # except requests to return an empty list when they have no objects 89 # assigned 90 if getattr(request.user, "type", None) == UserTypes.INTERNAL_SERVICE_ACCOUNT: 91 return queryset 92 if not queryset.exists(): 93 # User doesn't have direct permission to all objects 94 # and also no object permissions assigned (directly or via role) 95 raise PermissionDenied() 96 return queryset
Return a filtered queryset.
Inherited Members
class
SecretKeyFilter(django_filters.rest_framework.backends.DjangoFilterBackend):
99class SecretKeyFilter(DjangoFilterBackend): 100 """Allow access to all objects when authenticated with secret key as token. 101 102 Replaces both DjangoFilterBackend and ObjectFilter""" 103 104 def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet: 105 auth_header = get_authorization_header(request) 106 token = validate_auth(auth_header) 107 if token and token == settings.SECRET_KEY: 108 return queryset 109 queryset = ObjectFilter().filter_queryset(request, queryset, view) 110 return super().filter_queryset(request, queryset, view)
Allow access to all objects when authenticated with secret key as token.
Replaces both DjangoFilterBackend and ObjectFilter
def
filter_queryset( self, request: rest_framework.request.Request, queryset: django.db.models.query.QuerySet, view) -> django.db.models.query.QuerySet:
104 def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet: 105 auth_header = get_authorization_header(request) 106 token = validate_auth(auth_header) 107 if token and token == settings.SECRET_KEY: 108 return queryset 109 queryset = ObjectFilter().filter_queryset(request, queryset, view) 110 return super().filter_queryset(request, queryset, view)