*: initial migration to openapi v3

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-15 23:57:28 +02:00
parent 34e2bbc41d
commit 1324d03815
38 changed files with 22004 additions and 429 deletions

View file

@ -26,11 +26,11 @@ lint:
pylint authentik tests lifecycle pylint authentik tests lifecycle
gen: gen:
./manage.py generate_swagger -o swagger.yaml -f yaml ./manage.py spectacular --file schema.yml
docker run \ docker run \
--rm -v ${PWD}:/local \ --rm -v ${PWD}:/local \
openapitools/openapi-generator-cli generate \ openapitools/openapi-generator-cli generate \
-i /local/swagger.yaml \ -i /local/schema.yml \
-g typescript-fetch \ -g typescript-fetch \
-o /local/web/api \ -o /local/web/api \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0 --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0

View file

@ -44,6 +44,7 @@ urllib3 = {extras = ["secure"],version = "*"}
uvicorn = {extras = ["standard"],version = "*"} uvicorn = {extras = ["standard"],version = "*"}
webauthn = "*" webauthn = "*"
xmlsec = "*" xmlsec = "*"
drf-spectacular = "*"
[requires] [requires]
python_version = "3.9" python_version = "3.9"

73
Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "8a32708c1c04f8da03c817df973de28c37c97ee773f571ce0b3f3f834e1b7094" "sha256": "f38fd1bd4336a968ebb88f80874dd0d683b79f962551e4aff536322360a90b0d"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -122,18 +122,19 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:3317722a1e9acbfc0d30cdf273d1708c823ceb19309e9cd91cac8a3604762341", "sha256:13cfe0e3ae1bdc7baf4272b1814a7e760fbb508b19d6ac3f472a6bbd64baad61",
"sha256:ee3317fd79b443ef102469fac393a1ffb650ea51ac4fc27464013872c5e1ce31" "sha256:ce08b88a2d7a0ad8edb385f84ea4914296fee6813c66ebf0def956d5278de793"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.17.72" "version": "==1.17.73"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:0fa93a2e2daad5791c63ee526ada66896cc483d04cb2d32bfcadfeb881203453" "sha256:4b4aa58c61d4b125bc6ec1597924b2749e19de8f2c9a374ac087aa2561e71828",
"sha256:69dc0b6fdc0855f5a4f8b1d29c96b9cec44e71054fea0f968e5904d6ccfd4fd9"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.20.72" "version": "==1.20.73"
}, },
"cachetools": { "cachetools": {
"hashes": [ "hashes": [
@ -415,6 +416,14 @@
"index": "pypi", "index": "pypi",
"version": "==5.0.0" "version": "==5.0.0"
}, },
"drf-spectacular": {
"hashes": [
"sha256:4a77c233c99e028b8905cd2cf05388524838ade97b95d5c6e4861e0c3c95af31",
"sha256:d7f64b97e8940b143a0e8d1de20523e8d7d5fe8ec557aec574513282c413b0b3"
],
"index": "pypi",
"version": "==0.16.0"
},
"drf-yasg": { "drf-yasg": {
"hashes": [ "hashes": [
"sha256:8b72e5b1875931a8d11af407be3a9a5ba8776541492947a0df5bafda6b7f8267", "sha256:8b72e5b1875931a8d11af407be3a9a5ba8776541492947a0df5bafda6b7f8267",
@ -614,11 +623,11 @@
}, },
"ldap3": { "ldap3": {
"hashes": [ "hashes": [
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57",
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91", "sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c", "sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056", "sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59" "sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.9" "version": "==2.9"
@ -873,37 +882,37 @@
}, },
"pyasn1": { "pyasn1": {
"hashes": [ "hashes": [
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2" "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
], ],
"version": "==0.4.8" "version": "==0.4.8"
}, },
"pyasn1-modules": { "pyasn1-modules": {
"hashes": [ "hashes": [
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405", "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199" "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
], ],
"version": "==0.2.8" "version": "==0.2.8"
}, },
@ -1602,11 +1611,11 @@
}, },
"gitpython": { "gitpython": {
"hashes": [ "hashes": [
"sha256:2bfcd25e6b81fe431fa3ab1f0975986cfddabf7870a323c183f3afbc9447c0c5", "sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135",
"sha256:37ac36cacf2e2be5e88f0810187c5833e71c1a2a8cf81588f5699d1b70183baa" "sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"
], ],
"markers": "python_version >= '3.5'", "markers": "python_version >= '3.5'",
"version": "==3.1.16" "version": "==3.1.17"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
@ -1752,11 +1761,11 @@
}, },
"pytest-django": { "pytest-django": {
"hashes": [ "hashes": [
"sha256:80f8875226ec4dc0b205f0578072034563879d98d9b1bec143a80b9045716cb0", "sha256:d1c6758a592fb0ef8abaa2fe12dd28858c1dcfc3d466102ffe52aa8934733dca",
"sha256:a51150d8962200250e850c6adcab670779b9c2aa07271471059d1fb92a843fa9" "sha256:f96c4556f4e7b15d987dd1dcc1d1526df81d40c1548d31ce840d597ed2be8c46"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.2.0" "version": "==4.3.0"
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [

View file

@ -1,5 +1,5 @@
"""Meta API""" """Meta API"""
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from rest_framework.fields import CharField from rest_framework.fields import CharField
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request from rest_framework.request import Request
@ -22,7 +22,7 @@ class AppsViewSet(ViewSet):
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: AppSerializer(many=True)}) @extend_schema(responses={200: AppSerializer(many=True)})
def list(self, request: Request) -> Response: def list(self, request: Request) -> Response:
"""List current messages and pass into Serializer""" """List current messages and pass into Serializer"""
data = [] data = []

View file

@ -7,7 +7,7 @@ from django.db.models import Count, ExpressionWrapper, F
from django.db.models.fields import DurationField from django.db.models.fields import DurationField
from django.db.models.functions import ExtractHour from django.db.models.functions import ExtractHour
from django.utils.timezone import now from django.utils.timezone import now
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method from drf_spectacular.utils import extend_schema, extend_schema_field
from rest_framework.fields import IntegerField, SerializerMethodField from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request from rest_framework.request import Request
@ -58,12 +58,12 @@ class LoginMetricsSerializer(PassiveSerializer):
logins_per_1h = SerializerMethodField() logins_per_1h = SerializerMethodField()
logins_failed_per_1h = SerializerMethodField() logins_failed_per_1h = SerializerMethodField()
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_logins_per_1h(self, _): def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours""" """Get successful logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN) return get_events_per_1h(action=EventAction.LOGIN)
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _): def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours""" """Get failed logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN_FAILED) return get_events_per_1h(action=EventAction.LOGIN_FAILED)
@ -74,7 +74,7 @@ class AdministrationMetricsViewSet(ViewSet):
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)}) @extend_schema(responses={200: LoginMetricsSerializer(many=False)})
def list(self, request: Request) -> Response: def list(self, request: Request) -> Response:
"""Login Metrics per 1h""" """Login Metrics per 1h"""
serializer = LoginMetricsSerializer(True) serializer = LoginMetricsSerializer(True)

View file

@ -4,7 +4,7 @@ from importlib import import_module
from django.contrib import messages from django.contrib import messages
from django.http.response import Http404 from django.http.response import Http404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField
from rest_framework.permissions import IsAdminUser from rest_framework.permissions import IsAdminUser
@ -35,9 +35,7 @@ class TaskViewSet(ViewSet):
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]
@swagger_auto_schema( @extend_schema(responses={200: TaskSerializer(many=False), 404: "Task not found"})
responses={200: TaskSerializer(many=False), 404: "Task not found"}
)
# pylint: disable=invalid-name # pylint: disable=invalid-name
def retrieve(self, request: Request, pk=None) -> Response: def retrieve(self, request: Request, pk=None) -> Response:
"""Get a single system task""" """Get a single system task"""
@ -46,13 +44,13 @@ class TaskViewSet(ViewSet):
raise Http404 raise Http404
return Response(TaskSerializer(task, many=False).data) return Response(TaskSerializer(task, many=False).data)
@swagger_auto_schema(responses={200: TaskSerializer(many=True)}) @extend_schema(responses={200: TaskSerializer(many=True)})
def list(self, request: Request) -> Response: def list(self, request: Request) -> Response:
"""List system tasks""" """List system tasks"""
tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name) tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name)
return Response(TaskSerializer(tasks, many=True).data) return Response(TaskSerializer(tasks, many=True).data)
@swagger_auto_schema( @extend_schema(
responses={ responses={
204: "Task retried successfully", 204: "Task retried successfully",
404: "Task not found", 404: "Task not found",

View file

@ -2,7 +2,7 @@
from os import environ from os import environ
from django.core.cache import cache from django.core.cache import cache
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from packaging.version import parse from packaging.version import parse
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from rest_framework.mixins import ListModelMixin from rest_framework.mixins import ListModelMixin
@ -57,7 +57,7 @@ class VersionViewSet(ListModelMixin, GenericViewSet):
def get_queryset(self): # pragma: no cover def get_queryset(self): # pragma: no cover
return None return None
@swagger_auto_schema(responses={200: VersionSerializer(many=False)}) @extend_schema(responses={200: VersionSerializer(many=False)})
def list(self, request: Request) -> Response: def list(self, request: Request) -> Response:
"""Get running and latest version.""" """Get running and latest version."""
return Response(VersionSerializer(True).data) return Response(VersionSerializer(True).data)

View file

@ -7,6 +7,7 @@ from rest_framework.authentication import BaseAuthentication, get_authorization_
from rest_framework.exceptions import AuthenticationFailed from rest_framework.exceptions import AuthenticationFailed
from rest_framework.request import Request from rest_framework.request import Request
from structlog.stdlib import get_logger from structlog.stdlib import get_logger
from drf_spectacular.authentication import OpenApiAuthenticationExtension
from authentik.core.models import Token, TokenIntents, User from authentik.core.models import Token, TokenIntents, User
@ -55,3 +56,15 @@ class AuthentikTokenAuthentication(BaseAuthentication):
return None return None
return (token.user, None) # pragma: no cover return (token.user, None) # pragma: no cover
class TokenSchema(OpenApiAuthenticationExtension):
target_class = AuthentikTokenAuthentication
name = 'authentik'
def get_security_definition(self, auto_schema):
return {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization',
}

View file

@ -1,97 +0,0 @@
"""Swagger Pagination Schema class"""
from typing import OrderedDict
from drf_yasg import openapi
from drf_yasg.inspectors import PaginatorInspector
class PaginationInspector(PaginatorInspector):
"""Swagger Pagination Schema class"""
def get_paginated_response(self, paginator, response_schema):
"""
:param BasePagination paginator: the paginator
:param openapi.Schema response_schema: the response schema that must be paged.
:rtype: openapi.Schema
"""
return openapi.Schema(
type=openapi.TYPE_OBJECT,
properties=OrderedDict(
(
(
"pagination",
openapi.Schema(
type=openapi.TYPE_OBJECT,
properties=OrderedDict(
(
("next", openapi.Schema(type=openapi.TYPE_NUMBER)),
(
"previous",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
("count", openapi.Schema(type=openapi.TYPE_NUMBER)),
(
"current",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"total_pages",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"start_index",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"end_index",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
)
),
required=[
"next",
"previous",
"count",
"current",
"total_pages",
"start_index",
"end_index",
],
),
),
("results", response_schema),
)
),
required=["results", "pagination"],
)
def get_paginator_parameters(self, paginator):
"""
Get the pagination parameters for a single paginator **instance**.
Should return :data:`.NotHandled` if this inspector
does not know how to handle the given `paginator`.
:param BasePagination paginator: the paginator
:rtype: list[openapi.Parameter]
"""
return [
openapi.Parameter(
"page",
openapi.IN_QUERY,
"Page Index",
False,
None,
openapi.TYPE_INTEGER,
),
openapi.Parameter(
"page_size",
openapi.IN_QUERY,
"Page Size",
False,
None,
openapi.TYPE_INTEGER,
),
]

View file

@ -1,102 +0,0 @@
"""Error Response schema, from https://github.com/axnsan12/drf-yasg/issues/224"""
from drf_yasg import openapi
from drf_yasg.inspectors.view import SwaggerAutoSchema
from drf_yasg.utils import force_real_str, is_list_view
from rest_framework import exceptions, status
from rest_framework.settings import api_settings
class ErrorResponseAutoSchema(SwaggerAutoSchema):
"""Inspector which includes an error schema"""
def get_generic_error_schema(self):
"""Get a generic error schema"""
return openapi.Schema(
"Generic API Error",
type=openapi.TYPE_OBJECT,
properties={
"detail": openapi.Schema(
type=openapi.TYPE_STRING, description="Error details"
),
"code": openapi.Schema(
type=openapi.TYPE_STRING, description="Error code"
),
},
required=["detail"],
)
def get_validation_error_schema(self):
"""Get a generic validation error schema"""
return openapi.Schema(
"Validation Error",
type=openapi.TYPE_OBJECT,
properties={
api_settings.NON_FIELD_ERRORS_KEY: openapi.Schema(
description="List of validation errors not related to any field",
type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
),
},
additional_properties=openapi.Schema(
description=(
"A list of error messages for each "
"field that triggered a validation error"
),
type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
),
)
def get_response_serializers(self):
responses = super().get_response_serializers()
definitions = self.components.with_scope(
openapi.SCHEMA_DEFINITIONS
) # type: openapi.ReferenceResolver
definitions.setdefault("GenericError", self.get_generic_error_schema)
definitions.setdefault("ValidationError", self.get_validation_error_schema)
definitions.setdefault("APIException", self.get_generic_error_schema)
if self.get_request_serializer() or self.get_query_serializer():
responses.setdefault(
exceptions.ValidationError.status_code,
openapi.Response(
description=force_real_str(
exceptions.ValidationError.default_detail
),
schema=openapi.SchemaRef(definitions, "ValidationError"),
),
)
security = self.get_security()
if security is None or len(security) > 0:
# Note: 401 error codes are coerced into 403 see
# rest_framework/views.py:433:handle_exception
# This is b/c the API uses token auth which doesn't have WWW-Authenticate header
responses.setdefault(
status.HTTP_403_FORBIDDEN,
openapi.Response(
description="Authentication credentials were invalid, absent or insufficient.",
schema=openapi.SchemaRef(definitions, "GenericError"),
),
)
if not is_list_view(self.path, self.method, self.view):
responses.setdefault(
exceptions.PermissionDenied.status_code,
openapi.Response(
description="Permission denied.",
schema=openapi.SchemaRef(definitions, "APIException"),
),
)
responses.setdefault(
exceptions.NotFound.status_code,
openapi.Response(
description=(
"Object does not exist or caller "
"has insufficient permissions to access it."
),
schema=openapi.SchemaRef(definitions, "APIException"),
),
)
return responses

View file

@ -1,5 +1,5 @@
"""core Configs API""" """core Configs API"""
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from rest_framework.fields import BooleanField, CharField, ListField from rest_framework.fields import BooleanField, CharField, ListField
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.request import Request from rest_framework.request import Request
@ -34,7 +34,7 @@ class ConfigsViewSet(ViewSet):
permission_classes = [AllowAny] permission_classes = [AllowAny]
@swagger_auto_schema(responses={200: ConfigSerializer(many=False)}) @extend_schema(responses={200: ConfigSerializer(many=False)})
def list(self, request: Request) -> Response: def list(self, request: Request) -> Response:
"""Retrive public configuration options""" """Retrive public configuration options"""
config = ConfigSerializer( config = ConfigSerializer(

View file

@ -1,7 +1,6 @@
"""api v2 urls""" """api v2 urls"""
from django.urls import path, re_path from django.urls import path, re_path
from drf_yasg import openapi from drf_spectacular.views import SpectacularAPIView
from drf_yasg.views import get_schema_view
from rest_framework import routers from rest_framework import routers
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
@ -196,17 +195,6 @@ router.register("stages/user_write", UserWriteStageViewSet)
router.register("stages/dummy", DummyStageViewSet) router.register("stages/dummy", DummyStageViewSet)
router.register("policies/dummy", DummyPolicyViewSet) router.register("policies/dummy", DummyPolicyViewSet)
info = openapi.Info(
title="authentik API",
default_version="v2beta",
contact=openapi.Contact(email="hello@beryju.org"),
license=openapi.License(
name="GNU GPLv3",
url="https://github.com/goauthentik/authentik/blob/master/LICENSE",
),
)
SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,))
urlpatterns = ( urlpatterns = (
[ [
path("", SwaggerView.as_view(), name="swagger"), path("", SwaggerView.as_view(), name="swagger"),
@ -218,10 +206,6 @@ urlpatterns = (
FlowExecutorView.as_view(), FlowExecutorView.as_view(),
name="flow-executor", name="flow-executor",
), ),
re_path( path("schema/", SpectacularAPIView.as_view(), name="schema"),
r"^swagger(?P<format>\.json|\.yaml)$",
SchemaView.without_ui(cache_timeout=0),
name="schema-json",
),
] ]
) )

View file

@ -5,8 +5,8 @@ from django.core.cache import cache
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http.response import HttpResponseBadRequest from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from drf_yasg import openapi from drf_spectacular.types import OpenApiTypes
from drf_yasg.utils import no_body, swagger_auto_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField from rest_framework.fields import SerializerMethodField
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
@ -92,7 +92,7 @@ class ApplicationViewSet(ModelViewSet):
applications.append(application) applications.append(application)
return applications return applications
@swagger_auto_schema( @extend_schema(
responses={ responses={
204: "Access granted", 204: "Access granted",
403: "Access denied", 403: "Access denied",
@ -111,12 +111,12 @@ class ApplicationViewSet(ModelViewSet):
return Response(status=204) return Response(status=204)
return Response(status=403) return Response(status=403)
@swagger_auto_schema( @extend_schema(
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="superuser_full_list", name="superuser_full_list",
in_=openapi.IN_QUERY, location=OpenApiParameter.QUERY,
type=openapi.TYPE_BOOLEAN, type=OpenApiTypes.BOOL,
) )
] ]
) )
@ -151,13 +151,13 @@ class ApplicationViewSet(ModelViewSet):
return self.get_paginated_response(serializer.data) return self.get_paginated_response(serializer.data)
@permission_required("authentik_core.change_application") @permission_required("authentik_core.change_application")
@swagger_auto_schema( @extend_schema(
request_body=no_body, request=OpenApiTypes.NONE,
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="file", name="file",
in_=openapi.IN_FORM, location=OpenApiParameter.QUERY, # TODO: In Form
type=openapi.TYPE_FILE, type=OpenApiTypes.BINARY,
required=True, required=True,
) )
], ],
@ -184,7 +184,7 @@ class ApplicationViewSet(ModelViewSet):
@permission_required( @permission_required(
"authentik_core.view_application", ["authentik_events.view_event"] "authentik_core.view_application", ["authentik_events.view_event"]
) )
@swagger_auto_schema(responses={200: CoordinateSerializer(many=True)}) @extend_schema(responses={200: CoordinateSerializer(many=True)})
@action(detail=True, pagination_class=None, filter_backends=[]) @action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=unused-argument # pylint: disable=unused-argument
def metrics(self, request: Request, slug: str): def metrics(self, request: Request, slug: str):

View file

@ -1,8 +1,8 @@
"""PropertyMapping API Views""" """PropertyMapping API Views"""
from json import dumps from json import dumps
from drf_yasg import openapi from drf_spectacular.types import OpenApiTypes
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
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
@ -81,7 +81,7 @@ class PropertyMappingViewSet(
def get_queryset(self): def get_queryset(self):
return PropertyMapping.objects.select_subclasses() return PropertyMapping.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response: def types(self, request: Request) -> Response:
"""Get all creatable property-mapping types""" """Get all creatable property-mapping types"""
@ -100,14 +100,14 @@ class PropertyMappingViewSet(
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)
@permission_required("authentik_core.view_propertymapping") @permission_required("authentik_core.view_propertymapping")
@swagger_auto_schema( @extend_schema(
request_body=PolicyTestSerializer(), request=PolicyTestSerializer(),
responses={200: PropertyMappingTestResultSerializer, 400: "Invalid parameters"}, responses={200: PropertyMappingTestResultSerializer, 400: "Invalid parameters"},
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="format_result", name="format_result",
in_=openapi.IN_QUERY, location=OpenApiParameter.QUERY,
type=openapi.TYPE_BOOLEAN, type=OpenApiTypes.BOOL,
) )
], ],
) )

