initial api and schema
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
23ce63fb2e
commit
a95c33f1ca
97
authentik/core/api/applications_transactional.py
Normal file
97
authentik/core/api/applications_transactional.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
from django.apps import apps
|
||||||
|
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema, extend_schema_field
|
||||||
|
from rest_framework.exceptions import ValidationError
|
||||||
|
from rest_framework.fields import ChoiceField, DictField
|
||||||
|
from rest_framework.permissions import IsAdminUser
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from yaml import ScalarNode
|
||||||
|
from authentik.blueprints.v1.common import Blueprint, BlueprintEntry, BlueprintEntryDesiredState, KeyOf
|
||||||
|
from authentik.blueprints.v1.importer import Importer
|
||||||
|
|
||||||
|
from authentik.core.api.applications import ApplicationSerializer
|
||||||
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
|
from authentik.core.models import Provider
|
||||||
|
from authentik.lib.utils.reflection import all_subclasses
|
||||||
|
|
||||||
|
|
||||||
|
def get_provider_serializer_mapping():
|
||||||
|
map = {}
|
||||||
|
for model in all_subclasses(Provider):
|
||||||
|
if model._meta.abstract:
|
||||||
|
continue
|
||||||
|
map[f"{model._meta.app_label}.{model._meta.model_name}"] = model().serializer
|
||||||
|
return map
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_field(
|
||||||
|
PolymorphicProxySerializer(
|
||||||
|
component_name="model",
|
||||||
|
serializers=get_provider_serializer_mapping,
|
||||||
|
resource_type_field_name="provider_model",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
class TransactionProviderField(DictField):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionApplicationSerializer(PassiveSerializer):
|
||||||
|
"""Serializer for creating a provider and an application in one transaction"""
|
||||||
|
|
||||||
|
app = ApplicationSerializer()
|
||||||
|
provider_model = ChoiceField(choices=list(get_provider_serializer_mapping().keys()))
|
||||||
|
provider = TransactionProviderField()
|
||||||
|
|
||||||
|
_provider_model: type[Provider] = None
|
||||||
|
|
||||||
|
def validate_provider_model(self, fq_model_name: str) -> str:
|
||||||
|
"""Validate that the model exists and is a provider"""
|
||||||
|
if "." not in fq_model_name:
|
||||||
|
raise ValidationError("Invalid provider model")
|
||||||
|
try:
|
||||||
|
app, model_name = fq_model_name.split(".")
|
||||||
|
model = apps.get_model(app, model_name)
|
||||||
|
if not issubclass(model, Provider):
|
||||||
|
raise ValidationError("Invalid provider model")
|
||||||
|
self._provider_model = model
|
||||||
|
except LookupError:
|
||||||
|
raise ValidationError("Invalid provider model")
|
||||||
|
return fq_model_name
|
||||||
|
|
||||||
|
def validate_provider(self, provider: dict) -> dict:
|
||||||
|
"""Validate provider data"""
|
||||||
|
# ensure the model has been validated
|
||||||
|
self.validate_provider_model(self.initial_data["provider_model"])
|
||||||
|
model_serializer = self._provider_model().serializer(data=provider)
|
||||||
|
model_serializer.is_valid(raise_exception=True)
|
||||||
|
return model_serializer.validated_data
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionalApplicationView(APIView):
|
||||||
|
permission_classes = [IsAdminUser]
|
||||||
|
|
||||||
|
@extend_schema(request=TransactionApplicationSerializer())
|
||||||
|
def put(self, request: Request) -> Response:
|
||||||
|
data = TransactionApplicationSerializer(data=request.data)
|
||||||
|
data.is_valid(raise_exception=True)
|
||||||
|
print(data.validated_data)
|
||||||
|
|
||||||
|
blueprint = Blueprint()
|
||||||
|
blueprint.entries.append(BlueprintEntry(
|
||||||
|
model=data.validated_data["provider_model"],
|
||||||
|
state=BlueprintEntryDesiredState.PRESENT,
|
||||||
|
identifiers={},
|
||||||
|
id="provider",
|
||||||
|
attrs=data.validated_data["provider"],
|
||||||
|
))
|
||||||
|
app_data = data.validated_data["app"]
|
||||||
|
app_data["provider"] = KeyOf(None, ScalarNode(value="provider"))
|
||||||
|
blueprint.entries.append(BlueprintEntry(
|
||||||
|
model="authentik_core.application",
|
||||||
|
state=BlueprintEntryDesiredState.PRESENT,
|
||||||
|
identifiers={},
|
||||||
|
attrs=app_data,
|
||||||
|
))
|
||||||
|
importer = Importer("", {})
|
||||||
|
return Response(status=200)
|
|
@ -8,6 +8,7 @@ from django.views.decorators.csrf import ensure_csrf_cookie
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
|
||||||
from authentik.core.api.applications import ApplicationViewSet
|
from authentik.core.api.applications import ApplicationViewSet
|
||||||
|
from authentik.core.api.applications_transactional import TransactionalApplicationView
|
||||||
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
|
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
|
||||||
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
|
from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
|
||||||
from authentik.core.api.groups import GroupViewSet
|
from authentik.core.api.groups import GroupViewSet
|
||||||
|
@ -70,6 +71,11 @@ urlpatterns = [
|
||||||
api_urlpatterns = [
|
api_urlpatterns = [
|
||||||
("core/authenticated_sessions", AuthenticatedSessionViewSet),
|
("core/authenticated_sessions", AuthenticatedSessionViewSet),
|
||||||
("core/applications", ApplicationViewSet),
|
("core/applications", ApplicationViewSet),
|
||||||
|
path(
|
||||||
|
"core/applications/create_transactional/",
|
||||||
|
TransactionalApplicationView.as_view(),
|
||||||
|
name="core-apps-transactional",
|
||||||
|
),
|
||||||
("core/groups", GroupViewSet),
|
("core/groups", GroupViewSet),
|
||||||
("core/users", UserViewSet),
|
("core/users", UserViewSet),
|
||||||
("core/tokens", TokenViewSet),
|
("core/tokens", TokenViewSet),
|
||||||
|
|
|
@ -73,40 +73,24 @@ QS_QUERY = "query"
|
||||||
|
|
||||||
|
|
||||||
def challenge_types():
|
def challenge_types():
|
||||||
"""This is a workaround for PolymorphicProxySerializer not accepting a callable for
|
"""This function returns a class which is an iterator, which returns the
|
||||||
`serializers`. This function returns a class which is an iterator, which returns the
|
|
||||||
subclasses of Challenge, and Challenge itself."""
|
subclasses of Challenge, and Challenge itself."""
|
||||||
|
|
||||||
class Inner(dict):
|
|
||||||
"""dummy class with custom callback on .items()"""
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
mapping = {}
|
mapping = {}
|
||||||
classes = all_subclasses(Challenge)
|
classes = all_subclasses(Challenge)
|
||||||
classes.remove(WithUserInfoChallenge)
|
classes.remove(WithUserInfoChallenge)
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
mapping[cls().fields["component"].default] = cls
|
mapping[cls().fields["component"].default] = cls
|
||||||
return mapping.items()
|
return mapping
|
||||||
|
|
||||||
return Inner()
|
|
||||||
|
|
||||||
|
|
||||||
def challenge_response_types():
|
def challenge_response_types():
|
||||||
"""This is a workaround for PolymorphicProxySerializer not accepting a callable for
|
"""This function returns a class which is an iterator, which returns the
|
||||||
`serializers`. This function returns a class which is an iterator, which returns the
|
|
||||||
subclasses of Challenge, and Challenge itself."""
|
subclasses of Challenge, and Challenge itself."""
|
||||||
|
|
||||||
class Inner(dict):
|
|
||||||
"""dummy class with custom callback on .items()"""
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
mapping = {}
|
mapping = {}
|
||||||
classes = all_subclasses(ChallengeResponse)
|
classes = all_subclasses(ChallengeResponse)
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
mapping[cls(stage=None).fields["component"].default] = cls
|
mapping[cls(stage=None).fields["component"].default] = cls
|
||||||
return mapping.items()
|
return mapping
|
||||||
|
|
||||||
return Inner()
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidStageError(SentryIgnoredException):
|
class InvalidStageError(SentryIgnoredException):
|
||||||
|
@ -264,7 +248,7 @@ class FlowExecutorView(APIView):
|
||||||
responses={
|
responses={
|
||||||
200: PolymorphicProxySerializer(
|
200: PolymorphicProxySerializer(
|
||||||
component_name="ChallengeTypes",
|
component_name="ChallengeTypes",
|
||||||
serializers=challenge_types(),
|
serializers=challenge_types,
|
||||||
resource_type_field_name="component",
|
resource_type_field_name="component",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -304,13 +288,13 @@ class FlowExecutorView(APIView):
|
||||||
responses={
|
responses={
|
||||||
200: PolymorphicProxySerializer(
|
200: PolymorphicProxySerializer(
|
||||||
component_name="ChallengeTypes",
|
component_name="ChallengeTypes",
|
||||||
serializers=challenge_types(),
|
serializers=challenge_types,
|
||||||
resource_type_field_name="component",
|
resource_type_field_name="component",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
request=PolymorphicProxySerializer(
|
request=PolymorphicProxySerializer(
|
||||||
component_name="FlowChallengeResponse",
|
component_name="FlowChallengeResponse",
|
||||||
serializers=challenge_response_types(),
|
serializers=challenge_response_types,
|
||||||
resource_type_field_name="component",
|
resource_type_field_name="component",
|
||||||
),
|
),
|
||||||
parameters=[
|
parameters=[
|
||||||
|
|
75
schema.yml
75
schema.yml
|
@ -3088,6 +3088,34 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
|
/core/applications/create_transactional/:
|
||||||
|
put:
|
||||||
|
operationId: core_applications_create_transactional_update
|
||||||
|
tags:
|
||||||
|
- core
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TransactionApplicationRequest'
|
||||||
|
required: true
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: No response body
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
/core/authenticated_sessions/:
|
/core/authenticated_sessions/:
|
||||||
get:
|
get:
|
||||||
operationId: core_authenticated_sessions_list
|
operationId: core_authenticated_sessions_list
|
||||||
|
@ -37502,6 +37530,22 @@ components:
|
||||||
description: |-
|
description: |-
|
||||||
* `twilio` - Twilio
|
* `twilio` - Twilio
|
||||||
* `generic` - Generic
|
* `generic` - Generic
|
||||||
|
ProviderModelEnum:
|
||||||
|
enum:
|
||||||
|
- authentik_providers_ldap.ldapprovider
|
||||||
|
- authentik_providers_oauth2.oauth2provider
|
||||||
|
- authentik_providers_proxy.proxyprovider
|
||||||
|
- authentik_providers_radius.radiusprovider
|
||||||
|
- authentik_providers_saml.samlprovider
|
||||||
|
- authentik_providers_scim.scimprovider
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
* `authentik_providers_ldap.ldapprovider` - authentik_providers_ldap.ldapprovider
|
||||||
|
* `authentik_providers_oauth2.oauth2provider` - authentik_providers_oauth2.oauth2provider
|
||||||
|
* `authentik_providers_proxy.proxyprovider` - authentik_providers_proxy.proxyprovider
|
||||||
|
* `authentik_providers_radius.radiusprovider` - authentik_providers_radius.radiusprovider
|
||||||
|
* `authentik_providers_saml.samlprovider` - authentik_providers_saml.samlprovider
|
||||||
|
* `authentik_providers_scim.scimprovider` - authentik_providers_scim.scimprovider
|
||||||
ProviderRequest:
|
ProviderRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Provider Serializer
|
description: Provider Serializer
|
||||||
|
@ -39913,6 +39957,20 @@ components:
|
||||||
readOnly: true
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
- key
|
- key
|
||||||
|
TransactionApplicationRequest:
|
||||||
|
type: object
|
||||||
|
description: Serializer for creating a provider and an application in one transaction
|
||||||
|
properties:
|
||||||
|
app:
|
||||||
|
$ref: '#/components/schemas/ApplicationRequest'
|
||||||
|
provider_model:
|
||||||
|
$ref: '#/components/schemas/ProviderModelEnum'
|
||||||
|
provider:
|
||||||
|
$ref: '#/components/schemas/modelRequest'
|
||||||
|
required:
|
||||||
|
- app
|
||||||
|
- provider
|
||||||
|
- provider_model
|
||||||
TypeCreate:
|
TypeCreate:
|
||||||
type: object
|
type: object
|
||||||
description: Types of an object that can be created
|
description: Types of an object that can be created
|
||||||
|
@ -40840,6 +40898,23 @@ components:
|
||||||
type: integer
|
type: integer
|
||||||
required:
|
required:
|
||||||
- count
|
- count
|
||||||
|
modelRequest:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/LDAPProviderRequest'
|
||||||
|
- $ref: '#/components/schemas/OAuth2ProviderRequest'
|
||||||
|
- $ref: '#/components/schemas/ProxyProviderRequest'
|
||||||
|
- $ref: '#/components/schemas/RadiusProviderRequest'
|
||||||
|
- $ref: '#/components/schemas/SAMLProviderRequest'
|
||||||
|
- $ref: '#/components/schemas/SCIMProviderRequest'
|
||||||
|
discriminator:
|
||||||
|
propertyName: provider_model
|
||||||
|
mapping:
|
||||||
|
authentik_providers_ldap.ldapprovider: '#/components/schemas/LDAPProviderRequest'
|
||||||
|
authentik_providers_oauth2.oauth2provider: '#/components/schemas/OAuth2ProviderRequest'
|
||||||
|
authentik_providers_proxy.proxyprovider: '#/components/schemas/ProxyProviderRequest'
|
||||||
|
authentik_providers_radius.radiusprovider: '#/components/schemas/RadiusProviderRequest'
|
||||||
|
authentik_providers_saml.samlprovider: '#/components/schemas/SAMLProviderRequest'
|
||||||
|
authentik_providers_scim.scimprovider: '#/components/schemas/SCIMProviderRequest'
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
authentik:
|
authentik:
|
||||||
type: apiKey
|
type: apiKey
|
||||||
|
|
Reference in a new issue