api: cleanup owner permissions (#12598)
* api: cleanup owner superuser permissions Signed-off-by: Jens Langhammer <jens@goauthentik.io> * remove remaining owner filters Signed-off-by: Jens Langhammer <jens@goauthentik.io> * re-organise Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix order of filtering Signed-off-by: Jens Langhammer <jens@goauthentik.io> * re-add legacy behaviour for tokens Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix notifications Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -1,67 +0,0 @@
|
|||||||
"""API Authorization"""
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db.models import Model
|
|
||||||
from django.db.models.query import QuerySet
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from rest_framework.authentication import get_authorization_header
|
|
||||||
from rest_framework.filters import BaseFilterBackend
|
|
||||||
from rest_framework.permissions import BasePermission
|
|
||||||
from rest_framework.request import Request
|
|
||||||
|
|
||||||
from authentik.api.authentication import validate_auth
|
|
||||||
from authentik.rbac.filters import ObjectFilter
|
|
||||||
|
|
||||||
|
|
||||||
class OwnerFilter(BaseFilterBackend):
|
|
||||||
"""Filter objects by their owner"""
|
|
||||||
|
|
||||||
owner_key = "user"
|
|
||||||
|
|
||||||
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
|
||||||
if request.user.is_superuser:
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(**{self.owner_key: request.user})
|
|
||||||
|
|
||||||
|
|
||||||
class SecretKeyFilter(DjangoFilterBackend):
|
|
||||||
"""Allow access to all objects when authenticated with secret key as token.
|
|
||||||
|
|
||||||
Replaces both DjangoFilterBackend and ObjectFilter"""
|
|
||||||
|
|
||||||
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
|
||||||
auth_header = get_authorization_header(request)
|
|
||||||
token = validate_auth(auth_header)
|
|
||||||
if token and token == settings.SECRET_KEY:
|
|
||||||
return queryset
|
|
||||||
queryset = ObjectFilter().filter_queryset(request, queryset, view)
|
|
||||||
return super().filter_queryset(request, queryset, view)
|
|
||||||
|
|
||||||
|
|
||||||
class OwnerPermissions(BasePermission):
|
|
||||||
"""Authorize requests by an object's owner matching the requesting user"""
|
|
||||||
|
|
||||||
owner_key = "user"
|
|
||||||
|
|
||||||
def has_permission(self, request: Request, view) -> bool:
|
|
||||||
"""If the user is authenticated, we allow all requests here. For listing, the
|
|
||||||
object-level permissions are done by the filter backend"""
|
|
||||||
return request.user.is_authenticated
|
|
||||||
|
|
||||||
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
|
||||||
"""Check if the object's owner matches the currently logged in user"""
|
|
||||||
if not hasattr(obj, self.owner_key):
|
|
||||||
return False
|
|
||||||
owner = getattr(obj, self.owner_key)
|
|
||||||
if owner != request.user:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class OwnerSuperuserPermissions(OwnerPermissions):
|
|
||||||
"""Similar to OwnerPermissions, except always allow access for superusers"""
|
|
||||||
|
|
||||||
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
|
||||||
if request.user.is_superuser:
|
|
||||||
return True
|
|
||||||
return super().has_object_permission(request, view, obj)
|
|
||||||
@ -14,10 +14,10 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.validators import UniqueValidator
|
from rest_framework.validators import UniqueValidator
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import SecretKeyFilter
|
|
||||||
from authentik.brands.models import Brand
|
from authentik.brands.models import Brand
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||||
|
from authentik.rbac.filters import SecretKeyFilter
|
||||||
from authentik.tenants.utils import get_current_tenant
|
from authentik.tenants.utils import get_current_tenant
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,16 +2,12 @@
|
|||||||
|
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from guardian.utils import get_anonymous_user
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.fields import SerializerMethodField
|
from rest_framework.fields import SerializerMethodField
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
from ua_parser import user_agent_parser
|
from ua_parser import user_agent_parser
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerSuperuserPermissions
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.core.models import AuthenticatedSession
|
from authentik.core.models import AuthenticatedSession
|
||||||
@ -110,11 +106,4 @@ class AuthenticatedSessionViewSet(
|
|||||||
search_fields = ["user__username", "last_ip", "last_user_agent"]
|
search_fields = ["user__username", "last_ip", "last_user_agent"]
|
||||||
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
|
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
|
||||||
ordering = ["user__username"]
|
ordering = ["user__username"]
|
||||||
permission_classes = [OwnerSuperuserPermissions]
|
owner_field = "user"
|
||||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
user = self.request.user if self.request else get_anonymous_user()
|
|
||||||
if user.is_superuser:
|
|
||||||
return super().get_queryset()
|
|
||||||
return super().get_queryset().filter(user=user.pk)
|
|
||||||
|
|||||||
@ -2,19 +2,16 @@
|
|||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField
|
from rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.parsers import MultiPartParser
|
from rest_framework.parsers import MultiPartParser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
|
||||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||||
from authentik.core.api.object_types import TypesMixin
|
from authentik.core.api.object_types import TypesMixin
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
@ -189,11 +186,10 @@ class UserSourceConnectionViewSet(
|
|||||||
|
|
||||||
queryset = UserSourceConnection.objects.all()
|
queryset = UserSourceConnection.objects.all()
|
||||||
serializer_class = UserSourceConnectionSerializer
|
serializer_class = UserSourceConnectionSerializer
|
||||||
permission_classes = [OwnerSuperuserPermissions]
|
|
||||||
filterset_fields = ["user", "source__slug"]
|
filterset_fields = ["user", "source__slug"]
|
||||||
search_fields = ["source__slug"]
|
search_fields = ["source__slug"]
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
ordering = ["source__slug", "pk"]
|
ordering = ["source__slug", "pk"]
|
||||||
|
owner_field = "user"
|
||||||
|
|
||||||
|
|
||||||
class GroupSourceConnectionSerializer(SourceSerializer):
|
class GroupSourceConnectionSerializer(SourceSerializer):
|
||||||
@ -228,8 +224,7 @@ class GroupSourceConnectionViewSet(
|
|||||||
|
|
||||||
queryset = GroupSourceConnection.objects.all()
|
queryset = GroupSourceConnection.objects.all()
|
||||||
serializer_class = GroupSourceConnectionSerializer
|
serializer_class = GroupSourceConnectionSerializer
|
||||||
permission_classes = [OwnerSuperuserPermissions]
|
|
||||||
filterset_fields = ["group", "source__slug"]
|
filterset_fields = ["group", "source__slug"]
|
||||||
search_fields = ["source__slug"]
|
search_fields = ["source__slug"]
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
ordering = ["source__slug", "pk"]
|
ordering = ["source__slug", "pk"]
|
||||||
|
owner_field = "user"
|
||||||
|
|||||||
@ -3,18 +3,15 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
||||||
from guardian.shortcuts import assign_perm, get_anonymous_user
|
from guardian.shortcuts import assign_perm, get_anonymous_user
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerSuperuserPermissions
|
|
||||||
from authentik.blueprints.api import ManagedSerializer
|
from authentik.blueprints.api import ManagedSerializer
|
||||||
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
from authentik.blueprints.v1.importer import SERIALIZER_CONTEXT_BLUEPRINT
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
@ -138,8 +135,8 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
|
|||||||
"managed",
|
"managed",
|
||||||
]
|
]
|
||||||
ordering = ["identifier", "expires"]
|
ordering = ["identifier", "expires"]
|
||||||
permission_classes = [OwnerSuperuserPermissions]
|
owner_field = "user"
|
||||||
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
|
rbac_allow_create_without_perm = True
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user if self.request else get_anonymous_user()
|
user = self.request.user if self.request else get_anonymous_user()
|
||||||
|
|||||||
@ -28,7 +28,6 @@ from rest_framework.validators import UniqueValidator
|
|||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.api.authorization import SecretKeyFilter
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
||||||
from authentik.crypto.apps import MANAGED_KEY
|
from authentik.crypto.apps import MANAGED_KEY
|
||||||
@ -36,7 +35,7 @@ from authentik.crypto.builder import CertificateBuilder, PrivateKeyAlg
|
|||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.events.models import Event, EventAction
|
from authentik.events.models import Event, EventAction
|
||||||
from authentik.rbac.decorators import permission_required
|
from authentik.rbac.decorators import permission_required
|
||||||
from authentik.rbac.filters import ObjectFilter
|
from authentik.rbac.filters import ObjectFilter, SecretKeyFilter
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
"""RAC Provider API Views"""
|
"""RAC Provider API Views"""
|
||||||
|
|
||||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
|
||||||
from authentik.core.api.groups import GroupMemberSerializer
|
from authentik.core.api.groups import GroupMemberSerializer
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
@ -34,12 +31,6 @@ class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ConnectionTokenOwnerFilter(OwnerFilter):
|
|
||||||
"""Owner filter for connection tokens (checks session's user)"""
|
|
||||||
|
|
||||||
owner_key = "session__user"
|
|
||||||
|
|
||||||
|
|
||||||
class ConnectionTokenViewSet(
|
class ConnectionTokenViewSet(
|
||||||
mixins.RetrieveModelMixin,
|
mixins.RetrieveModelMixin,
|
||||||
mixins.UpdateModelMixin,
|
mixins.UpdateModelMixin,
|
||||||
@ -55,10 +46,4 @@ class ConnectionTokenViewSet(
|
|||||||
filterset_fields = ["endpoint", "session__user", "provider"]
|
filterset_fields = ["endpoint", "session__user", "provider"]
|
||||||
search_fields = ["endpoint__name", "provider__name"]
|
search_fields = ["endpoint__name", "provider__name"]
|
||||||
ordering = ["endpoint__name", "provider__name"]
|
ordering = ["endpoint__name", "provider__name"]
|
||||||
permission_classes = [OwnerSuperuserPermissions]
|
owner_field = "session__user"
|
||||||
filter_backends = [
|
|
||||||
ConnectionTokenOwnerFilter,
|
|
||||||
DjangoFilterBackend,
|
|
||||||
OrderingFilter,
|
|
||||||
SearchFilter,
|
|
||||||
]
|
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
"""AuthenticatorEndpointGDTCStage API Views"""
|
"""AuthenticatorEndpointGDTCStage API Views"""
|
||||||
|
|
||||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.permissions import IsAdminUser
|
from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.serializers import ModelSerializer
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.enterprise.api import EnterpriseRequiredMixin
|
from authentik.enterprise.api import EnterpriseRequiredMixin
|
||||||
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
|
from authentik.enterprise.stages.authenticator_endpoint_gdtc.models import (
|
||||||
@ -67,8 +64,7 @@ class EndpointDeviceViewSet(
|
|||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
filterset_fields = ["name"]
|
filterset_fields = ["name"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
permission_classes = [OwnerPermissions]
|
owner_field = "user"
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
|
|
||||||
|
|
||||||
class EndpointAdminDeviceViewSet(ModelViewSet):
|
class EndpointAdminDeviceViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -1,17 +1,15 @@
|
|||||||
"""Notification API Views"""
|
"""Notification API Views"""
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import ReadOnlyField
|
from rest_framework.fields import ReadOnlyField
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.events.api.events import EventSerializer
|
from authentik.events.api.events import EventSerializer
|
||||||
@ -57,8 +55,7 @@ class NotificationViewSet(
|
|||||||
"seen",
|
"seen",
|
||||||
"user",
|
"user",
|
||||||
]
|
]
|
||||||
permission_classes = [OwnerPermissions]
|
owner_field = "user"
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
request=OpenApiTypes.NONE,
|
request=OpenApiTypes.NONE,
|
||||||
@ -66,7 +63,7 @@ class NotificationViewSet(
|
|||||||
204: OpenApiResponse(description="Marked tasks as read successfully."),
|
204: OpenApiResponse(description="Marked tasks as read successfully."),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@action(detail=False, methods=["post"])
|
@action(detail=False, methods=["post"], permission_classes=[IsAuthenticated])
|
||||||
def mark_all_seen(self, request: Request) -> Response:
|
def mark_all_seen(self, request: Request) -> Response:
|
||||||
"""Mark all the user's notifications as seen"""
|
"""Mark all the user's notifications as seen"""
|
||||||
Notification.objects.filter(user=request.user, seen=False).update(seen=True)
|
Notification.objects.filter(user=request.user, seen=False).update(seen=True)
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
"""RBAC API Filter"""
|
"""RBAC API Filter"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework.authentication import get_authorization_header
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.views import APIView
|
||||||
from rest_framework_guardian.filters import ObjectPermissionsFilter
|
from rest_framework_guardian.filters import ObjectPermissionsFilter
|
||||||
|
|
||||||
|
from authentik.api.authentication import validate_auth
|
||||||
from authentik.core.models import UserTypes
|
from authentik.core.models import UserTypes
|
||||||
|
|
||||||
|
|
||||||
@ -12,7 +17,7 @@ class ObjectFilter(ObjectPermissionsFilter):
|
|||||||
"""Object permission filter that grants global permission higher priority than
|
"""Object permission filter that grants global permission higher priority than
|
||||||
per-object permissions"""
|
per-object permissions"""
|
||||||
|
|
||||||
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
def filter_queryset(self, request: Request, queryset: QuerySet, view: APIView) -> QuerySet:
|
||||||
permission = self.perm_format % {
|
permission = self.perm_format % {
|
||||||
"app_label": queryset.model._meta.app_label,
|
"app_label": queryset.model._meta.app_label,
|
||||||
"model_name": queryset.model._meta.model_name,
|
"model_name": queryset.model._meta.model_name,
|
||||||
@ -21,6 +26,9 @@ class ObjectFilter(ObjectPermissionsFilter):
|
|||||||
# per-object permissions
|
# per-object permissions
|
||||||
if request.user.has_perm(permission):
|
if request.user.has_perm(permission):
|
||||||
return queryset
|
return queryset
|
||||||
|
# User does not have permissions, but we have an owner field defined, so filter by that
|
||||||
|
if owner_field := getattr(view, "owner_field", None):
|
||||||
|
return queryset.filter(**{owner_field: request.user})
|
||||||
queryset = super().filter_queryset(request, queryset, view)
|
queryset = super().filter_queryset(request, queryset, view)
|
||||||
# Outposts (which are the only objects using internal service accounts)
|
# Outposts (which are the only objects using internal service accounts)
|
||||||
# except requests to return an empty list when they have no objects
|
# except requests to return an empty list when they have no objects
|
||||||
@ -32,3 +40,17 @@ class ObjectFilter(ObjectPermissionsFilter):
|
|||||||
# and also no object permissions assigned (directly or via role)
|
# and also no object permissions assigned (directly or via role)
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class SecretKeyFilter(DjangoFilterBackend):
|
||||||
|
"""Allow access to all objects when authenticated with secret key as token.
|
||||||
|
|
||||||
|
Replaces both DjangoFilterBackend and ObjectFilter"""
|
||||||
|
|
||||||
|
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
||||||
|
auth_header = get_authorization_header(request)
|
||||||
|
token = validate_auth(auth_header)
|
||||||
|
if token and token == settings.SECRET_KEY:
|
||||||
|
return queryset
|
||||||
|
queryset = ObjectFilter().filter_queryset(request, queryset, view)
|
||||||
|
return super().filter_queryset(request, queryset, view)
|
||||||
|
|||||||
@ -15,6 +15,17 @@ class ObjectPermissions(DjangoObjectPermissions):
|
|||||||
lookup = getattr(view, "lookup_url_kwarg", None) or getattr(view, "lookup_field", None)
|
lookup = getattr(view, "lookup_url_kwarg", None) or getattr(view, "lookup_field", None)
|
||||||
if lookup and lookup in view.kwargs:
|
if lookup and lookup in view.kwargs:
|
||||||
return True
|
return True
|
||||||
|
# Legacy behaviour:
|
||||||
|
# Allow creation of objects even without explicit permission
|
||||||
|
queryset = self._queryset(view)
|
||||||
|
required_perms = self.get_required_permissions(request.method, queryset.model)
|
||||||
|
if (
|
||||||
|
len(required_perms) == 1
|
||||||
|
and f"{queryset.model._meta.app_label}.add_{queryset.model._meta.model_name}"
|
||||||
|
in required_perms
|
||||||
|
and getattr(view, "rbac_allow_create_without_perm", False)
|
||||||
|
):
|
||||||
|
return True
|
||||||
return super().has_permission(request, view)
|
return super().has_permission(request, view)
|
||||||
|
|
||||||
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
def has_object_permission(self, request: Request, view, obj: Model) -> bool:
|
||||||
@ -24,6 +35,10 @@ class ObjectPermissions(DjangoObjectPermissions):
|
|||||||
# Rank global permissions higher than per-object permissions
|
# Rank global permissions higher than per-object permissions
|
||||||
if request.user.has_perms(perms):
|
if request.user.has_perms(perms):
|
||||||
return True
|
return True
|
||||||
|
# Allow access for owners if configured
|
||||||
|
if owner_field := getattr(view, "owner_field", None):
|
||||||
|
if getattr(obj, owner_field) == request.user:
|
||||||
|
return True
|
||||||
return super().has_object_permission(request, view, obj)
|
return super().has_object_permission(request, view, obj)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
"""Kerberos Source Serializer"""
|
"""Kerberos Source Serializer"""
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
|
||||||
from authentik.core.api.sources import (
|
from authentik.core.api.sources import (
|
||||||
GroupSourceConnectionSerializer,
|
GroupSourceConnectionSerializer,
|
||||||
GroupSourceConnectionViewSet,
|
GroupSourceConnectionViewSet,
|
||||||
@ -32,9 +29,8 @@ class UserKerberosSourceConnectionViewSet(UsedByMixin, ModelViewSet):
|
|||||||
serializer_class = UserKerberosSourceConnectionSerializer
|
serializer_class = UserKerberosSourceConnectionSerializer
|
||||||
filterset_fields = ["source__slug"]
|
filterset_fields = ["source__slug"]
|
||||||
search_fields = ["source__slug"]
|
search_fields = ["source__slug"]
|
||||||
permission_classes = [OwnerSuperuserPermissions]
|
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
ordering = ["source__slug"]
|
ordering = ["source__slug"]
|
||||||
|
owner_field = "user"
|
||||||
|
|
||||||
|
|
||||||
class GroupKerberosSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
class GroupKerberosSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||||
|
|||||||
@ -1,20 +1,17 @@
|
|||||||
"""AuthenticatorDuoStage API Views"""
|
"""AuthenticatorDuoStage API Views"""
|
||||||
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
|
||||||
from guardian.shortcuts import get_objects_for_user
|
from guardian.shortcuts import get_objects_for_user
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField, ChoiceField, IntegerField
|
from rest_framework.fields import CharField, ChoiceField, IntegerField
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.flows.api.stages import StageSerializer
|
from authentik.flows.api.stages import StageSerializer
|
||||||
@ -189,8 +186,7 @@ class DuoDeviceViewSet(
|
|||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
filterset_fields = ["name"]
|
filterset_fields = ["name"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
permission_classes = [OwnerPermissions]
|
owner_field = "user"
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
|
|
||||||
|
|
||||||
class DuoAdminDeviceViewSet(ModelViewSet):
|
class DuoAdminDeviceViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
"""AuthenticatorSMSStage API Views"""
|
"""AuthenticatorSMSStage API Views"""
|
||||||
|
|
||||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.flows.api.stages import StageSerializer
|
from authentik.flows.api.stages import StageSerializer
|
||||||
@ -65,11 +62,10 @@ class SMSDeviceViewSet(
|
|||||||
|
|
||||||
queryset = SMSDevice.objects.all()
|
queryset = SMSDevice.objects.all()
|
||||||
serializer_class = SMSDeviceSerializer
|
serializer_class = SMSDeviceSerializer
|
||||||
permission_classes = [OwnerPermissions]
|
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
filterset_fields = ["name"]
|
filterset_fields = ["name"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
owner_field = "user"
|
||||||
|
|
||||||
|
|
||||||
class SMSAdminDeviceViewSet(ModelViewSet):
|
class SMSAdminDeviceViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
"""AuthenticatorStaticStage API Views"""
|
"""AuthenticatorStaticStage API Views"""
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.flows.api.stages import StageSerializer
|
from authentik.flows.api.stages import StageSerializer
|
||||||
@ -69,11 +66,10 @@ class StaticDeviceViewSet(
|
|||||||
|
|
||||||
queryset = StaticDevice.objects.filter(confirmed=True)
|
queryset = StaticDevice.objects.filter(confirmed=True)
|
||||||
serializer_class = StaticDeviceSerializer
|
serializer_class = StaticDeviceSerializer
|
||||||
permission_classes = [OwnerPermissions]
|
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
filterset_fields = ["name"]
|
filterset_fields = ["name"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
owner_field = "user"
|
||||||
|
|
||||||
|
|
||||||
class StaticAdminDeviceViewSet(ModelViewSet):
|
class StaticAdminDeviceViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
"""AuthenticatorTOTPStage API Views"""
|
"""AuthenticatorTOTPStage API Views"""
|
||||||
|
|
||||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.fields import ChoiceField
|
from rest_framework.fields import ChoiceField
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.flows.api.stages import StageSerializer
|
from authentik.flows.api.stages import StageSerializer
|
||||||
@ -61,11 +58,10 @@ class TOTPDeviceViewSet(
|
|||||||
|
|
||||||
queryset = TOTPDevice.objects.filter(confirmed=True)
|
queryset = TOTPDevice.objects.filter(confirmed=True)
|
||||||
serializer_class = TOTPDeviceSerializer
|
serializer_class = TOTPDeviceSerializer
|
||||||
permission_classes = [OwnerPermissions]
|
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
filterset_fields = ["name"]
|
filterset_fields = ["name"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
owner_field = "user"
|
||||||
|
|
||||||
|
|
||||||
class TOTPAdminDeviceViewSet(ModelViewSet):
|
class TOTPAdminDeviceViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
"""AuthenticatorWebAuthnStage API Views"""
|
"""AuthenticatorWebAuthnStage API Views"""
|
||||||
|
|
||||||
from django_filters.rest_framework.backends import DjangoFilterBackend
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerPermissions
|
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import ModelSerializer
|
from authentik.core.api.utils import ModelSerializer
|
||||||
from authentik.stages.authenticator_webauthn.api.device_types import WebAuthnDeviceTypeSerializer
|
from authentik.stages.authenticator_webauthn.api.device_types import WebAuthnDeviceTypeSerializer
|
||||||
@ -40,8 +37,7 @@ class WebAuthnDeviceViewSet(
|
|||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
filterset_fields = ["name"]
|
filterset_fields = ["name"]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
permission_classes = [OwnerPermissions]
|
owner_field = "user"
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
|
|
||||||
|
|
||||||
class WebAuthnAdminDeviceViewSet(ModelViewSet):
|
class WebAuthnAdminDeviceViewSet(ModelViewSet):
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
"""ConsentStage API Views"""
|
"""ConsentStage API Views"""
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from guardian.utils import get_anonymous_user
|
|
||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
|
|
||||||
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
|
|
||||||
from authentik.core.api.applications import ApplicationSerializer
|
from authentik.core.api.applications import ApplicationSerializer
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.users import UserSerializer
|
from authentik.core.api.users import UserSerializer
|
||||||
@ -57,11 +53,4 @@ class UserConsentViewSet(
|
|||||||
filterset_fields = ["user", "application"]
|
filterset_fields = ["user", "application"]
|
||||||
ordering = ["application", "expires"]
|
ordering = ["application", "expires"]
|
||||||
search_fields = ["user__username"]
|
search_fields = ["user__username"]
|
||||||
permission_classes = [OwnerSuperuserPermissions]
|
owner_field = "user"
|
||||||
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
user = self.request.user if self.request else get_anonymous_user()
|
|
||||||
if user.is_superuser:
|
|
||||||
return super().get_queryset()
|
|
||||||
return super().get_queryset().filter(user=user.pk)
|
|
||||||
|
|||||||
@ -8,11 +8,11 @@ from django.http import HttpResponseNotFound
|
|||||||
from django.http.request import urljoin
|
from django.http.request import urljoin
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||||
from rest_framework import permissions
|
|
||||||
from rest_framework.authentication import get_authorization_header
|
from rest_framework.authentication import get_authorization_header
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import CharField, IntegerField
|
from rest_framework.fields import CharField, IntegerField
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
|
from rest_framework.permissions import BasePermission
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import DateTimeField, ModelSerializer
|
from rest_framework.serializers import DateTimeField, ModelSerializer
|
||||||
@ -27,7 +27,7 @@ from authentik.recovery.lib import create_admin_group, create_recovery_token
|
|||||||
from authentik.tenants.models import Tenant
|
from authentik.tenants.models import Tenant
|
||||||
|
|
||||||
|
|
||||||
class TenantApiKeyPermission(permissions.BasePermission):
|
class TenantApiKeyPermission(BasePermission):
|
||||||
"""Authentication based on tenants.api_key"""
|
"""Authentication based on tenants.api_key"""
|
||||||
|
|
||||||
def has_permission(self, request: Request, view: View) -> bool:
|
def has_permission(self, request: Request, view: View) -> bool:
|
||||||
|
|||||||
Reference in New Issue
Block a user