core: add API for user source settings

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-18 00:32:40 +01:00
parent 9a27bc8627
commit 07142cab8b
4 changed files with 78 additions and 4 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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}"

View File

@ -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