Merge branch 'version-2021.5' into next
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> # Conflicts: # authentik/stages/authenticator_static/api.py # swagger.yaml
This commit is contained in:
commit
1a0f72d0a8
|
@ -43,7 +43,7 @@ def token_from_header(raw_header: bytes) -> Optional[Token]:
|
||||||
return tokens.first()
|
return tokens.first()
|
||||||
|
|
||||||
|
|
||||||
class AuthentikTokenAuthentication(BaseAuthentication):
|
class TokenAuthentication(BaseAuthentication):
|
||||||
"""Token-based authentication using HTTP Bearer authentication"""
|
"""Token-based authentication using HTTP Bearer authentication"""
|
||||||
|
|
||||||
def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
|
def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
|
||||||
|
@ -61,7 +61,7 @@ class AuthentikTokenAuthentication(BaseAuthentication):
|
||||||
class TokenSchema(OpenApiAuthenticationExtension):
|
class TokenSchema(OpenApiAuthenticationExtension):
|
||||||
"""Auth schema"""
|
"""Auth schema"""
|
||||||
|
|
||||||
target_class = AuthentikTokenAuthentication
|
target_class = TokenAuthentication
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
|
|
||||||
def get_security_definition(self, auto_schema):
|
def get_security_definition(self, auto_schema):
|
|
@ -0,0 +1,35 @@
|
||||||
|
"""API Authorization"""
|
||||||
|
from django.db.models import Model
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from rest_framework.filters import BaseFilterBackend
|
||||||
|
from rest_framework.permissions import BasePermission
|
||||||
|
from rest_framework.request import Request
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerFilter(BaseFilterBackend):
|
||||||
|
"""Filter objects by their owner"""
|
||||||
|
|
||||||
|
owner_key = "user"
|
||||||
|
|
||||||
|
def filter_queryset(self, request: Request, queryset: QuerySet, view) -> QuerySet:
|
||||||
|
return queryset.filter(**{self.owner_key: request.user})
|
||||||
|
|
||||||
|
|
||||||
|
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
|
|
@ -5,7 +5,7 @@ from django.test import TestCase
|
||||||
from guardian.shortcuts import get_anonymous_user
|
from guardian.shortcuts import get_anonymous_user
|
||||||
from rest_framework.exceptions import AuthenticationFailed
|
from rest_framework.exceptions import AuthenticationFailed
|
||||||
|
|
||||||
from authentik.api.auth import token_from_header
|
from authentik.api.authentication import token_from_header
|
||||||
from authentik.core.models import Token, TokenIntents
|
from authentik.core.models import Token, TokenIntents
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -161,9 +161,19 @@ router.register("propertymappings/scope", ScopeMappingViewSet)
|
||||||
router.register("authenticators/static", StaticDeviceViewSet)
|
router.register("authenticators/static", StaticDeviceViewSet)
|
||||||
router.register("authenticators/totp", TOTPDeviceViewSet)
|
router.register("authenticators/totp", TOTPDeviceViewSet)
|
||||||
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
|
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
|
||||||
router.register("authenticators/admin/static", StaticAdminDeviceViewSet)
|
router.register(
|
||||||
router.register("authenticators/admin/totp", TOTPAdminDeviceViewSet)
|
"authenticators/admin/static",
|
||||||
router.register("authenticators/admin/webauthn", WebAuthnAdminDeviceViewSet)
|
StaticAdminDeviceViewSet,
|
||||||
|
basename="admin-staticdevice",
|
||||||
|
)
|
||||||
|
router.register(
|
||||||
|
"authenticators/admin/totp", TOTPAdminDeviceViewSet, basename="admin-totpdevice"
|
||||||
|
)
|
||||||
|
router.register(
|
||||||
|
"authenticators/admin/webauthn",
|
||||||
|
WebAuthnAdminDeviceViewSet,
|
||||||
|
basename="admin-webauthndevice",
|
||||||
|
)
|
||||||
|
|
||||||
router.register("stages/all", StageViewSet)
|
router.register("stages/all", StageViewSet)
|
||||||
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
|
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
@extend_schema(responses={200: SessionUserSerializer(many=False)})
|
@extend_schema(responses={200: SessionUserSerializer(many=False)})
|
||||||
|
|
|
@ -4,7 +4,7 @@ from channels.generic.websocket import JsonWebsocketConsumer
|
||||||
from rest_framework.exceptions import AuthenticationFailed
|
from rest_framework.exceptions import AuthenticationFailed
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.api.auth import token_from_header
|
from authentik.api.authentication import token_from_header
|
||||||
from authentik.core.models import User
|
from authentik.core.models import User
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
|
||||||
|
|
|
@ -2,28 +2,6 @@
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import CreateView
|
|
||||||
from guardian.shortcuts import assign_perm
|
|
||||||
|
|
||||||
|
|
||||||
class CreateAssignPermView(CreateView):
|
|
||||||
"""Assign permissions to object after creation"""
|
|
||||||
|
|
||||||
permissions = [
|
|
||||||
"%s.view_%s",
|
|
||||||
"%s.change_%s",
|
|
||||||
"%s.delete_%s",
|
|
||||||
]
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
response = super().form_valid(form)
|
|
||||||
for permission in self.permissions:
|
|
||||||
full_permission = permission % (
|
|
||||||
self.object._meta.app_label,
|
|
||||||
self.object._meta.model_name,
|
|
||||||
)
|
|
||||||
assign_perm(full_permission, self.request.user, self.object)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def bad_request_message(
|
def bad_request_message(
|
||||||
|
|
|
@ -92,7 +92,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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -174,7 +174,7 @@ REST_FRAMEWORK = {
|
||||||
"rest_framework.permissions.DjangoObjectPermissions",
|
"rest_framework.permissions.DjangoObjectPermissions",
|
||||||
),
|
),
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||||
"authentik.api.auth.AuthentikTokenAuthentication",
|
"authentik.api.authentication.TokenAuthentication",
|
||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
),
|
),
|
||||||
"DEFAULT_RENDERER_CLASSES": [
|
"DEFAULT_RENDERER_CLASSES": [
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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, StaticToken
|
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
||||||
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
|
||||||
|
|
||||||
|
@ -47,23 +48,22 @@ class StaticDeviceSerializer(ModelSerializer):
|
||||||
fields = ["name", "token_set", "pk"]
|
fields = ["name", "token_set", "pk"]
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
|
|
|
@ -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)
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
|
@ -1,5 +1,5 @@
|
||||||
"""dummy tests"""
|
"""dummy tests"""
|
||||||
from django.test import Client, TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ class TestDummyStage(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.user = User.objects.create(username="unittest", email="test@beryju.org")
|
self.user = User.objects.create(username="unittest", email="test@beryju.org")
|
||||||
self.client = Client()
|
|
||||||
|
|
||||||
self.flow = Flow.objects.create(
|
self.flow = Flow.objects.create(
|
||||||
name="test-dummy",
|
name="test-dummy",
|
||||||
|
|
124
schema.yml
124
schema.yml
|
@ -444,37 +444,6 @@ paths:
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: '#/components/schemas/ValidationError'
|
||||||
'403':
|
'403':
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
post:
|
|
||||||
operationId: authenticators_static_create
|
|
||||||
description: Viewset for static authenticator devices
|
|
||||||
tags:
|
|
||||||
- authenticators
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/StaticDeviceRequest'
|
|
||||||
application/x-www-form-urlencoded:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/StaticDeviceRequest'
|
|
||||||
multipart/form-data:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/StaticDeviceRequest'
|
|
||||||
required: true
|
|
||||||
security:
|
|
||||||
- authentik: []
|
|
||||||
- cookieAuth: []
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/StaticDevice'
|
|
||||||
description: ''
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/schemas/ValidationError'
|
|
||||||
'403':
|
|
||||||
$ref: '#/components/schemas/GenericError'
|
|
||||||
/api/v2beta/authenticators/static/{id}/:
|
/api/v2beta/authenticators/static/{id}/:
|
||||||
get:
|
get:
|
||||||
operationId: authenticators_static_retrieve
|
operationId: authenticators_static_retrieve
|
||||||
|
@ -648,37 +617,6 @@ paths:
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: '#/components/schemas/ValidationError'
|
||||||
'403':
|
'403':
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
post:
|
|
||||||
operationId: authenticators_totp_create
|
|
||||||
description: Viewset for totp authenticator devices
|
|
||||||
tags:
|
|
||||||
- authenticators
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/TOTPDeviceRequest'
|
|
||||||
application/x-www-form-urlencoded:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/TOTPDeviceRequest'
|
|
||||||
multipart/form-data:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/TOTPDeviceRequest'
|
|
||||||
required: true
|
|
||||||
security:
|
|
||||||
- authentik: []
|
|
||||||
- cookieAuth: []
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/TOTPDevice'
|
|
||||||
description: ''
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/schemas/ValidationError'
|
|
||||||
'403':
|
|
||||||
$ref: '#/components/schemas/GenericError'
|
|
||||||
/api/v2beta/authenticators/totp/{id}/:
|
/api/v2beta/authenticators/totp/{id}/:
|
||||||
get:
|
get:
|
||||||
operationId: authenticators_totp_retrieve
|
operationId: authenticators_totp_retrieve
|
||||||
|
@ -852,37 +790,6 @@ paths:
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: '#/components/schemas/ValidationError'
|
||||||
'403':
|
'403':
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
post:
|
|
||||||
operationId: authenticators_webauthn_create
|
|
||||||
description: Viewset for WebAuthn authenticator devices
|
|
||||||
tags:
|
|
||||||
- authenticators
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/WebAuthnDeviceRequest'
|
|
||||||
application/x-www-form-urlencoded:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/WebAuthnDeviceRequest'
|
|
||||||
multipart/form-data:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/WebAuthnDeviceRequest'
|
|
||||||
required: true
|
|
||||||
security:
|
|
||||||
- authentik: []
|
|
||||||
- cookieAuth: []
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/WebAuthnDevice'
|
|
||||||
description: ''
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/schemas/ValidationError'
|
|
||||||
'403':
|
|
||||||
$ref: '#/components/schemas/GenericError'
|
|
||||||
/api/v2beta/authenticators/webauthn/{id}/:
|
/api/v2beta/authenticators/webauthn/{id}/:
|
||||||
get:
|
get:
|
||||||
operationId: authenticators_webauthn_retrieve
|
operationId: authenticators_webauthn_retrieve
|
||||||
|
@ -10117,37 +10024,6 @@ paths:
|
||||||
$ref: '#/components/schemas/ValidationError'
|
$ref: '#/components/schemas/ValidationError'
|
||||||
'403':
|
'403':
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
post:
|
|
||||||
operationId: sources_oauth_user_connections_create
|
|
||||||
description: Source Viewset
|
|
||||||
tags:
|
|
||||||
- sources
|
|
||||||
requestBody:
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/UserOAuthSourceConnectionRequest'
|
|
||||||
application/x-www-form-urlencoded:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/UserOAuthSourceConnectionRequest'
|
|
||||||
multipart/form-data:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/UserOAuthSourceConnectionRequest'
|
|
||||||
required: true
|
|
||||||
security:
|
|
||||||
- authentik: []
|
|
||||||
- cookieAuth: []
|
|
||||||
responses:
|
|
||||||
'201':
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/UserOAuthSourceConnection'
|
|
||||||
description: ''
|
|
||||||
'400':
|
|
||||||
$ref: '#/components/schemas/ValidationError'
|
|
||||||
'403':
|
|
||||||
$ref: '#/components/schemas/GenericError'
|
|
||||||
/api/v2beta/sources/oauth_user_connections/{id}/:
|
/api/v2beta/sources/oauth_user_connections/{id}/:
|
||||||
get:
|
get:
|
||||||
operationId: sources_oauth_user_connections_retrieve
|
operationId: sources_oauth_user_connections_retrieve
|
||||||
|
|
Reference in New Issue