stages/authenticator_*: fix Permission Error when disabling Authenticator as non-superuser

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-21 21:04:56 +02:00
parent a603f42cc0
commit a265dd54cc
15 changed files with 122 additions and 167 deletions

View File

@ -78,7 +78,7 @@ class PropertyMappingViewSet(
filterset_fields = {"managed": ["isnull"]} filterset_fields = {"managed": ["isnull"]}
ordering = ["name"] ordering = ["name"]
def get_queryset(self): def get_queryset(self): # pragma: no cover
return PropertyMapping.objects.select_subclasses() return PropertyMapping.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})

View File

@ -63,7 +63,7 @@ class ProviderViewSet(
"application__name", "application__name",
] ]
def get_queryset(self): def get_queryset(self): # pragma: no cover
return Provider.objects.select_subclasses() return Provider.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})

View File

@ -61,7 +61,7 @@ class SourceViewSet(
serializer_class = SourceSerializer serializer_class = SourceSerializer
lookup_field = "slug" lookup_field = "slug"
def get_queryset(self): def get_queryset(self): # pragma: no cover
return Source.objects.select_subclasses() return Source.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})

View File

@ -139,7 +139,7 @@ class UserViewSet(ModelViewSet):
search_fields = ["username", "name", "is_active"] search_fields = ["username", "name", "is_active"]
filterset_class = UsersFilter filterset_class = UsersFilter
def get_queryset(self): def get_queryset(self): # pragma: no cover
return User.objects.all().exclude(pk=get_anonymous_user().pk) return User.objects.all().exclude(pk=get_anonymous_user().pk)
@swagger_auto_schema(responses={200: SessionUserSerializer(many=False)}) @swagger_auto_schema(responses={200: SessionUserSerializer(many=False)})

View File

@ -1,12 +1,12 @@
"""Notification API Views""" """Notification API Views"""
from django_filters.rest_framework import DjangoFilterBackend 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 ReadOnlyField from rest_framework.fields import ReadOnlyField
from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.events.api.event import EventSerializer from authentik.events.api.event import EventSerializer
from authentik.events.models import Notification from authentik.events.models import Notification
@ -49,12 +49,5 @@ class NotificationViewSet(
"event", "event",
"seen", "seen",
] ]
filter_backends = [ permission_classes = [OwnerPermissions]
DjangoFilterBackend, filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
OrderingFilter,
SearchFilter,
]
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
return Notification.objects.filter(user=user.pk)

View File

@ -65,7 +65,7 @@ class StageViewSet(
search_fields = ["name"] search_fields = ["name"]
filterset_fields = ["name"] filterset_fields = ["name"]
def get_queryset(self): def get_queryset(self): # pragma: no cover
return Stage.objects.select_subclasses() return Stage.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})

View File

@ -91,7 +91,7 @@ class PolicyViewSet(
} }
search_fields = ["name"] search_fields = ["name"]
def get_queryset(self): def get_queryset(self): # pragma: no cover
return Policy.objects.select_subclasses().prefetch_related( return Policy.objects.select_subclasses().prefetch_related(
"bindings", "promptstage_set" "bindings", "promptstage_set"
) )

View File

