mirror of
https://github.com/goauthentik/authentik.git
synced 2026-08-30 18:51:39 -07:00
sources/ldap: implement nested group parentship sync (#19069)
* sources/ldap: add sync_group_parentage field * sources/ldap: regenerate schema after adding field * sources/ldap: add group parentage synchronizer * web/admin: add LDAP source toggle for sync_group_parentage * sources/ldap: rename LDAPSource fields to more accurate names * web/admin: update admin UI with new field names Also added better descriptions to all relevant fields, explaining how they interact with each other. * sources/ldap: add unit tests * sources/ldap: fix problem discovered by test * sources/ldap: lint * web/admin: lint * website/docs: update LDAP source docs about lookup fields & membership * sources/ldap: fix renamed attributes * web/admin: make web * sources/ldap: update tests and entries.json fixture * sources/ldap: update migration with current help_text * sources/ldap: fix lint error * sources/ldap: restore membership.py to main, with renamed fields in prep for separating out the synchronizers * sources/ldap: abstract get_group into BaseMembershipLDAPSynchronizer * sources/ldap: add separate ParentshipLDAPSynchronizer * sources/ldap: fix wrong attribute name in extra parents filtering * remove renames * rename `sync_group_parents` to `sync_group_hierarchy` * set `sync_group_hierarchy` default to `False` This is a breaking change if it's `True`. It should be changed eventually, but not in this release. * revert pagination function change I'm not entirely sure why, but this change hangs the test `test_membership_sync_special_chars_in_group_dn`. * add `sync_group_hierarchy` guard to hierarchy sync * simplify hierarchy sync * reword group `parentship` to `hierarchy` * fix lint * remove dead code * move `syncGroupHierarchy` toggle next to similar toggles --------- Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com> Co-authored-by: Simonyi Gergő <gergo@goauthentik.io>
This commit is contained in:
committed by
GitHub
parent
b32135975f
commit
e36c628342
@@ -103,6 +103,7 @@ class LDAPSourceSerializer(SourceSerializer):
|
||||
"lookup_groups_from_user",
|
||||
"delete_not_found_objects",
|
||||
"sync_outgoing_trigger_mode",
|
||||
"sync_group_hierarchy",
|
||||
]
|
||||
extra_kwargs = {"bind_password": {"write_only": True}}
|
||||
|
||||
@@ -141,6 +142,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
"group_property_mappings",
|
||||
"lookup_groups_from_user",
|
||||
"delete_not_found_objects",
|
||||
"sync_group_hierarchy",
|
||||
]
|
||||
search_fields = ["name", "slug"]
|
||||
ordering = ["name"]
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.15 on 2026-07-24 10:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_ldap", "0011_ldapsource_sync_outgoing_trigger_mode"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="ldapsource",
|
||||
name="sync_group_hierarchy",
|
||||
field=models.BooleanField(
|
||||
default=False, help_text="Sync group parentage/hierarchy from LDAP directories."
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -144,6 +144,10 @@ class LDAPSource(IncomingSyncSource):
|
||||
Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT
|
||||
)
|
||||
|
||||
sync_group_hierarchy = models.BooleanField(
|
||||
default=False, help_text=_("Sync group parentage/hierarchy from LDAP directories.")
|
||||
)
|
||||
|
||||
lookup_groups_from_user = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_(
|
||||
|
||||
@@ -17,8 +17,8 @@ from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
|
||||
from authentik.tasks.models import Task
|
||||
|
||||
|
||||
class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
"""Sync LDAP Users and groups into authentik"""
|
||||
class BaseMembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
"""Sync membership of LDAP Users and Groups into authentik"""
|
||||
|
||||
group_cache: dict[str, Group]
|
||||
|
||||
@@ -26,6 +26,47 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
super().__init__(source, task)
|
||||
self.group_cache: dict[str, Group] = {}
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_objects(self, **kwargs) -> Generator:
|
||||
raise NotImplementedError
|
||||
|
||||
def sync(self, page_data: list) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_group(self, group_dict: dict[str, Any]) -> Group | None:
|
||||
"""Check if we fetched the group already, and if not cache it for later"""
|
||||
group_dn = group_dict.get("attributes", {}).get(LDAP_DISTINGUISHED_NAME, [])
|
||||
group_uniq = group_dict.get("attributes", {}).get(self._source.object_uniqueness_field, [])
|
||||
# group_uniq might be a single string or an array with (hopefully) a single string
|
||||
if isinstance(group_uniq, list):
|
||||
if len(group_uniq) < 1:
|
||||
self._task.info(
|
||||
f"Group does not have a uniqueness attribute: '{group_dn}'",
|
||||
group=group_dn,
|
||||
)
|
||||
return None
|
||||
group_uniq = group_uniq[0]
|
||||
if group_uniq not in self.group_cache:
|
||||
groups = GroupLDAPSourceConnection.objects.filter(identifier=group_uniq).select_related(
|
||||
"group"
|
||||
)
|
||||
if not groups.exists():
|
||||
if self._source.sync_groups:
|
||||
self._task.info(
|
||||
f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'",
|
||||
group=group_dn,
|
||||
)
|
||||
return None
|
||||
self.group_cache[group_uniq] = groups.first().group
|
||||
return self.group_cache[group_uniq]
|
||||
|
||||
|
||||
class MembershipLDAPSynchronizer(BaseMembershipLDAPSynchronizer):
|
||||
"""Sync membership of LDAP Users into authentik"""
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
return "membership"
|
||||
@@ -94,29 +135,84 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
self._logger.debug("Successfully updated group membership")
|
||||
return membership_count
|
||||
|
||||
def get_group(self, group_dict: dict[str, Any]) -> Group | None:
|
||||
"""Check if we fetched the group already, and if not cache it for later"""
|
||||
group_dn = group_dict.get("attributes", {}).get(LDAP_DISTINGUISHED_NAME, [])
|
||||
group_uniq = group_dict.get("attributes", {}).get(self._source.object_uniqueness_field, [])
|
||||
# group_uniq might be a single string or an array with (hopefully) a single string
|
||||
if isinstance(group_uniq, list):
|
||||
if len(group_uniq) < 1:
|
||||
self._task.info(
|
||||
f"Group does not have a uniqueness attribute: '{group_dn}'",
|
||||
group=group_dn,
|
||||
|
||||
class GroupHierarchyLDAPSynchronizer(BaseMembershipLDAPSynchronizer):
|
||||
"""Sync hierarchy of LDAP Groups into authentik"""
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
return "group hierarchy"
|
||||
|
||||
def get_objects(self, **kwargs) -> Generator:
|
||||
if not self._source.sync_groups:
|
||||
self._task.info("Group syncing is disabled for this Source")
|
||||
return iter(())
|
||||
if not self._source.sync_group_hierarchy:
|
||||
self._task.info("Group hierarchy syncing is disabled for this Source")
|
||||
return iter(())
|
||||
|
||||
attributes = [
|
||||
self._source.group_membership_field,
|
||||
self._source.user_membership_attribute,
|
||||
self._source.object_uniqueness_field,
|
||||
LDAP_DISTINGUISHED_NAME,
|
||||
]
|
||||
|
||||
return self.search_paginator(
|
||||
search_base=self.base_dn_groups,
|
||||
search_filter=self._source.group_object_filter,
|
||||
search_scope=SUBTREE,
|
||||
attributes=attributes,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def sync(self, page_data: list) -> int:
|
||||
"""Iterate over all Groups and assign their parents"""
|
||||
if not self._source.sync_groups:
|
||||
self._task.info("Group syncing is disabled for this Source")
|
||||
return -1
|
||||
if not self._source.sync_group_hierarchy:
|
||||
self._task.info("Group hierarchy syncing is disabled for this Source")
|
||||
return -1
|
||||
count = 0
|
||||
for group_data in page_data:
|
||||
if (attributes := self.get_attributes(group_data)) is None:
|
||||
continue
|
||||
group = self.get_group(group_data)
|
||||
if not group:
|
||||
continue
|
||||
|
||||
# Deliberately WET
|
||||
if self._source.lookup_groups_from_user:
|
||||
parents_from_source_raw = attributes.get(self._source.group_membership_field, [])
|
||||
parents_from_source = Group.objects.filter(
|
||||
**{
|
||||
(
|
||||
"attributes__" f"{self._source.user_membership_attribute}__in"
|
||||
): parents_from_source_raw
|
||||
}
|
||||
)
|
||||
return None
|
||||
group_uniq = group_uniq[0]
|
||||
if group_uniq not in self.group_cache:
|
||||
groups = GroupLDAPSourceConnection.objects.filter(identifier=group_uniq).select_related(
|
||||
"group"
|
||||
)
|
||||
if not groups.exists():
|
||||
if self._source.sync_groups:
|
||||
self._task.info(
|
||||
f"Group does not exist in our DB yet, run sync_groups first: '{group_dn}'",
|
||||
group=group_dn,
|
||||
)
|
||||
return None
|
||||
self.group_cache[group_uniq] = groups.first().group
|
||||
return self.group_cache[group_uniq]
|
||||
parents_not_from_source = group.parents.exclude(
|
||||
groupsourceconnection__source=self._source
|
||||
)
|
||||
|
||||
count = len(parents_from_source)
|
||||
group.parents.set(parents_from_source.union(parents_not_from_source))
|
||||
else:
|
||||
children_from_source_raw = attributes.get(self._source.group_membership_field, [])
|
||||
children_from_source = Group.objects.filter(
|
||||
**{
|
||||
(
|
||||
"attributes__" f"{self._source.user_membership_attribute}__in"
|
||||
): children_from_source_raw
|
||||
}
|
||||
)
|
||||
children_not_from_source = group.children.exclude(
|
||||
groupsourceconnection__source=self._source
|
||||
)
|
||||
|
||||
count = len(children_from_source)
|
||||
group.children.set(children_from_source.union(children_not_from_source))
|
||||
|
||||
self._logger.debug("Successfully updated group hierarchy")
|
||||
return count
|
||||
|
||||
@@ -21,7 +21,10 @@ from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.forward_delete_groups import GroupLDAPForwardDeletion
|
||||
from authentik.sources.ldap.sync.forward_delete_users import UserLDAPForwardDeletion
|
||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.membership import (
|
||||
GroupHierarchyLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
)
|
||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
||||
from authentik.tasks.middleware import CurrentTask
|
||||
from authentik.tasks.models import Task
|
||||
@@ -31,6 +34,7 @@ SYNC_CLASSES: list[type[BaseLDAPSynchronizer]] = [
|
||||
UserLDAPSynchronizer,
|
||||
GroupLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
GroupHierarchyLDAPSynchronizer,
|
||||
]
|
||||
CACHE_KEY_PREFIX = "goauthentik.io/sources/ldap/page/"
|
||||
CACHE_KEY_STATUS = "goauthentik.io/sources/ldap/status/"
|
||||
@@ -71,7 +75,10 @@ def ldap_sync(source_pk: str):
|
||||
+ ldap_sync_paginator(task, source, GroupLDAPSynchronizer)
|
||||
)
|
||||
|
||||
membership_tasks = group(ldap_sync_paginator(task, source, MembershipLDAPSynchronizer))
|
||||
membership_tasks = group(
|
||||
ldap_sync_paginator(task, source, MembershipLDAPSynchronizer)
|
||||
+ ldap_sync_paginator(task, source, GroupHierarchyLDAPSynchronizer)
|
||||
)
|
||||
|
||||
deletion_tasks = group(
|
||||
ldap_sync_paginator(task, source, UserLDAPForwardDeletion)
|
||||
|
||||
@@ -22,7 +22,10 @@ from authentik.sources.ldap.models import (
|
||||
)
|
||||
from authentik.sources.ldap.sync.forward_delete_users import DELETE_CHUNK_SIZE
|
||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.membership import (
|
||||
GroupHierarchyLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
)
|
||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
||||
from authentik.sources.ldap.tasks import ldap_sync, ldap_sync_page
|
||||
from authentik.sources.ldap.tests.mock_ad import mock_ad_connection
|
||||
@@ -417,6 +420,102 @@ class LDAPSyncTests(TestCase):
|
||||
posix_group = Group.objects.filter(name="group-posix").first()
|
||||
self.assertTrue(posix_group.users.filter(name="user-posix").exists())
|
||||
|
||||
def test_sync_group_hierarchy_ad(self):
|
||||
"""Test group hierarchy sync"""
|
||||
self.source.base_dn = "dc=t,dc=goauthentik,dc=io"
|
||||
self.source.additional_user_dn = ""
|
||||
self.source.additional_group_dn = ""
|
||||
self.source.sync_group_hierarchy = True
|
||||
self.source.save()
|
||||
self.source.user_property_mappings.set(
|
||||
LDAPSourcePropertyMapping.objects.filter(
|
||||
Q(managed__startswith="goauthentik.io/sources/ldap/default")
|
||||
| Q(managed__startswith="goauthentik.io/sources/ldap/ms")
|
||||
)
|
||||
)
|
||||
self.source.group_property_mappings.set(
|
||||
LDAPSourcePropertyMapping.objects.filter(
|
||||
managed="goauthentik.io/sources/ldap/default-name"
|
||||
)
|
||||
)
|
||||
connection = MagicMock(return_value=mock_ad_connection())
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
_user = create_test_admin_user()
|
||||
sync_parent_group = Group.objects.get(name=_user.username)
|
||||
self.source.sync_parent_group = sync_parent_group
|
||||
self.source.save()
|
||||
group_sync = GroupLDAPSynchronizer(self.source, Task())
|
||||
group_sync.sync_full()
|
||||
hierarchy_sync = GroupHierarchyLDAPSynchronizer(self.source, Task())
|
||||
hierarchy_sync.sync_full()
|
||||
child_group_name = "Domain Admins"
|
||||
parent_group_name = "Administrators"
|
||||
group: Group = Group.objects.filter(name=child_group_name).first()
|
||||
parent_ad_group = Group.objects.filter(name=parent_group_name).first()
|
||||
self.assertIsNotNone(group, f"Child group {child_group_name} not found")
|
||||
self.assertIsNotNone(parent_ad_group, f"Parent group {parent_group_name} not found")
|
||||
self.assertTrue(
|
||||
parent_ad_group in group.parents.all(),
|
||||
f"Parent group {parent_group_name} not synced as parent of {child_group_name}",
|
||||
)
|
||||
self.assertTrue(
|
||||
sync_parent_group in group.parents.all(),
|
||||
f"Additional parent group missing from {child_group_name}'s parents",
|
||||
)
|
||||
self.assertTrue(
|
||||
sync_parent_group in parent_ad_group.parents.all(),
|
||||
f"Additional parent group missing from {parent_group_name}'s parents",
|
||||
)
|
||||
|
||||
def test_sync_group_hierarchy_ad_memberOf(self):
|
||||
"""Test group hierarchy sync"""
|
||||
self.source.base_dn = "dc=t,dc=goauthentik,dc=io"
|
||||
self.source.additional_user_dn = ""
|
||||
self.source.additional_group_dn = ""
|
||||
self.source.sync_group_hierarchy = True
|
||||
self.source.lookup_groups_from_user = True
|
||||
self.source.group_membership_field = "memberOf"
|
||||
self.source.save()
|
||||
self.source.user_property_mappings.set(
|
||||
LDAPSourcePropertyMapping.objects.filter(
|
||||
Q(managed__startswith="goauthentik.io/sources/ldap/default")
|
||||
| Q(managed__startswith="goauthentik.io/sources/ldap/ms")
|
||||
)
|
||||
)
|
||||
self.source.group_property_mappings.set(
|
||||
LDAPSourcePropertyMapping.objects.filter(
|
||||
managed="goauthentik.io/sources/ldap/default-name"
|
||||
)
|
||||
)
|
||||
connection = MagicMock(return_value=mock_ad_connection())
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
_user = create_test_admin_user()
|
||||
sync_parent_group = Group.objects.get(name=_user.username)
|
||||
self.source.sync_parent_group = sync_parent_group
|
||||
self.source.save()
|
||||
group_sync = GroupLDAPSynchronizer(self.source, Task())
|
||||
group_sync.sync_full()
|
||||
hierarchy_sync = GroupHierarchyLDAPSynchronizer(self.source, Task())
|
||||
hierarchy_sync.sync_full()
|
||||
child_group_name = "Domain Admins"
|
||||
parent_group_name = "Administrators"
|
||||
group: Group = Group.objects.filter(name=child_group_name).first()
|
||||
parent_ad_group = Group.objects.filter(name=parent_group_name).first()
|
||||
self.assertIsNotNone(group, f"Child group {child_group_name} not found")
|
||||
self.assertIsNotNone(parent_ad_group, f"Parent group {parent_group_name} not found")
|
||||
self.assertTrue(
|
||||
parent_ad_group in group.parents.all(),
|
||||
f"Parent group {parent_group_name} not synced as parent of {child_group_name}",
|
||||
)
|
||||
self.assertTrue(
|
||||
sync_parent_group in group.parents.all(),
|
||||
f"Additional parent group missing from {child_group_name}'s parents",
|
||||
)
|
||||
self.assertTrue(
|
||||
sync_parent_group in parent_ad_group.parents.all(),
|
||||
f"Additional parent group missing from {parent_group_name}'s parents",
|
||||
)
|
||||
|
||||
def test_tasks_ad(self):
|
||||
"""Test Scheduled tasks"""
|
||||
self.source.user_property_mappings.set(
|
||||
|
||||
@@ -13062,6 +13062,11 @@
|
||||
],
|
||||
"title": "Sync outgoing trigger mode",
|
||||
"description": "When to trigger sync for outgoing providers"
|
||||
},
|
||||
"sync_group_hierarchy": {
|
||||
"type": "boolean",
|
||||
"title": "Sync group hierarchy",
|
||||
"description": "Sync group parentage/hierarchy from LDAP directories."
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
||||
5
packages/client-ts/src/apis/SourcesApi.ts
generated
5
packages/client-ts/src/apis/SourcesApi.ts
generated
@@ -715,6 +715,7 @@ export interface SourcesLdapListRequest {
|
||||
slug?: string;
|
||||
sni?: boolean;
|
||||
startTls?: boolean;
|
||||
syncGroupHierarchy?: boolean;
|
||||
syncGroups?: boolean;
|
||||
syncParentGroup?: string;
|
||||
syncUsers?: boolean;
|
||||
@@ -5968,6 +5969,10 @@ export class SourcesApi extends runtime.BaseAPI {
|
||||
queryParameters["start_tls"] = requestParameters["startTls"];
|
||||
}
|
||||
|
||||
if (requestParameters["syncGroupHierarchy"] != null) {
|
||||
queryParameters["sync_group_hierarchy"] = requestParameters["syncGroupHierarchy"];
|
||||
}
|
||||
|
||||
if (requestParameters["syncGroups"] != null) {
|
||||
queryParameters["sync_groups"] = requestParameters["syncGroups"];
|
||||
}
|
||||
|
||||
9
packages/client-ts/src/models/LDAPSource.ts
generated
9
packages/client-ts/src/models/LDAPSource.ts
generated
@@ -288,6 +288,12 @@ export interface LDAPSource {
|
||||
* @memberof LDAPSource
|
||||
*/
|
||||
syncOutgoingTriggerMode?: SyncOutgoingTriggerModeEnum;
|
||||
/**
|
||||
* Sync group parentage/hierarchy from LDAP directories.
|
||||
* @type {boolean}
|
||||
* @memberof LDAPSource
|
||||
*/
|
||||
syncGroupHierarchy?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -456,6 +462,8 @@ export function LDAPSourceFromJSONTyped(json: any, ignoreDiscriminator: boolean)
|
||||
json["sync_outgoing_trigger_mode"] == null
|
||||
? undefined
|
||||
: SyncOutgoingTriggerModeEnumFromJSON(json["sync_outgoing_trigger_mode"]),
|
||||
syncGroupHierarchy:
|
||||
json["sync_group_hierarchy"] == null ? undefined : json["sync_group_hierarchy"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -519,5 +527,6 @@ export function LDAPSourceToJSONTyped(
|
||||
sync_outgoing_trigger_mode: SyncOutgoingTriggerModeEnumToJSON(
|
||||
value["syncOutgoingTriggerMode"],
|
||||
),
|
||||
sync_group_hierarchy: value["syncGroupHierarchy"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -238,6 +238,12 @@ export interface LDAPSourceRequest {
|
||||
* @memberof LDAPSourceRequest
|
||||
*/
|
||||
syncOutgoingTriggerMode?: SyncOutgoingTriggerModeEnum;
|
||||
/**
|
||||
* Sync group parentage/hierarchy from LDAP directories.
|
||||
* @type {boolean}
|
||||
* @memberof LDAPSourceRequest
|
||||
*/
|
||||
syncGroupHierarchy?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -362,6 +368,8 @@ export function LDAPSourceRequestFromJSONTyped(
|
||||
json["sync_outgoing_trigger_mode"] == null
|
||||
? undefined
|
||||
: SyncOutgoingTriggerModeEnumFromJSON(json["sync_outgoing_trigger_mode"]),
|
||||
syncGroupHierarchy:
|
||||
json["sync_group_hierarchy"] == null ? undefined : json["sync_group_hierarchy"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -415,5 +423,6 @@ export function LDAPSourceRequestToJSONTyped(
|
||||
sync_outgoing_trigger_mode: SyncOutgoingTriggerModeEnumToJSON(
|
||||
value["syncOutgoingTriggerMode"],
|
||||
),
|
||||
sync_group_hierarchy: value["syncGroupHierarchy"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -238,6 +238,12 @@ export interface PatchedLDAPSourceRequest {
|
||||
* @memberof PatchedLDAPSourceRequest
|
||||
*/
|
||||
syncOutgoingTriggerMode?: SyncOutgoingTriggerModeEnum;
|
||||
/**
|
||||
* Sync group parentage/hierarchy from LDAP directories.
|
||||
* @type {boolean}
|
||||
* @memberof PatchedLDAPSourceRequest
|
||||
*/
|
||||
syncGroupHierarchy?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,6 +354,8 @@ export function PatchedLDAPSourceRequestFromJSONTyped(
|
||||
json["sync_outgoing_trigger_mode"] == null
|
||||
? undefined
|
||||
: SyncOutgoingTriggerModeEnumFromJSON(json["sync_outgoing_trigger_mode"]),
|
||||
syncGroupHierarchy:
|
||||
json["sync_group_hierarchy"] == null ? undefined : json["sync_group_hierarchy"],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -401,5 +409,6 @@ export function PatchedLDAPSourceRequestToJSONTyped(
|
||||
sync_outgoing_trigger_mode: SyncOutgoingTriggerModeEnumToJSON(
|
||||
value["syncOutgoingTriggerMode"],
|
||||
),
|
||||
sync_group_hierarchy: value["syncGroupHierarchy"],
|
||||
};
|
||||
}
|
||||
|
||||
13
schema.yml
13
schema.yml
@@ -23766,6 +23766,10 @@ paths:
|
||||
name: start_tls
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: sync_group_hierarchy
|
||||
schema:
|
||||
type: boolean
|
||||
- in: query
|
||||
name: sync_groups
|
||||
schema:
|
||||
@@ -42602,6 +42606,9 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SyncOutgoingTriggerModeEnum'
|
||||
description: When to trigger sync for outgoing providers
|
||||
sync_group_hierarchy:
|
||||
type: boolean
|
||||
description: Sync group parentage/hierarchy from LDAP directories.
|
||||
required:
|
||||
- base_dn
|
||||
- component
|
||||
@@ -42821,6 +42828,9 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SyncOutgoingTriggerModeEnum'
|
||||
description: When to trigger sync for outgoing providers
|
||||
sync_group_hierarchy:
|
||||
type: boolean
|
||||
description: Sync group parentage/hierarchy from LDAP directories.
|
||||
required:
|
||||
- base_dn
|
||||
- name
|
||||
@@ -49918,6 +49928,9 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SyncOutgoingTriggerModeEnum'
|
||||
description: When to trigger sync for outgoing providers
|
||||
sync_group_hierarchy:
|
||||
type: boolean
|
||||
description: Sync group parentage/hierarchy from LDAP directories.
|
||||
PatchedLicenseRequest:
|
||||
type: object
|
||||
description: License Serializer
|
||||
|
||||
@@ -118,6 +118,12 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
||||
label=${msg("Sync groups")}
|
||||
?checked=${this.instance?.syncGroups ?? true}
|
||||
></ak-switch-input>
|
||||
<ak-switch-input
|
||||
name="syncGroupHierarchy"
|
||||
label=${msg("Sync Group Hierarchy")}
|
||||
?checked=${this.instance?.syncGroupHierarchy ?? true}
|
||||
help=${msg("Sync group hierarchy from LDAP directories.")}
|
||||
></ak-switch-input>
|
||||
<ak-switch-input
|
||||
name="deleteNotFoundObjects"
|
||||
label=${msg("Delete Not Found Objects")}
|
||||
@@ -243,7 +249,10 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
||||
</ak-form-group>
|
||||
<ak-form-group label="${msg("Additional settings")}">
|
||||
<div class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${msg("Parent Group")} name="syncParentGroup">
|
||||
<ak-form-element-horizontal
|
||||
label=${msg("Additional Parent Group")}
|
||||
name="additionalParentGroup"
|
||||
>
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
||||
const args: CoreGroupsListRequest = {
|
||||
|
||||
@@ -47,6 +47,7 @@ If the LDAP server rejects the TLS handshake, verify that **Server URI**, **Enab
|
||||
|
||||
#### Additional settings
|
||||
|
||||
- **Sync Group Parents**: Sync group hierarchy from LDAP directories. Adds parents to groups imported from this LDAP directory, using same lookup fields and method as for user group membership.
|
||||
- **Parent Group**: Parent group for all the groups imported from LDAP. An example use case would be to import Active Directory groups under a root `imported-from-ad` group.
|
||||
- **User path**: Path template for all new users created.
|
||||
- **Additional User DN**: Prepended to the base DN for user queries.
|
||||
|
||||
Reference in New Issue
Block a user