View file

@ -1,6 +1,6 @@
"""Provider API Views""" """Provider API Views"""
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import 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
@ -66,7 +66,7 @@ class ProviderViewSet(
def get_queryset(self): def get_queryset(self):
return Provider.objects.select_subclasses() return Provider.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response: def types(self, request: Request) -> Response:
"""Get all creatable provider types""" """Get all creatable provider types"""

View file

@ -1,7 +1,7 @@
"""Source API Views""" """Source API Views"""
from typing import Iterable from typing import Iterable
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import 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.request import Request from rest_framework.request import Request
@ -64,7 +64,7 @@ class SourceViewSet(
def get_queryset(self): def get_queryset(self):
return Source.objects.select_subclasses() return Source.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response: def types(self, request: Request) -> Response:
"""Get all creatable source types""" """Get all creatable source types"""
@ -87,7 +87,7 @@ class SourceViewSet(
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: UserSettingSerializer(many=True)}) @extend_schema(responses={200: UserSettingSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def user_settings(self, request: Request) -> Response: def user_settings(self, request: Request) -> Response:
"""Get all sources the user can configure""" """Get all sources the user can configure"""

View file

@ -1,6 +1,6 @@
"""Tokens API Viewset""" """Tokens API Viewset"""
from django.http.response import Http404 from django.http.response import Http404
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField from rest_framework.fields import CharField
from rest_framework.request import Request from rest_framework.request import Request
@ -67,7 +67,7 @@ class TokenViewSet(ModelViewSet):
serializer.save(user=self.request.user, intent=TokenIntents.INTENT_API) serializer.save(user=self.request.user, intent=TokenIntents.INTENT_API)
@permission_required("authentik_core.view_token_key") @permission_required("authentik_core.view_token_key")
@swagger_auto_schema( @extend_schema(
responses={ responses={
200: TokenViewSerializer(many=False), 200: TokenViewSerializer(many=False),
404: "Token not found or expired", 404: "Token not found or expired",

View file

@ -7,7 +7,7 @@ from django.urls import reverse_lazy
from django.utils.http import urlencode from django.utils.http import urlencode
from django_filters.filters import BooleanFilter, CharFilter from django_filters.filters import BooleanFilter, CharFilter
from django_filters.filterset import FilterSet from django_filters.filterset import FilterSet
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method from drf_spectacular.utils import extend_schema, extend_schema_field
from guardian.utils import get_anonymous_user from guardian.utils import get_anonymous_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, JSONField, SerializerMethodField from rest_framework.fields import CharField, JSONField, SerializerMethodField
@ -77,13 +77,13 @@ class UserMetricsSerializer(PassiveSerializer):
logins_failed_per_1h = SerializerMethodField() logins_failed_per_1h = SerializerMethodField()
authorizations_per_1h = SerializerMethodField() authorizations_per_1h = SerializerMethodField()
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_logins_per_1h(self, _): def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours""" """Get successful logins per hour for the last 24 hours"""
user = self.context["user"] user = self.context["user"]
return get_events_per_1h(action=EventAction.LOGIN, user__pk=user.pk) return get_events_per_1h(action=EventAction.LOGIN, user__pk=user.pk)
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _): def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours""" """Get failed logins per hour for the last 24 hours"""
user = self.context["user"] user = self.context["user"]
@ -91,7 +91,7 @@ class UserMetricsSerializer(PassiveSerializer):
action=EventAction.LOGIN_FAILED, context__username=user.username action=EventAction.LOGIN_FAILED, context__username=user.username
) )
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True)) @extend_schema_field(CoordinateSerializer(many=True))
def get_authorizations_per_1h(self, _): def get_authorizations_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours""" """Get failed logins per hour for the last 24 hours"""
user = self.context["user"] user = self.context["user"]
@ -142,7 +142,7 @@ class UserViewSet(ModelViewSet):
def get_queryset(self): def get_queryset(self):
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)}) @extend_schema(responses={200: SessionUserSerializer(many=False)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
# pylint: disable=invalid-name # pylint: disable=invalid-name
def me(self, request: Request) -> Response: def me(self, request: Request) -> Response:
@ -158,7 +158,7 @@ class UserViewSet(ModelViewSet):
return Response(serializer.data) return Response(serializer.data)
@permission_required("authentik_core.view_user", ["authentik_events.view_event"]) @permission_required("authentik_core.view_user", ["authentik_events.view_event"])
@swagger_auto_schema(responses={200: UserMetricsSerializer(many=False)}) @extend_schema(responses={200: UserMetricsSerializer(many=False)})
@action(detail=True, pagination_class=None, filter_backends=[]) @action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=invalid-name, unused-argument # pylint: disable=invalid-name, unused-argument
def metrics(self, request: Request, pk: int) -> Response: def metrics(self, request: Request, pk: int) -> Response:
@ -169,7 +169,7 @@ class UserViewSet(ModelViewSet):
return Response(serializer.data) return Response(serializer.data)
@permission_required("authentik_core.reset_user_password") @permission_required("authentik_core.reset_user_password")
@swagger_auto_schema( @extend_schema(
responses={"200": LinkSerializer(many=False), "404": "No recovery flow found."}, responses={"200": LinkSerializer(many=False), "404": "No recovery flow found."},
) )
@action(detail=True, pagination_class=None, filter_backends=[]) @action(detail=True, pagination_class=None, filter_backends=[])

View file

@ -28,6 +28,9 @@ class PassiveSerializer(Serializer):
) -> Model: # pragma: no cover ) -> Model: # pragma: no cover
return Model() return Model()
class Meta:
model = Model
class MetaNameSerializer(PassiveSerializer): class MetaNameSerializer(PassiveSerializer):
"""Add verbose names to response""" """Add verbose names to response"""

View file

@ -5,8 +5,8 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import load_pem_x509_certificate from cryptography.x509 import load_pem_x509_certificate
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi from drf_spectacular.types import OpenApiTypes
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import ( from rest_framework.fields import (
CharField, CharField,
@ -125,8 +125,8 @@ class CertificateKeyPairViewSet(ModelViewSet):
filterset_class = CertificateKeyPairFilter filterset_class = CertificateKeyPairFilter
@permission_required(None, ["authentik_crypto.add_certificatekeypair"]) @permission_required(None, ["authentik_crypto.add_certificatekeypair"])
@swagger_auto_schema( @extend_schema(
request_body=CertificateGenerationSerializer(), request=CertificateGenerationSerializer(),
responses={200: CertificateKeyPairSerializer, 400: "Bad request"}, responses={200: CertificateKeyPairSerializer, 400: "Bad request"},
) )
@action(detail=False, methods=["POST"]) @action(detail=False, methods=["POST"])
@ -147,12 +147,12 @@ class CertificateKeyPairViewSet(ModelViewSet):
serializer = self.get_serializer(instance) serializer = self.get_serializer(instance)
return Response(serializer.data) return Response(serializer.data)
@swagger_auto_schema( @extend_schema(
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="download", name="download",
in_=openapi.IN_QUERY, location=OpenApiParameter.QUERY,
type=openapi.TYPE_BOOLEAN, type=OpenApiTypes.BOOL,
) )
], ],
responses={200: CertificateDataSerializer(many=False)}, responses={200: CertificateDataSerializer(many=False)},
@ -180,12 +180,12 @@ class CertificateKeyPairViewSet(ModelViewSet):
CertificateDataSerializer({"data": certificate.certificate_data}).data CertificateDataSerializer({"data": certificate.certificate_data}).data
) )
@swagger_auto_schema( @extend_schema(
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="download", name="download",
in_=openapi.IN_QUERY, location=OpenApiParameter.QUERY,
type=openapi.TYPE_BOOLEAN, type=OpenApiTypes.BOOL,
) )
], ],
responses={200: CertificateDataSerializer(many=False)}, responses={200: CertificateDataSerializer(many=False)},

View file

@ -1,8 +1,9 @@
"""Events API Views""" """Events API Views"""
from drf_spectacular.types import OpenApiTypes
import django_filters import django_filters
from django.db.models.aggregates import Count from django.db.models.aggregates import Count
from django.db.models.fields.json import KeyTextTransform from django.db.models.fields.json import KeyTextTransform
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, DictField, IntegerField from rest_framework.fields import CharField, DictField, IntegerField
@ -38,12 +39,6 @@ class EventSerializer(ModelSerializer):
] ]
class EventTopPerUserParams(PassiveSerializer):
"""Query params for top_per_user"""
top_n = IntegerField(default=15)
class EventTopPerUserSerializer(PassiveSerializer): class EventTopPerUserSerializer(PassiveSerializer):
"""Response object of Event's top_per_user""" """Response object of Event's top_per_user"""
@ -111,10 +106,17 @@ class EventViewSet(ReadOnlyModelViewSet):
] ]
filterset_class = EventsFilter filterset_class = EventsFilter
@swagger_auto_schema( @extend_schema(
method="GET", methods=["GET"],
responses={200: EventTopPerUserSerializer(many=True)}, responses={200: EventTopPerUserSerializer(many=True)},
query_serializer=EventTopPerUserParams, parameters=[
OpenApiParameter(
"top_n",
type=OpenApiTypes.INT,
location=OpenApiParameter.QUERY,
required=False,
)
]
) )
@action(detail=False, methods=["GET"]) @action(detail=False, methods=["GET"])
def top_per_user(self, request: Request): def top_per_user(self, request: Request):
@ -134,7 +136,7 @@ class EventViewSet(ReadOnlyModelViewSet):
.order_by("-counted_events")[:top_n] .order_by("-counted_events")[:top_n]
) )
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def actions(self, request: Request) -> Response: def actions(self, request: Request) -> Response:
"""Get all actions""" """Get all actions"""

View file

@ -1,5 +1,6 @@
"""NotificationTransport API Views""" """NotificationTransport API Views"""
from drf_yasg.utils import no_body, swagger_auto_schema from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, ListField, SerializerMethodField from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.request import Request from rest_framework.request import Request
@ -58,12 +59,12 @@ class NotificationTransportViewSet(ModelViewSet):
serializer_class = NotificationTransportSerializer serializer_class = NotificationTransportSerializer
@permission_required("authentik_events.change_notificationtransport") @permission_required("authentik_events.change_notificationtransport")
@swagger_auto_schema( @extend_schema(
responses={ responses={
200: NotificationTransportTestSerializer(many=False), 200: NotificationTransportTestSerializer(many=False),
503: "Failed to test transport", 503: "Failed to test transport",
}, },
request_body=no_body, request=OpenApiTypes.NONE,
) )
@action(detail=True, pagination_class=None, filter_backends=[], methods=["post"]) @action(detail=True, pagination_class=None, filter_backends=[], methods=["post"])
# pylint: disable=invalid-name, unused-argument # pylint: disable=invalid-name, unused-argument

View file

@ -6,8 +6,13 @@ from django.db.models import Model
from django.http.response import HttpResponseBadRequest, JsonResponse from django.http.response import HttpResponseBadRequest, JsonResponse
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from drf_yasg import openapi from drf_spectacular.types import OpenApiTypes
from drf_yasg.utils import no_body, swagger_auto_schema from drf_spectacular.utils import (
OpenApiParameter,
OpenApiResponse,
OpenApiSchemaBase,
extend_schema,
)
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
@ -97,15 +102,15 @@ class FlowViewSet(ModelViewSet):
filterset_fields = ["flow_uuid", "name", "slug", "designation"] filterset_fields = ["flow_uuid", "name", "slug", "designation"]
@permission_required(None, ["authentik_flows.view_flow_cache"]) @permission_required(None, ["authentik_flows.view_flow_cache"])
@swagger_auto_schema(responses={200: CacheSerializer(many=False)}) @extend_schema(responses={200: CacheSerializer(many=False)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def cache_info(self, request: Request) -> Response: def cache_info(self, request: Request) -> Response:
"""Info about cached flows""" """Info about cached flows"""
return Response(data={"count": len(cache.keys("flow_*"))}) return Response(data={"count": len(cache.keys("flow_*"))})
@permission_required(None, ["authentik_flows.clear_flow_cache"]) @permission_required(None, ["authentik_flows.clear_flow_cache"])
@swagger_auto_schema( @extend_schema(
request_body=no_body, request=OpenApiTypes.NONE,
responses={204: "Successfully cleared cache", 400: "Bad request"}, responses={204: "Successfully cleared cache", 400: "Bad request"},
) )
@action(detail=False, methods=["POST"]) @action(detail=False, methods=["POST"])
@ -133,13 +138,13 @@ class FlowViewSet(ModelViewSet):
"authentik_stages_prompt.change_prompt", "authentik_stages_prompt.change_prompt",
], ],
) )
@swagger_auto_schema( @extend_schema(
request_body=no_body, request=OpenApiTypes.NONE,
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="file", name="file",
in_=openapi.IN_FORM, location=OpenApiParameter.QUERY, # TODO: Form
type=openapi.TYPE_FILE, type=OpenApiTypes.BINARY,
required=True, required=True,
) )
], ],
@ -171,10 +176,10 @@ class FlowViewSet(ModelViewSet):
"authentik_stages_prompt.view_prompt", "authentik_stages_prompt.view_prompt",
], ],
) )
@swagger_auto_schema( @extend_schema(
responses={ responses={
"200": openapi.Response( "200": OpenApiResponse(
"File Attachment", schema=openapi.Schema(type=openapi.TYPE_FILE) response=OpenApiParameter("File Attachment", type=OpenApiTypes.BINARY)
), ),
}, },
) )
@ -188,7 +193,7 @@ class FlowViewSet(ModelViewSet):
response["Content-Disposition"] = f'attachment; filename="{flow.slug}.akflow"' response["Content-Disposition"] = f'attachment; filename="{flow.slug}.akflow"'
return response return response
@swagger_auto_schema(responses={200: FlowDiagramSerializer()}) @extend_schema(responses={200: FlowDiagramSerializer()})
@action(detail=True, pagination_class=None, filter_backends=[], methods=["get"]) @action(detail=True, pagination_class=None, filter_backends=[], methods=["get"])
# pylint: disable=unused-argument # pylint: disable=unused-argument
def diagram(self, request: Request, slug: str) -> Response: def diagram(self, request: Request, slug: str) -> Response:
@ -259,13 +264,13 @@ class FlowViewSet(ModelViewSet):
return Response({"diagram": diagram}) return Response({"diagram": diagram})
@permission_required("authentik_flows.change_flow") @permission_required("authentik_flows.change_flow")
@swagger_auto_schema( @extend_schema(
request_body=no_body, request=OpenApiTypes.NONE,
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="file", name="file",
in_=openapi.IN_FORM, location=OpenApiParameter.QUERY, # TODO: Form
type=openapi.TYPE_FILE, type=OpenApiTypes.BINARY,
required=True, required=True,
) )
], ],
@ -289,7 +294,7 @@ class FlowViewSet(ModelViewSet):
app.save() app.save()
return Response({}) return Response({})
@swagger_auto_schema( @extend_schema(
responses={200: LinkSerializer(many=False), 400: "Flow not applicable"}, responses={200: LinkSerializer(many=False), 400: "Flow not applicable"},
) )
@action(detail=True, pagination_class=None, filter_backends=[]) @action(detail=True, pagination_class=None, filter_backends=[])

View file

@ -1,7 +1,7 @@
"""Flow Stage API Views""" """Flow Stage API Views"""
from typing import Iterable from typing import Iterable
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import 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 BooleanField from rest_framework.fields import BooleanField
@ -68,7 +68,7 @@ class StageViewSet(
def get_queryset(self): def get_queryset(self):
return Stage.objects.select_subclasses() return Stage.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response: def types(self, request: Request) -> Response:
"""Get all creatable stage types""" """Get all creatable stage types"""
@ -86,7 +86,7 @@ class StageViewSet(
data = sorted(data, key=lambda x: x["name"]) data = sorted(data, key=lambda x: x["name"])
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: StageUserSettingSerializer(many=True)}) @extend_schema(responses={200: StageUserSettingSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def user_settings(self, request: Request) -> Response: def user_settings(self, request: Request) -> Response:
"""Get all stages the user can configure""" """Get all stages the user can configure"""

View file

@ -10,8 +10,8 @@ from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View from django.views.generic import View
from drf_yasg import openapi from drf_spectacular.types import OpenApiTypes
from drf_yasg.utils import no_body, swagger_auto_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.views import APIView from rest_framework.views import APIView
from sentry_sdk import capture_exception from sentry_sdk import capture_exception
@ -125,19 +125,19 @@ class FlowExecutorView(APIView):
self.current_stage_view.request = request self.current_stage_view.request = request
return super().dispatch(request) return super().dispatch(request)
@swagger_auto_schema( @extend_schema(
responses={ responses={
200: Challenge(), 200: Challenge(),
404: "No Token found", # This error can be raised by the email stage 404: "No Token found", # This error can be raised by the email stage
}, },
request_body=no_body, request=OpenApiTypes.NONE,
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
"query", name="query",
openapi.IN_QUERY, location=OpenApiParameter.QUERY,
required=True, required=True,
description="Querystring as received", description="Querystring as received",
type=openapi.TYPE_STRING, type=OpenApiTypes.STR,
) )
], ],
operation_id="flows_executor_get", operation_id="flows_executor_get",
@ -157,16 +157,16 @@ class FlowExecutorView(APIView):
self._logger.warning(exc) self._logger.warning(exc)
return to_stage_response(request, FlowErrorResponse(request, exc)) return to_stage_response(request, FlowErrorResponse(request, exc))
@swagger_auto_schema( @extend_schema(
responses={200: Challenge()}, responses={200: Challenge()},
request_body=ChallengeResponse(), request=ChallengeResponse(),
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
"query", name="query",
openapi.IN_QUERY, location=OpenApiParameter.QUERY,
required=True, required=True,
description="Querystring as received", description="Querystring as received",
type=openapi.TYPE_STRING, type=OpenApiTypes.STR,
) )
], ],
operation_id="flows_executor_solve", operation_id="flows_executor_solve",

View file

@ -2,7 +2,7 @@
from dataclasses import asdict from dataclasses import asdict
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from kubernetes.client.configuration import Configuration from kubernetes.client.configuration import Configuration
from kubernetes.config.config_exception import ConfigException from kubernetes.config.config_exception import ConfigException
from kubernetes.config.kube_config import load_kube_config_from_dict from kubernetes.config.kube_config import load_kube_config_from_dict
@ -69,7 +69,7 @@ class ServiceConnectionViewSet(
search_fields = ["name"] search_fields = ["name"]
filterset_fields = ["name"] filterset_fields = ["name"]
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response: def types(self, request: Request) -> Response:
"""Get all creatable service connection types""" """Get all creatable service connection types"""
@ -87,7 +87,7 @@ class ServiceConnectionViewSet(
) )
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: ServiceConnectionStateSerializer(many=False)}) @extend_schema(responses={200: ServiceConnectionStateSerializer(many=False)})
@action(detail=True, pagination_class=None, filter_backends=[]) @action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=unused-argument, invalid-name # pylint: disable=unused-argument, invalid-name
def state(self, request: Request, pk: str) -> Response: def state(self, request: Request, pk: str) -> Response:

View file

@ -1,7 +1,7 @@
"""Outpost API Views""" """Outpost API Views"""
from dacite.core import from_dict from dacite.core import from_dict
from dacite.exceptions import DaciteError from dacite.exceptions import DaciteError
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, DateTimeField from rest_framework.fields import BooleanField, CharField, DateTimeField
from rest_framework.request import Request from rest_framework.request import Request
@ -72,7 +72,7 @@ class OutpostViewSet(ModelViewSet):
] ]
ordering = ["name"] ordering = ["name"]
@swagger_auto_schema(responses={200: OutpostHealthSerializer(many=True)}) @extend_schema(responses={200: OutpostHealthSerializer(many=True)})
@action(methods=["GET"], detail=True) @action(methods=["GET"], detail=True)
# pylint: disable=invalid-name, unused-argument # pylint: disable=invalid-name, unused-argument
def health(self, request: Request, pk: int) -> Response: def health(self, request: Request, pk: int) -> Response:
@ -90,7 +90,7 @@ class OutpostViewSet(ModelViewSet):
) )
return Response(OutpostHealthSerializer(states, many=True).data) return Response(OutpostHealthSerializer(states, many=True).data)
@swagger_auto_schema(responses={200: OutpostDefaultConfigSerializer(many=False)}) @extend_schema(responses={200: OutpostDefaultConfigSerializer(many=False)})
@action(detail=False, methods=["GET"]) @action(detail=False, methods=["GET"])
def default_settings(self, request: Request) -> Response: def default_settings(self, request: Request) -> Response:
"""Global default outpost config""" """Global default outpost config"""

View file

@ -1,6 +1,7 @@
"""policy API Views""" """policy API Views"""
from django.core.cache import cache from django.core.cache import cache
from drf_yasg.utils import no_body, swagger_auto_schema from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema
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
@ -96,7 +97,7 @@ class PolicyViewSet(
"bindings", "promptstage_set" "bindings", "promptstage_set"
) )
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response: def types(self, request: Request) -> Response:
"""Get all creatable policy types""" """Get all creatable policy types"""
@ -114,15 +115,15 @@ class PolicyViewSet(
return Response(TypeCreateSerializer(data, many=True).data) return Response(TypeCreateSerializer(data, many=True).data)
@permission_required(None, ["authentik_policies.view_policy_cache"]) @permission_required(None, ["authentik_policies.view_policy_cache"])
@swagger_auto_schema(responses={200: CacheSerializer(many=False)}) @extend_schema(responses={200: CacheSerializer(many=False)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def cache_info(self, request: Request) -> Response: def cache_info(self, request: Request) -> Response:
"""Info about cached policies""" """Info about cached policies"""
return Response(data={"count": len(cache.keys("policy_*"))}) return Response(data={"count": len(cache.keys("policy_*"))})
@permission_required(None, ["authentik_policies.clear_policy_cache"]) @permission_required(None, ["authentik_policies.clear_policy_cache"])
@swagger_auto_schema( @extend_schema(
request_body=no_body, request=OpenApiTypes.NONE,
responses={204: "Successfully cleared cache", 400: "Bad request"}, responses={204: "Successfully cleared cache", 400: "Bad request"},
) )
@action(detail=False, methods=["POST"]) @action(detail=False, methods=["POST"])
@ -137,8 +138,8 @@ class PolicyViewSet(
return Response(status=204) return Response(status=204)
@permission_required("authentik_policies.view_policy") @permission_required("authentik_policies.view_policy")
@swagger_auto_schema( @extend_schema(
request_body=PolicyTestSerializer(), request=PolicyTestSerializer(),
responses={200: PolicyTestResultSerializer(), 400: "Invalid parameters"}, responses={200: PolicyTestResultSerializer(), 400: "Invalid parameters"},
) )
@action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"]) @action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"])

View file

@ -1,7 +1,8 @@
"""OAuth2Provider API Views""" """OAuth2Provider API Views"""
from django.db.models.base import Model
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
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.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
@ -60,6 +61,9 @@ class OAuth2ProviderSetupURLs(PassiveSerializer):
provider_info = ReadOnlyField() provider_info = ReadOnlyField()
logout = ReadOnlyField() logout = ReadOnlyField()
class Meta:
model = Model
class OAuth2ProviderViewSet(ModelViewSet): class OAuth2ProviderViewSet(ModelViewSet):
"""OAuth2Provider Viewset""" """OAuth2Provider Viewset"""
@ -67,9 +71,9 @@ class OAuth2ProviderViewSet(ModelViewSet):
queryset = OAuth2Provider.objects.all() queryset = OAuth2Provider.objects.all()
serializer_class = OAuth2ProviderSerializer serializer_class = OAuth2ProviderSerializer
@swagger_auto_schema( @extend_schema(
responses={ responses={
200: OAuth2ProviderSetupURLs(many=False), 200: OAuth2ProviderSetupURLs,
404: "Provider has no application assigned", 404: "Provider has no application assigned",
} }
) )

View file

@ -1,7 +1,7 @@
"""ProxyProvider API Views""" """ProxyProvider API Views"""
from typing import Any from typing import Any
from drf_yasg.utils import swagger_serializer_method from drf_spectacular.utils import extend_schema_field
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ListField, SerializerMethodField from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
@ -107,7 +107,7 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"forward_auth_mode", "forward_auth_mode",
] ]
@swagger_serializer_method(serializer_or_field=OpenIDConnectConfigurationSerializer) @extend_schema_field(OpenIDConnectConfigurationSerializer)
def get_oidc_configuration(self, obj: ProxyProvider): def get_oidc_configuration(self, obj: ProxyProvider):
"""Embed OpenID Connect provider information""" """Embed OpenID Connect provider information"""
return ProviderInfoView(request=self.context["request"]._request).get_info(obj) return ProviderInfoView(request=self.context["request"]._request).get_info(obj)

View file

@ -5,8 +5,8 @@ from defusedxml.ElementTree import fromstring
from django.http.response import HttpResponse from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi from drf_spectacular.types import OpenApiTypes
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import CharField, FileField, ReadOnlyField from rest_framework.fields import CharField, FileField, ReadOnlyField
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
@ -80,16 +80,16 @@ class SAMLProviderViewSet(ModelViewSet):
queryset = SAMLProvider.objects.all() queryset = SAMLProvider.objects.all()
serializer_class = SAMLProviderSerializer serializer_class = SAMLProviderSerializer
@swagger_auto_schema( @extend_schema(
responses={ responses={
200: SAMLMetadataSerializer(many=False), 200: SAMLMetadataSerializer(many=False),
404: "Provider has no application assigned", 404: "Provider has no application assigned",
}, },
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="download", name="download",
in_=openapi.IN_QUERY, location=OpenApiParameter.QUERY,
type=openapi.TYPE_BOOLEAN, type=OpenApiTypes.BOOL,
) )
], ],
) )
@ -118,8 +118,8 @@ class SAMLProviderViewSet(ModelViewSet):
"authentik_crypto.add_certificatekeypair", "authentik_crypto.add_certificatekeypair",
], ],
) )
@swagger_auto_schema( @extend_schema(
request_body=SAMLProviderImportSerializer(), request=SAMLProviderImportSerializer(),
responses={204: "Successfully imported provider", 400: "Bad request"}, responses={204: "Successfully imported provider", 400: "Bad request"},
) )
@action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,)) @action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,))

View file

@ -130,7 +130,7 @@ INSTALLED_APPS = [
"authentik.stages.user_write", "authentik.stages.user_write",
"rest_framework", "rest_framework",
"django_filters", "django_filters",
"drf_yasg", "drf_spectacular",
"guardian", "guardian",
"django_prometheus", "django_prometheus",
"channels", "channels",
@ -139,14 +139,17 @@ INSTALLED_APPS = [
GUARDIAN_MONKEY_PATCH = False GUARDIAN_MONKEY_PATCH = False
SWAGGER_SETTINGS = { SPECTACULAR_SETTINGS = {
"DEFAULT_AUTO_SCHEMA_CLASS": "authentik.api.schema.ErrorResponseAutoSchema", "TITLE": "authentik",
"DEFAULT_INFO": "authentik.api.v2.urls.info", "DESCRIPTION": "Making authentication simple.",
"DEFAULT_PAGINATOR_INSPECTORS": [ "VERSION": __version__,
"authentik.api.pagination_schema.PaginationInspector", "COMPONENT_SPLIT_REQUEST": True,
], "CONTACT": {
"SECURITY_DEFINITIONS": { "email": "hello@beryju.org",
"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"} },
"LICENSE": {
"name": "GNU GPLv3",
"url": "https://github.com/goauthentik/authentik/blob/master/LICENSE",
}, },
} }
@ -169,6 +172,7 @@ REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": [ "DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer", "rest_framework.renderers.JSONRenderer",
], ],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
} }
CACHES = { CACHES = {

View file

@ -1,7 +1,7 @@
"""Source API Views""" """Source API Views"""
from django.http.response import Http404 from django.http.response import Http404
from django.utils.text import slugify from django.utils.text import slugify
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -48,9 +48,7 @@ class LDAPSourceViewSet(ModelViewSet):
serializer_class = LDAPSourceSerializer serializer_class = LDAPSourceSerializer
lookup_field = "slug" lookup_field = "slug"
@swagger_auto_schema( @extend_schema(responses={200: TaskSerializer(many=False), 404: "Task not found"})
responses={200: TaskSerializer(many=False), 404: "Task not found"}
)
@action(methods=["GET"], detail=True) @action(methods=["GET"], detail=True)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def sync_status(self, request: Request, slug: str) -> Response: def sync_status(self, request: Request, slug: str) -> Response:

View file

@ -1,6 +1,6 @@
"""OAuth Source Serializer""" """OAuth Source Serializer"""
from django.urls.base import reverse_lazy from django.urls.base import reverse_lazy
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method from drf_spectacular.utils import extend_schema, extend_schema_field
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, SerializerMethodField from rest_framework.fields import BooleanField, CharField, SerializerMethodField
from rest_framework.request import Request from rest_framework.request import Request
@ -43,7 +43,7 @@ class OAuthSourceSerializer(SourceSerializer):
type = SerializerMethodField() type = SerializerMethodField()
@swagger_serializer_method(serializer_or_field=SourceTypeSerializer) @extend_schema_field(SourceTypeSerializer)
def get_type(self, instace: OAuthSource) -> SourceTypeSerializer: def get_type(self, instace: OAuthSource) -> SourceTypeSerializer:
"""Get source's type configuration""" """Get source's type configuration"""
return SourceTypeSerializer(instace.type).data return SourceTypeSerializer(instace.type).data
@ -85,7 +85,7 @@ class OAuthSourceViewSet(ModelViewSet):
serializer_class = OAuthSourceSerializer serializer_class = OAuthSourceSerializer
lookup_field = "slug" lookup_field = "slug"
@swagger_auto_schema(responses={200: SourceTypeSerializer(many=True)}) @extend_schema(responses={200: SourceTypeSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def source_types(self, request: Request) -> Response: def source_types(self, request: Request) -> Response:
"""Get all creatable source types""" """Get all creatable source types"""

View file

@ -1,7 +1,7 @@
"""Plex Source Serializer""" """Plex Source Serializer"""
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from drf_yasg import openapi from drf_spectacular.types import OpenApiTypes
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from rest_framework.fields import CharField from rest_framework.fields import CharField
@ -50,18 +50,18 @@ class PlexSourceViewSet(ModelViewSet):
lookup_field = "slug" lookup_field = "slug"
@permission_required(None) @permission_required(None)
@swagger_auto_schema( @extend_schema(
request_body=PlexTokenRedeemSerializer(), request=PlexTokenRedeemSerializer(),
responses={ responses={
200: RedirectChallenge(), 200: RedirectChallenge(),
400: "Token not found", 400: "Token not found",
403: "Access denied", 403: "Access denied",
}, },
manual_parameters=[ parameters=[
openapi.Parameter( OpenApiParameter(
name="slug", name="slug",
in_=openapi.IN_QUERY, location=OpenApiParameter.QUERY,
type=openapi.TYPE_STRING, type=OpenApiTypes.STR,
) )
], ],
) )

View file

@ -1,5 +1,5 @@
"""SAMLSource API Views""" """SAMLSource API Views"""
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -39,7 +39,7 @@ class SAMLSourceViewSet(ModelViewSet):
serializer_class = SAMLSourceSerializer serializer_class = SAMLSourceSerializer
lookup_field = "slug" lookup_field = "slug"
@swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)}) @extend_schema(responses={200: SAMLMetadataSerializer(many=False)})
@action(methods=["GET"], detail=True) @action(methods=["GET"], detail=True)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def metadata(self, request: Request, slug: str) -> Response: def metadata(self, request: Request, slug: str) -> Response:

View file

@ -1,5 +1,5 @@
"""EmailStage API Views""" """EmailStage API Views"""
from drf_yasg.utils import swagger_auto_schema from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -52,7 +52,7 @@ class EmailStageViewSet(ModelViewSet):
queryset = EmailStage.objects.all() queryset = EmailStage.objects.all()
serializer_class = EmailStageSerializer serializer_class = EmailStageSerializer
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)}) @extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[]) @action(detail=False, pagination_class=None, filter_backends=[])
def templates(self, request: Request) -> Response: def templates(self, request: Request) -> Response:
"""Get all available templates, including custom templates""" """Get all available templates, including custom templates"""

21751
schema.yml Normal file

File diff suppressed because it is too large Load diff