@ -1,9 +1,10 @@
"""OAuth Source Serializer""" """OAuth Source Serializer"""
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from guardian.utils import get_anonymous_user from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import GenericViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.sources import SourceSerializer from authentik.core.api.sources import SourceSerializer
from authentik.sources.oauth.models import UserOAuthSourceConnection from authentik.sources.oauth.models import UserOAuthSourceConnection
@ -21,20 +22,17 @@ class UserOAuthSourceConnectionSerializer(SourceSerializer):
] ]
class UserOAuthSourceConnectionViewSet(ModelViewSet): class UserOAuthSourceConnectionViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""Source Viewset""" """Source Viewset"""
queryset = UserOAuthSourceConnection.objects.all() queryset = UserOAuthSourceConnection.objects.all()
serializer_class = UserOAuthSourceConnectionSerializer serializer_class = UserOAuthSourceConnectionSerializer
filterset_fields = ["source__slug"] filterset_fields = ["source__slug"]
filter_backends = [ permission_classes = [OwnerPermissions]
DjangoFilterBackend, filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
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)

View File

@ -1,12 +1,13 @@
"""AuthenticatorStaticStage API Views""" """AuthenticatorStaticStage API Views"""
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from django_otp.plugins.otp_static.models import StaticDevice from django_otp.plugins.otp_static.models import StaticDevice
from guardian.utils import get_anonymous_user from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter 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 ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_static.models import AuthenticatorStaticStage from authentik.stages.authenticator_static.models import AuthenticatorStaticStage
@ -37,23 +38,22 @@ class StaticDeviceSerializer(ModelSerializer):
depth = 2 depth = 2
class StaticDeviceViewSet(ModelViewSet): class StaticDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""Viewset for static authenticator devices""" """Viewset for static authenticator devices"""
queryset = StaticDevice.objects.none() queryset = StaticDevice.objects.all()
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"]
filter_backends = [
DjangoFilterBackend,
OrderingFilter,
SearchFilter,
]
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
return StaticDevice.objects.filter(user=user.pk)
class StaticAdminDeviceViewSet(ReadOnlyModelViewSet): class StaticAdminDeviceViewSet(ReadOnlyModelViewSet):

View File

@ -0,0 +1,20 @@
"""Test Static API"""
from django.urls import reverse
from django_otp.plugins.otp_static.models import StaticDevice
from rest_framework.test import APITestCase
from authentik.core.models import User
class AuthenticatorStaticStage(APITestCase):
"""Test Static API"""
def test_api_delete(self):
"""Test api delete"""
user = User.objects.create(username="foo")
self.client.force_login(user)
dev = StaticDevice.objects.create(user=user)
response = self.client.delete(
reverse("authentik_api:staticdevice-detail", kwargs={"pk": dev.pk})
)
self.assertEqual(response.status_code, 204)

View File

@ -1,12 +1,13 @@
"""AuthenticatorTOTPStage API Views""" """AuthenticatorTOTPStage API Views"""
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework.backends import DjangoFilterBackend
from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_totp.models import TOTPDevice
from guardian.utils import get_anonymous_user from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter 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 ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage from authentik.stages.authenticator_totp.models import AuthenticatorTOTPStage
@ -40,23 +41,22 @@ class TOTPDeviceSerializer(ModelSerializer):
depth = 2 depth = 2
class TOTPDeviceViewSet(ModelViewSet): class TOTPDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""Viewset for totp authenticator devices""" """Viewset for totp authenticator devices"""
queryset = TOTPDevice.objects.none() queryset = TOTPDevice.objects.all()
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"]
filter_backends = [
DjangoFilterBackend,
OrderingFilter,
SearchFilter,
]
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
return TOTPDevice.objects.filter(user=user.pk)
class TOTPAdminDeviceViewSet(ReadOnlyModelViewSet): class TOTPAdminDeviceViewSet(ReadOnlyModelViewSet):

View File

@ -0,0 +1,20 @@
"""Test TOTP API"""
from django.urls import reverse
from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.test import APITestCase
from authentik.core.models import User
class AuthenticatorTOTPStage(APITestCase):
"""Test TOTP API"""
def test_api_delete(self):
"""Test api delete"""
user = User.objects.create(username="foo")
self.client.force_login(user)
dev = TOTPDevice.objects.create(user=user)
response = self.client.delete(
reverse("authentik_api:totpdevice-detail", kwargs={"pk": dev.pk})
)
self.assertEqual(response.status_code, 204)

View File

@ -1,11 +1,12 @@
"""AuthenticateWebAuthnStage API Views""" """AuthenticateWebAuthnStage API Views"""
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework.backends import DjangoFilterBackend
from guardian.utils import get_anonymous_user from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter 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 ModelViewSet, ReadOnlyModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.flows.api.stages import StageSerializer from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_webauthn.models import ( from authentik.stages.authenticator_webauthn.models import (
AuthenticateWebAuthnStage, AuthenticateWebAuthnStage,
@ -39,23 +40,22 @@ class WebAuthnDeviceSerializer(ModelSerializer):
depth = 2 depth = 2
class WebAuthnDeviceViewSet(ModelViewSet): class WebAuthnDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""Viewset for WebAuthn authenticator devices""" """Viewset for WebAuthn authenticator devices"""
queryset = WebAuthnDevice.objects.none() queryset = WebAuthnDevice.objects.all()
serializer_class = WebAuthnDeviceSerializer serializer_class = WebAuthnDeviceSerializer
search_fields = ["name"] search_fields = ["name"]
filterset_fields = ["name"] filterset_fields = ["name"]
ordering = ["name"] ordering = ["name"]
filter_backends = [ permission_classes = [OwnerPermissions]
DjangoFilterBackend, filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
OrderingFilter,
SearchFilter,
]
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()
return WebAuthnDevice.objects.filter(user=user.pk)
class WebAuthnAdminDeviceViewSet(ReadOnlyModelViewSet): class WebAuthnAdminDeviceViewSet(ReadOnlyModelViewSet):

