core: add API for user source settings
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
9a27bc8627
commit
07142cab8b
|
@ -1,4 +1,6 @@
|
||||||
"""Source API Views"""
|
"""Source API Views"""
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from drf_yasg2.utils import swagger_auto_schema
|
from drf_yasg2.utils import swagger_auto_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -6,11 +8,16 @@ from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
from rest_framework.serializers import ModelSerializer, SerializerMethodField
|
||||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
|
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
|
||||||
from authentik.core.models import Source
|
from authentik.core.models import Source
|
||||||
|
from authentik.flows.challenge import Challenge
|
||||||
from authentik.lib.templatetags.authentik_utils import verbose_name
|
from authentik.lib.templatetags.authentik_utils import verbose_name
|
||||||
from authentik.lib.utils.reflection import all_subclasses
|
from authentik.lib.utils.reflection import all_subclasses
|
||||||
|
from authentik.policies.engine import PolicyEngine
|
||||||
|
|
||||||
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
class SourceSerializer(ModelSerializer, MetaNameSerializer):
|
||||||
|
@ -63,3 +70,25 @@ class SourceViewSet(ReadOnlyModelViewSet):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return Response(TypeCreateSerializer(data, many=True).data)
|
return Response(TypeCreateSerializer(data, many=True).data)
|
||||||
|
|
||||||
|
@swagger_auto_schema(responses={200: Challenge(many=True)})
|
||||||
|
@action(detail=False)
|
||||||
|
def user_settings(self, request: Request) -> Response:
|
||||||
|
"""Get all sources the user can configure"""
|
||||||
|
_all_sources: Iterable[Source] = Source.objects.filter(
|
||||||
|
enabled=True
|
||||||
|
).select_subclasses()
|
||||||
|
matching_sources: list[Challenge] = []
|
||||||
|
for source in _all_sources:
|
||||||
|
user_settings = source.ui_user_settings
|
||||||
|
if not user_settings:
|
||||||
|
continue
|
||||||
|
policy_engine = PolicyEngine(source, request.user, request)
|
||||||
|
policy_engine.build()
|
||||||
|
if not policy_engine.passing:
|
||||||
|
continue
|
||||||
|
source_settings = source.ui_user_settings
|
||||||
|
if not source_settings.is_valid():
|
||||||
|
LOGGER.warning(source_settings.errors)
|
||||||
|
matching_sources.append(source_settings.validated_data)
|
||||||
|
return Response(matching_sources)
|
||||||
|
|
|
@ -25,6 +25,7 @@ from structlog.stdlib import get_logger
|
||||||
from authentik.core.exceptions import PropertyMappingExpressionException
|
from authentik.core.exceptions import PropertyMappingExpressionException
|
||||||
from authentik.core.signals import password_changed
|
from authentik.core.signals import password_changed
|
||||||
from authentik.core.types import UILoginButton
|
from authentik.core.types import UILoginButton
|
||||||
|
from authentik.flows.challenge import Challenge
|
||||||
from authentik.flows.models import Flow
|
from authentik.flows.models import Flow
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
|
from authentik.lib.models import CreatedUpdatedModel, SerializerModel
|
||||||
|
@ -286,9 +287,9 @@ class Source(SerializerModel, PolicyBindingModel):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ui_user_settings(self) -> Optional[str]:
|
def ui_user_settings(self) -> Optional[Challenge]:
|
||||||
"""Entrypoint to integrate with User settings. Can either return None if no
|
"""Entrypoint to integrate with User settings. Can either return None if no
|
||||||
user settings are available, or a string with the URL to fetch."""
|
user settings are available, or a challenge."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -10,6 +10,7 @@ from rest_framework.serializers import Serializer
|
||||||
|
|
||||||
from authentik.core.models import Source, UserSourceConnection
|
from authentik.core.models import Source, UserSourceConnection
|
||||||
from authentik.core.types import UILoginButton
|
from authentik.core.types import UILoginButton
|
||||||
|
from authentik.flows.challenge import Challenge, ChallengeTypes
|
||||||
|
|
||||||
|
|
||||||
class OAuthSource(Source):
|
class OAuthSource(Source):
|
||||||
|
@ -66,9 +67,15 @@ class OAuthSource(Source):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ui_user_settings(self) -> Optional[str]:
|
def ui_user_settings(self) -> Optional[Challenge]:
|
||||||
view_name = "authentik_sources_oauth:oauth-client-user"
|
view_name = "authentik_sources_oauth:oauth-client-user"
|
||||||
return reverse(view_name, kwargs={"source_slug": self.slug})
|
return Challenge(
|
||||||
|
data={
|
||||||
|
"type": ChallengeTypes.shell.value,
|
||||||
|
"title": self.name,
|
||||||
|
"component": reverse(view_name, kwargs={"source_slug": self.slug}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"OAuth Source {self.name}"
|
return f"OAuth Source {self.name}"
|
||||||
|
|
37
swagger.yaml
37
swagger.yaml
|
@ -6688,6 +6688,43 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- sources
|
- sources
|
||||||
parameters: []
|
parameters: []
|
||||||
|
/sources/all/user_settings/:
|
||||||
|
get:
|
||||||
|
operationId: sources_all_user_settings
|
||||||
|
description: Get all sources the user can configure
|
||||||
|
parameters:
|
||||||
|
- name: ordering
|
||||||
|
in: query
|
||||||
|
description: Which field to use when ordering the results.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: search
|
||||||
|
in: query
|
||||||
|
description: A search term.
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
- name: page
|
||||||
|
in: query
|
||||||
|
description: Page Index
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
- name: page_size
|
||||||
|
in: query
|
||||||
|
description: Page Size
|
||||||
|
required: false
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Challenge that gets sent to the client based on which stage
|
||||||
|
is currently active
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Challenge'
|
||||||
|
tags:
|
||||||
|
- sources
|
||||||
|
parameters: []
|
||||||
/sources/all/{slug}/:
|
/sources/all/{slug}/:
|
||||||
get:
|
get:
|
||||||
operationId: sources_all_read
|
operationId: sources_all_read
|
||||||
|
|
Reference in New Issue