
* add api Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add UI Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
117 lines
4.0 KiB
Python
117 lines
4.0 KiB
Python
from celery import Task
|
|
from django.utils.text import slugify
|
|
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
|
from guardian.shortcuts import get_objects_for_user
|
|
from rest_framework.decorators import action
|
|
from rest_framework.fields import BooleanField, CharField, ChoiceField
|
|
from rest_framework.request import Request
|
|
from rest_framework.response import Response
|
|
|
|
from authentik.core.api.utils import ModelSerializer, PassiveSerializer
|
|
from authentik.core.models import Group, User
|
|
from authentik.events.api.tasks import SystemTaskSerializer
|
|
from authentik.events.logs import LogEvent, LogEventSerializer
|
|
from authentik.lib.sync.outgoing.models import OutgoingSyncProvider
|
|
from authentik.lib.utils.reflection import class_to_path
|
|
from authentik.rbac.filters import ObjectFilter
|
|
|
|
|
|
class SyncStatusSerializer(PassiveSerializer):
|
|
"""Provider sync status"""
|
|
|
|
is_running = BooleanField(read_only=True)
|
|
tasks = SystemTaskSerializer(many=True, read_only=True)
|
|
|
|
|
|
class SyncObjectSerializer(PassiveSerializer):
|
|
"""Sync object serializer"""
|
|
|
|
sync_object_model = ChoiceField(
|
|
choices=(
|
|
(class_to_path(User), "user"),
|
|
(class_to_path(Group), "group"),
|
|
)
|
|
)
|
|
sync_object_id = CharField()
|
|
|
|
|
|
class SyncObjectResultSerializer(PassiveSerializer):
|
|
"""Result of a single object sync"""
|
|
|
|
messages = LogEventSerializer(many=True, read_only=True)
|
|
|
|
|
|
class OutgoingSyncProviderStatusMixin:
|
|
"""Common API Endpoints for Outgoing sync providers"""
|
|
|
|
sync_single_task: type[Task] = None
|
|
sync_objects_task: type[Task] = None
|
|
|
|
@extend_schema(
|
|
responses={
|
|
200: SyncStatusSerializer(),
|
|
404: OpenApiResponse(description="Task not found"),
|
|
}
|
|
)
|
|
@action(
|
|
methods=["GET"],
|
|
detail=True,
|
|
pagination_class=None,
|
|
url_path="sync/status",
|
|
filter_backends=[ObjectFilter],
|
|
)
|
|
def sync_status(self, request: Request, pk: int) -> Response:
|
|
"""Get provider's sync status"""
|
|
provider: OutgoingSyncProvider = self.get_object()
|
|
tasks = list(
|
|
get_objects_for_user(request.user, "authentik_events.view_systemtask").filter(
|
|
name=self.sync_single_task.__name__,
|
|
uid=slugify(provider.name),
|
|
)
|
|
)
|
|
with provider.sync_lock as lock_acquired:
|
|
status = {
|
|
"tasks": tasks,
|
|
# If we could not acquire the lock, it means a task is using it, and thus is running
|
|
"is_running": not lock_acquired,
|
|
}
|
|
return Response(SyncStatusSerializer(status).data)
|
|
|
|
@extend_schema(
|
|
request=SyncObjectSerializer,
|
|
responses={200: SyncObjectResultSerializer()},
|
|
)
|
|
@action(
|
|
methods=["POST"],
|
|
detail=True,
|
|
pagination_class=None,
|
|
url_path="sync/object",
|
|
filter_backends=[ObjectFilter],
|
|
)
|
|
def sync_object(self, request: Request, pk: int) -> Response:
|
|
"""Sync/Re-sync a single user/group object"""
|
|
provider: OutgoingSyncProvider = self.get_object()
|
|
params = SyncObjectSerializer(data=request.data)
|
|
params.is_valid(raise_exception=True)
|
|
res: list[LogEvent] = self.sync_objects_task.delay(
|
|
params.validated_data["sync_object_model"],
|
|
page=1,
|
|
provider_pk=provider.pk,
|
|
pk=params.validated_data["sync_object_id"],
|
|
).get()
|
|
return Response(SyncObjectResultSerializer(instance={"messages": res}).data)
|
|
|
|
|
|
class OutgoingSyncConnectionCreateMixin:
|
|
"""Mixin for connection objects that fetches remote data upon creation"""
|
|
|
|
def perform_create(self, serializer: ModelSerializer):
|
|
super().perform_create(serializer)
|
|
try:
|
|
instance = serializer.instance
|
|
client = instance.provider.client_for_model(instance.__class__)
|
|
client.update_single_attribute(instance)
|
|
instance.save()
|
|
except NotImplementedError:
|
|
pass
|