Compare commits
3 Commits
version/20
...
sources/ld
Author | SHA1 | Date | |
---|---|---|---|
3835734ed4 | |||
e0355b13cd | |||
d092093e94 |
40
authentik/sources/ldap/api/property_mappings.py
Normal file
40
authentik/sources/ldap/api/property_mappings.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"""Source API Views"""
|
||||||
|
from django_filters.filters import AllValuesMultipleFilter
|
||||||
|
from django_filters.filterset import FilterSet
|
||||||
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import extend_schema_field
|
||||||
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
|
from authentik.core.api.propertymappings import PropertyMappingSerializer
|
||||||
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
|
from authentik.sources.ldap.models import LDAPPropertyMapping
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPPropertyMappingSerializer(PropertyMappingSerializer):
|
||||||
|
"""LDAP PropertyMapping Serializer"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = LDAPPropertyMapping
|
||||||
|
fields = PropertyMappingSerializer.Meta.fields + [
|
||||||
|
"object_field",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPPropertyMappingFilter(FilterSet):
|
||||||
|
"""Filter for LDAPPropertyMapping"""
|
||||||
|
|
||||||
|
managed = extend_schema_field(OpenApiTypes.STR)(AllValuesMultipleFilter(field_name="managed"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = LDAPPropertyMapping
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPPropertyMappingViewSet(UsedByMixin, ModelViewSet):
|
||||||
|
"""LDAP PropertyMapping Viewset"""
|
||||||
|
|
||||||
|
queryset = LDAPPropertyMapping.objects.all()
|
||||||
|
serializer_class = LDAPPropertyMappingSerializer
|
||||||
|
filterset_class = LDAPPropertyMappingFilter
|
||||||
|
search_fields = ["name"]
|
||||||
|
ordering = ["name"]
|
@ -2,10 +2,7 @@
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django_filters.filters import AllValuesMultipleFilter
|
from drf_spectacular.utils import extend_schema, inline_serializer
|
||||||
from django_filters.filterset import FilterSet
|
|
||||||
from drf_spectacular.types import OpenApiTypes
|
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_field, inline_serializer
|
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.fields import BooleanField, DictField, ListField, SerializerMethodField
|
from rest_framework.fields import BooleanField, DictField, ListField, SerializerMethodField
|
||||||
@ -15,14 +12,13 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
|
|
||||||
from authentik.admin.api.tasks import TaskSerializer
|
from authentik.admin.api.tasks import TaskSerializer
|
||||||
from authentik.core.api.propertymappings import PropertyMappingSerializer
|
|
||||||
from authentik.core.api.sources import SourceSerializer
|
from authentik.core.api.sources import SourceSerializer
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.crypto.models import CertificateKeyPair
|
from authentik.crypto.models import CertificateKeyPair
|
||||||
from authentik.events.monitored_tasks import TaskInfo
|
from authentik.events.monitored_tasks import TaskInfo
|
||||||
from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
from authentik.sources.ldap.models import LDAPSource
|
||||||
from authentik.sources.ldap.tasks import CACHE_KEY_STATUS, SYNC_CLASSES
|
from authentik.sources.ldap.tasks import CACHE_KEY_STATUS, SYNC_CLASSES, ldap_sync_single
|
||||||
|
|
||||||
|
|
||||||
class LDAPSourceSerializer(SourceSerializer):
|
class LDAPSourceSerializer(SourceSerializer):
|
||||||
@ -59,6 +55,20 @@ class LDAPSourceSerializer(SourceSerializer):
|
|||||||
)
|
)
|
||||||
return super().validate(attrs)
|
return super().validate(attrs)
|
||||||
|
|
||||||
|
def create(self, validated_data) -> LDAPSource:
|
||||||
|
# Create both creates the actual model and assigns m2m fields
|
||||||
|
instance: LDAPSource = super().create(validated_data)
|
||||||
|
if not instance.enabled:
|
||||||
|
return instance
|
||||||
|
# Don't sync sources when they don't have any property mappings. This will only happen if:
|
||||||
|
# - the user forgets to set them or
|
||||||
|
# - the source is newly created, this is the first save event
|
||||||
|
# and the mappings are created with an m2m event
|
||||||
|
if not instance.property_mappings.exists() or not instance.property_mappings_group.exists():
|
||||||
|
return instance
|
||||||
|
ldap_sync_single.delay(instance.pk)
|
||||||
|
return instance
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LDAPSource
|
model = LDAPSource
|
||||||
fields = SourceSerializer.Meta.fields + [
|
fields = SourceSerializer.Meta.fields + [
|
||||||
@ -128,14 +138,18 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
request=None,
|
||||||
responses={
|
responses={
|
||||||
200: LDAPSyncStatusSerializer(),
|
200: LDAPSyncStatusSerializer(),
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
@action(methods=["GET"], detail=True, pagination_class=None, filter_backends=[])
|
@action(methods=["GET", "POST"], detail=True, pagination_class=None, filter_backends=[])
|
||||||
def sync_status(self, request: Request, slug: str) -> Response:
|
def sync(self, request: Request, slug: str) -> Response:
|
||||||
"""Get source's sync status"""
|
"""Get source's sync status or start source sync"""
|
||||||
source: LDAPSource = self.get_object()
|
source = self.get_object()
|
||||||
|
if request.method == "POST":
|
||||||
|
# We're not waiting for the sync to finish here as it could take multiple hours
|
||||||
|
ldap_sync_single.delay(source.pk)
|
||||||
tasks = TaskInfo.by_name(f"ldap_sync:{source.slug}:*") or []
|
tasks = TaskInfo.by_name(f"ldap_sync:{source.slug}:*") or []
|
||||||
status = {
|
status = {
|
||||||
"tasks": tasks,
|
"tasks": tasks,
|
||||||
@ -169,33 +183,3 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||||||
obj.pop("raw_dn", None)
|
obj.pop("raw_dn", None)
|
||||||
all_objects[class_name].append(obj)
|
all_objects[class_name].append(obj)
|
||||||
return Response(data=all_objects)
|
return Response(data=all_objects)
|
||||||
|
|
||||||
|
|
||||||
class LDAPPropertyMappingSerializer(PropertyMappingSerializer):
|
|
||||||
"""LDAP PropertyMapping Serializer"""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = LDAPPropertyMapping
|
|
||||||
fields = PropertyMappingSerializer.Meta.fields + [
|
|
||||||
"object_field",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class LDAPPropertyMappingFilter(FilterSet):
|
|
||||||
"""Filter for LDAPPropertyMapping"""
|
|
||||||
|
|
||||||
managed = extend_schema_field(OpenApiTypes.STR)(AllValuesMultipleFilter(field_name="managed"))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = LDAPPropertyMapping
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
class LDAPPropertyMappingViewSet(UsedByMixin, ModelViewSet):
|
|
||||||
"""LDAP PropertyMapping Viewset"""
|
|
||||||
|
|
||||||
queryset = LDAPPropertyMapping.objects.all()
|
|
||||||
serializer_class = LDAPPropertyMappingSerializer
|
|
||||||
filterset_class = LDAPPropertyMappingFilter
|
|
||||||
search_fields = ["name"]
|
|
||||||
ordering = ["name"]
|
|
@ -115,7 +115,7 @@ class LDAPSource(Source):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[Serializer]:
|
def serializer(self) -> type[Serializer]:
|
||||||
from authentik.sources.ldap.api import LDAPSourceSerializer
|
from authentik.sources.ldap.api.sources import LDAPSourceSerializer
|
||||||
|
|
||||||
return LDAPSourceSerializer
|
return LDAPSourceSerializer
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ class LDAPPropertyMapping(PropertyMapping):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[Serializer]:
|
def serializer(self) -> type[Serializer]:
|
||||||
from authentik.sources.ldap.api import LDAPPropertyMappingSerializer
|
from authentik.sources.ldap.api.property_mappings import LDAPPropertyMappingSerializer
|
||||||
|
|
||||||
return LDAPPropertyMappingSerializer
|
return LDAPPropertyMappingSerializer
|
||||||
|
|
||||||
|
@ -14,24 +14,17 @@ from authentik.events.models import Event, EventAction
|
|||||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
from authentik.sources.ldap.models import LDAPSource
|
from authentik.sources.ldap.models import LDAPSource
|
||||||
from authentik.sources.ldap.password import LDAPPasswordChanger
|
from authentik.sources.ldap.password import LDAPPasswordChanger
|
||||||
from authentik.sources.ldap.tasks import ldap_connectivity_check, ldap_sync_single
|
from authentik.sources.ldap.tasks import ldap_connectivity_check
|
||||||
from authentik.stages.prompt.signals import password_validate
|
from authentik.stages.prompt.signals import password_validate
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=LDAPSource)
|
@receiver(post_save, sender=LDAPSource)
|
||||||
def sync_ldap_source_on_save(sender, instance: LDAPSource, **_):
|
def check_ldap_source_on_save(sender, instance: LDAPSource, **_):
|
||||||
"""Ensure that source is synced on save (if enabled)"""
|
"""Check LDAP source's connectivity on save (if enabled)"""
|
||||||
if not instance.enabled:
|
if not instance.enabled:
|
||||||
return
|
return
|
||||||
# Don't sync sources when they don't have any property mappings. This will only happen if:
|
|
||||||
# - the user forgets to set them or
|
|
||||||
# - the source is newly created, this is the first save event
|
|
||||||
# and the mappings are created with an m2m event
|
|
||||||
if not instance.property_mappings.exists() or not instance.property_mappings_group.exists():
|
|
||||||
return
|
|
||||||
ldap_sync_single.delay(instance.pk)
|
|
||||||
ldap_connectivity_check.delay(instance.pk)
|
ldap_connectivity_check.delay(instance.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""API URLs"""
|
"""API URLs"""
|
||||||
from authentik.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
|
from authentik.sources.ldap.api.property_mappings import LDAPPropertyMappingViewSet
|
||||||
|
from authentik.sources.ldap.api.sources import LDAPSourceViewSet
|
||||||
|
|
||||||
api_urlpatterns = [
|
api_urlpatterns = [
|
||||||
("propertymappings/ldap", LDAPPropertyMappingViewSet),
|
("propertymappings/ldap", LDAPPropertyMappingViewSet),
|
||||||
|
39
schema.yml
39
schema.yml
@ -18940,10 +18940,43 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GenericError'
|
$ref: '#/components/schemas/GenericError'
|
||||||
description: ''
|
description: ''
|
||||||
/sources/ldap/{slug}/sync_status/:
|
/sources/ldap/{slug}/sync/:
|
||||||
get:
|
get:
|
||||||
operationId: sources_ldap_sync_status_retrieve
|
operationId: sources_ldap_sync_retrieve
|
||||||
description: Get source's sync status
|
description: Get source's sync status or start source sync
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: slug
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Internal source name, used in URLs.
|
||||||
|
required: true
|
||||||
|
tags:
|
||||||
|
- sources
|
||||||
|
security:
|
||||||
|
- authentik: []
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LDAPSyncStatus'
|
||||||
|
description: ''
|
||||||
|
'400':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
description: ''
|
||||||
|
'403':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GenericError'
|
||||||
|
description: ''
|
||||||
|
post:
|
||||||
|
operationId: sources_ldap_sync_create
|
||||||
|
description: Get source's sync status or start source sync
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- in: path
|
||||||
name: slug
|
name: slug
|
||||||
|
@ -100,7 +100,7 @@ export class LDAPSourceViewPage extends AKElement {
|
|||||||
|
|
||||||
load(): void {
|
load(): void {
|
||||||
new SourcesApi(DEFAULT_CONFIG)
|
new SourcesApi(DEFAULT_CONFIG)
|
||||||
.sourcesLdapSyncStatusRetrieve({
|
.sourcesLdapSyncRetrieve({
|
||||||
slug: this.source.slug,
|
slug: this.source.slug,
|
||||||
})
|
})
|
||||||
.then((state) => {
|
.then((state) => {
|
||||||
@ -198,9 +198,8 @@ export class LDAPSourceViewPage extends AKElement {
|
|||||||
?disabled=${this.syncState?.isRunning}
|
?disabled=${this.syncState?.isRunning}
|
||||||
.apiRequest=${() => {
|
.apiRequest=${() => {
|
||||||
return new SourcesApi(DEFAULT_CONFIG)
|
return new SourcesApi(DEFAULT_CONFIG)
|
||||||
.sourcesLdapPartialUpdate({
|
.sourcesLdapSyncCreate({
|
||||||
slug: this.source?.slug || "",
|
slug: this.source?.slug || "",
|
||||||
patchedLDAPSourceRequest: this.source,
|
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
|
Reference in New Issue
Block a user