View File

@ -0,0 +1,20 @@
"""Test WebAuthn API"""
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
class AuthenticatorWebAuthnStage(APITestCase):
"""Test WebAuthn API"""
def test_api_delete(self):
"""Test api delete"""
user = User.objects.create(username="foo")
self.client.force_login(user)
dev = WebAuthnDevice.objects.create(user=user)
response = self.client.delete(
reverse("authentik_api:webauthndevice-detail", kwargs={"pk": dev.pk})
)
self.assertEqual(response.status_code, 204)

View File

@ -595,30 +595,6 @@ paths:
$ref: '#/definitions/GenericError' $ref: '#/definitions/GenericError'
tags: tags:
- authenticators - authenticators
post:
operationId: authenticators_static_create
description: Viewset for static authenticator devices
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/StaticDevice'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/StaticDevice'
'400':
description: Invalid input.
schema:
$ref: '#/definitions/ValidationError'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags:
- authenticators
parameters: [] parameters: []
/authenticators/static/{id}/: /authenticators/static/{id}/:
get: get:
@ -797,30 +773,6 @@ paths:
$ref: '#/definitions/GenericError' $ref: '#/definitions/GenericError'
tags: tags:
- authenticators - authenticators
post:
operationId: authenticators_totp_create
description: Viewset for totp authenticator devices
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/TOTPDevice'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/TOTPDevice'
'400':
description: Invalid input.
schema:
$ref: '#/definitions/ValidationError'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags:
- authenticators
parameters: [] parameters: []
/authenticators/totp/{id}/: /authenticators/totp/{id}/:
get: get:
@ -999,30 +951,6 @@ paths:
$ref: '#/definitions/GenericError' $ref: '#/definitions/GenericError'
tags: tags:
- authenticators - authenticators
post:
operationId: authenticators_webauthn_create
description: Viewset for WebAuthn authenticator devices
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/WebAuthnDevice'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/WebAuthnDevice'
'400':
description: Invalid input.
schema:
$ref: '#/definitions/ValidationError'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags:
- authenticators
parameters: [] parameters: []
/authenticators/webauthn/{id}/: /authenticators/webauthn/{id}/:
get: get:
@ -10425,30 +10353,6 @@ paths:
$ref: '#/definitions/GenericError' $ref: '#/definitions/GenericError'
tags: tags:
- sources - sources
post:
operationId: sources_oauth_user_connections_create
description: Source Viewset
parameters:
- name: data
in: body
required: true
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
responses:
'201':
description: ''
schema:
$ref: '#/definitions/UserOAuthSourceConnection'
'400':
description: Invalid input.
schema:
$ref: '#/definitions/ValidationError'
'403':
description: Authentication credentials were invalid, absent or insufficient.
schema:
$ref: '#/definitions/GenericError'
tags:
- sources
parameters: [] parameters: []
/sources/oauth_user_connections/{id}/: /sources/oauth_user_connections/{id}/:
get: get: