sources/ldap: improve scalability (#6056)
* sources/ldap: improve scalability Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix lint Signed-off-by: Jens Langhammer <jens@goauthentik.io> * use cache instead of call signature for page data Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
a987846c76
commit
e712225ced
|
@ -70,8 +70,10 @@ class TaskInfo:
|
|||
return cache.get_many(cache.keys(CACHE_KEY_PREFIX + "*"))
|
||||
|
||||
@staticmethod
|
||||
def by_name(name: str) -> Optional["TaskInfo"]:
|
||||
def by_name(name: str) -> Optional["TaskInfo"] | Optional[list["TaskInfo"]]:
|
||||
"""Get TaskInfo Object by name"""
|
||||
if "*" in name:
|
||||
return cache.get_many(cache.keys(CACHE_KEY_PREFIX + name)).values()
|
||||
return cache.get(CACHE_KEY_PREFIX + name, None)
|
||||
|
||||
def delete(self):
|
||||
|
|
|
@ -118,10 +118,9 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||
"""Get source's sync status"""
|
||||
source = self.get_object()
|
||||
results = []
|
||||
for sync_class in SYNC_CLASSES:
|
||||
sync_name = sync_class.__name__.replace("LDAPSynchronizer", "").lower()
|
||||
task = TaskInfo.by_name(f"ldap_sync:{source.slug}:{sync_name}")
|
||||
if task:
|
||||
tasks = TaskInfo.by_name(f"ldap_sync:{source.slug}:*")
|
||||
if tasks:
|
||||
for task in tasks:
|
||||
results.append(task)
|
||||
return Response(TaskSerializer(results, many=True).data)
|
||||
|
||||
|
@ -143,7 +142,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||
source = self.get_object()
|
||||
all_objects = {}
|
||||
for sync_class in SYNC_CLASSES:
|
||||
class_name = sync_class.__name__.replace("LDAPSynchronizer", "").lower()
|
||||
class_name = sync_class.name()
|
||||
all_objects.setdefault(class_name, [])
|
||||
for obj in sync_class(source).get_objects(size_limit=10):
|
||||
obj: dict
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
from authentik.sources.ldap.models import LDAPSource
|
||||
from authentik.sources.ldap.tasks import SYNC_CLASSES, ldap_sync
|
||||
from authentik.sources.ldap.tasks import ldap_sync_single
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
@ -21,7 +20,4 @@ class Command(BaseCommand):
|
|||
if not source:
|
||||
LOGGER.warning("Source does not exist", slug=source_slug)
|
||||
continue
|
||||
for sync_class in SYNC_CLASSES:
|
||||
LOGGER.info("Starting sync", cls=sync_class)
|
||||
# pylint: disable=no-value-for-parameter
|
||||
ldap_sync(source.pk, class_to_path(sync_class))
|
||||
ldap_sync_single(source)
|
||||
|
|
|
@ -12,13 +12,9 @@ from authentik.core.models import User
|
|||
from authentik.core.signals import password_changed
|
||||
from authentik.events.models import Event, EventAction
|
||||
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||
from authentik.lib.utils.reflection import class_to_path
|
||||
from authentik.sources.ldap.models import LDAPSource
|
||||
from authentik.sources.ldap.password import LDAPPasswordChanger
|
||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
||||
from authentik.sources.ldap.tasks import ldap_sync
|
||||
from authentik.sources.ldap.tasks import ldap_sync_single
|
||||
from authentik.stages.prompt.signals import password_validate
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
@ -35,12 +31,7 @@ def sync_ldap_source_on_save(sender, instance: LDAPSource, **_):
|
|||
# and the mappings are created with an m2m event
|
||||
if not instance.property_mappings.exists() or not instance.property_mappings_group.exists():
|
||||
return
|
||||
for sync_class in [
|
||||
UserLDAPSynchronizer,
|
||||
GroupLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
]:
|
||||
ldap_sync.delay(instance.pk, class_to_path(sync_class))
|
||||
ldap_sync_single.delay(instance.pk)
|
||||
|
||||
|
||||
@receiver(password_validate)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"""Sync LDAP Users and groups into authentik"""
|
||||
from typing import Any, Generator
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models.base import Model
|
||||
from django.db.models.query import QuerySet
|
||||
from ldap3 import Connection
|
||||
from ldap3 import DEREF_ALWAYS, SUBTREE, Connection
|
||||
from structlog.stdlib import BoundLogger, get_logger
|
||||
|
||||
from authentik.core.exceptions import PropertyMappingExpressionException
|
||||
|
@ -29,6 +30,24 @@ class BaseLDAPSynchronizer:
|
|||
self._messages = []
|
||||
self._logger = get_logger().bind(source=source, syncer=self.__class__.__name__)
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
"""UI name for the type of object this class synchronizes"""
|
||||
raise NotImplementedError
|
||||
|
||||
def sync_full(self):
|
||||
"""Run full sync, this function should only be used in tests"""
|
||||
if not settings.TEST: # noqa
|
||||
raise RuntimeError(
|
||||
f"{self.__class__.__name__}.sync_full() should only be used in tests"
|
||||
)
|
||||
for page in self.get_objects():
|
||||
self.sync(page)
|
||||
|
||||
def sync(self, page_data: list) -> int:
|
||||
"""Sync function, implemented in subclass"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def messages(self) -> list[str]:
|
||||
"""Get all UI messages"""
|
||||
|
@ -60,9 +79,47 @@ class BaseLDAPSynchronizer:
|
|||
"""Get objects from LDAP, implemented in subclass"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def sync(self) -> int:
|
||||
"""Sync function, implemented in subclass"""
|
||||
raise NotImplementedError()
|
||||
# pylint: disable=too-many-arguments
|
||||
def search_paginator(
|
||||
self,
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope=SUBTREE,
|
||||
dereference_aliases=DEREF_ALWAYS,
|
||||
attributes=None,
|
||||
size_limit=0,
|
||||
time_limit=0,
|
||||
types_only=False,
|
||||
get_operational_attributes=False,
|
||||
controls=None,
|
||||
paged_size=5,
|
||||
paged_criticality=False,
|
||||
):
|
||||
"""Search in pages, returns each page"""
|
||||
cookie = True
|
||||
while cookie:
|
||||
self._connection.search(
|
||||
search_base,
|
||||
search_filter,
|
||||
search_scope,
|
||||
dereference_aliases,
|
||||
attributes,
|
||||
size_limit,
|
||||
time_limit,
|
||||
types_only,
|
||||
get_operational_attributes,
|
||||
controls,
|
||||
paged_size,
|
||||
paged_criticality,
|
||||
None if cookie is True else cookie,
|
||||
)
|
||||
try:
|
||||
cookie = self._connection.result["controls"]["1.2.840.113556.1.4.319"]["value"][
|
||||
"cookie"
|
||||
]
|
||||
except KeyError:
|
||||
cookie = None
|
||||
yield self._connection.response
|
||||
|
||||
def _flatten(self, value: Any) -> Any:
|
||||
"""Flatten `value` if its a list"""
|
||||
|
|
|
@ -13,8 +13,12 @@ from authentik.sources.ldap.sync.base import LDAP_UNIQUENESS, BaseLDAPSynchroniz
|
|||
class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
"""Sync LDAP Users and groups into authentik"""
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
return "groups"
|
||||
|
||||
def get_objects(self, **kwargs) -> Generator:
|
||||
return self._connection.extend.standard.paged_search(
|
||||
return self.search_paginator(
|
||||
search_base=self.base_dn_groups,
|
||||
search_filter=self._source.group_object_filter,
|
||||
search_scope=SUBTREE,
|
||||
|
@ -22,13 +26,13 @@ class GroupLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||
**kwargs,
|
||||
)
|
||||
|
||||
def sync(self) -> int:
|
||||
def sync(self, page_data: list) -> int:
|
||||
"""Iterate over all LDAP Groups and create authentik_core.Group instances"""
|
||||
if not self._source.sync_groups:
|
||||
self.message("Group syncing is disabled for this Source")
|
||||
return -1
|
||||
group_count = 0
|
||||
for group in self.get_objects():
|
||||
for group in page_data:
|
||||
if "attributes" not in group:
|
||||
continue
|
||||
attributes = group.get("attributes", {})
|
||||
|
|
|
@ -19,8 +19,12 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||
super().__init__(source)
|
||||
self.group_cache: dict[str, Group] = {}
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
return "membership"
|
||||
|
||||
def get_objects(self, **kwargs) -> Generator:
|
||||
return self._connection.extend.standard.paged_search(
|
||||
return self.search_paginator(
|
||||
search_base=self.base_dn_groups,
|
||||
search_filter=self._source.group_object_filter,
|
||||
search_scope=SUBTREE,
|
||||
|
@ -32,13 +36,13 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||
**kwargs,
|
||||
)
|
||||
|
||||
def sync(self) -> int:
|
||||
def sync(self, page_data: list) -> int:
|
||||
"""Iterate over all Users and assign Groups using memberOf Field"""
|
||||
if not self._source.sync_groups:
|
||||
self.message("Group syncing is disabled for this Source")
|
||||
return -1
|
||||
membership_count = 0
|
||||
for group in self.get_objects():
|
||||
for group in page_data:
|
||||
if "attributes" not in group:
|
||||
continue
|
||||
members = group.get("attributes", {}).get(self._source.group_membership_field, [])
|
||||
|
|
|
@ -15,8 +15,12 @@ from authentik.sources.ldap.sync.vendor.ms_ad import MicrosoftActiveDirectory
|
|||
class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
"""Sync LDAP Users into authentik"""
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
return "users"
|
||||
|
||||
def get_objects(self, **kwargs) -> Generator:
|
||||
return self._connection.extend.standard.paged_search(
|
||||
return self.search_paginator(
|
||||
search_base=self.base_dn_users,
|
||||
search_filter=self._source.user_object_filter,
|
||||
search_scope=SUBTREE,
|
||||
|
@ -24,13 +28,13 @@ class UserLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||
**kwargs,
|
||||
)
|
||||
|
||||
def sync(self) -> int:
|
||||
def sync(self, page_data: list) -> int:
|
||||
"""Iterate over all LDAP Users and create authentik_core.User instances"""
|
||||
if not self._source.sync_users:
|
||||
self.message("User syncing is disabled for this Source")
|
||||
return -1
|
||||
user_count = 0
|
||||
for user in self.get_objects():
|
||||
for user in page_data:
|
||||
if "attributes" not in user:
|
||||
continue
|
||||
attributes = user.get("attributes", {})
|
||||
|
|
|
@ -11,6 +11,10 @@ from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
|
|||
class FreeIPA(BaseLDAPSynchronizer):
|
||||
"""FreeIPA-specific LDAP"""
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
return "freeipa"
|
||||
|
||||
def get_objects(self, **kwargs) -> Generator:
|
||||
yield None
|
||||
|
||||
|
|
4
authentik/sources/ldap/sync/vendor/ms_ad.py
vendored
4
authentik/sources/ldap/sync/vendor/ms_ad.py
vendored
|
@ -42,6 +42,10 @@ class UserAccountControl(IntFlag):
|
|||
class MicrosoftActiveDirectory(BaseLDAPSynchronizer):
|
||||
"""Microsoft-specific LDAP"""
|
||||
|
||||
@staticmethod
|
||||
def name() -> str:
|
||||
return "microsoft_ad"
|
||||
|
||||
def get_objects(self, **kwargs) -> Generator:
|
||||
yield None
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
"""LDAP Sync tasks"""
|
||||
from uuid import uuid4
|
||||
|
||||
from celery import chain, group
|
||||
from django.core.cache import cache
|
||||
from ldap3.core.exceptions import LDAPException
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
|
@ -8,6 +12,7 @@ from authentik.lib.utils.errors import exception_to_string
|
|||
from authentik.lib.utils.reflection import class_to_path, path_to_class
|
||||
from authentik.root.celery import CELERY_APP
|
||||
from authentik.sources.ldap.models import LDAPSource
|
||||
from authentik.sources.ldap.sync.base import BaseLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.groups import GroupLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.membership import MembershipLDAPSynchronizer
|
||||
from authentik.sources.ldap.sync.users import UserLDAPSynchronizer
|
||||
|
@ -18,14 +23,43 @@ SYNC_CLASSES = [
|
|||
GroupLDAPSynchronizer,
|
||||
MembershipLDAPSynchronizer,
|
||||
]
|
||||
CACHE_KEY_PREFIX = "goauthentik.io/sources/ldap/page/"
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
def ldap_sync_all():
|
||||
"""Sync all sources"""
|
||||
for source in LDAPSource.objects.filter(enabled=True):
|
||||
for sync_class in SYNC_CLASSES:
|
||||
ldap_sync.delay(source.pk, class_to_path(sync_class))
|
||||
ldap_sync_single(source)
|
||||
|
||||
|
||||
@CELERY_APP.task()
|
||||
def ldap_sync_single(source: LDAPSource):
|
||||
"""Sync a single source"""
|
||||
task = chain(
|
||||
# User and group sync can happen at once, they have no dependencies on each other
|
||||
group(
|
||||
ldap_sync_paginator(source, UserLDAPSynchronizer)
|
||||
+ ldap_sync_paginator(source, GroupLDAPSynchronizer),
|
||||
),
|
||||
# Membership sync needs to run afterwards
|
||||
group(
|
||||
ldap_sync_paginator(source, MembershipLDAPSynchronizer),
|
||||
),
|
||||
)
|
||||
task()
|
||||
|
||||
|
||||
def ldap_sync_paginator(source: LDAPSource, sync: type[BaseLDAPSynchronizer]) -> list:
|
||||
"""Return a list of task signatures with LDAP pagination data"""
|
||||
sync_inst: BaseLDAPSynchronizer = sync(source)
|
||||
signatures = []
|
||||
for page in sync_inst.get_objects():
|
||||
page_cache_key = CACHE_KEY_PREFIX + str(uuid4())
|
||||
cache.set(page_cache_key, page)
|
||||
page_sync = ldap_sync.si(source.pk, class_to_path(sync), page_cache_key)
|
||||
signatures.append(page_sync)
|
||||
return signatures
|
||||
|
||||
|
||||
@CELERY_APP.task(
|
||||
|
@ -34,7 +68,7 @@ def ldap_sync_all():
|
|||
soft_time_limit=60 * 60 * int(CONFIG.y("ldap.task_timeout_hours")),
|
||||
task_time_limit=60 * 60 * int(CONFIG.y("ldap.task_timeout_hours")),
|
||||
)
|
||||
def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str):
|
||||
def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str, page_cache_key: str):
|
||||
"""Synchronization of an LDAP Source"""
|
||||
self.result_timeout_hours = int(CONFIG.y("ldap.task_timeout_hours"))
|
||||
try:
|
||||
|
@ -43,11 +77,16 @@ def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str):
|
|||
# Because the source couldn't be found, we don't have a UID
|
||||
# to set the state with
|
||||
return
|
||||
sync = path_to_class(sync_class)
|
||||
self.set_uid(f"{source.slug}:{sync.__name__.replace('LDAPSynchronizer', '').lower()}")
|
||||
sync: type[BaseLDAPSynchronizer] = path_to_class(sync_class)
|
||||
uid = page_cache_key.replace(CACHE_KEY_PREFIX, "")
|
||||
self.set_uid(f"{source.slug}:{sync.name()}:{uid}")
|
||||
try:
|
||||
sync_inst = sync(source)
|
||||
count = sync_inst.sync()
|
||||
sync_inst: BaseLDAPSynchronizer = sync(source)
|
||||
page = cache.get(page_cache_key)
|
||||
if not page:
|
||||
return
|
||||
cache.touch(page_cache_key)
|
||||
count = sync_inst.sync(page)
|
||||
messages = sync_inst.messages
|
||||
messages.append(f"Synced {count} objects.")
|
||||
self.set_status(
|
||||
|
@ -56,6 +95,7 @@ def ldap_sync(self: MonitoredTask, source_pk: str, sync_class: str):
|
|||
messages,
|
||||
)
|
||||
)
|
||||
cache.delete(page_cache_key)
|
||||
except LDAPException as exc:
|
||||
# No explicit event is created here as .set_status with an error will do that
|
||||
LOGGER.warning(exception_to_string(exc))
|
||||
|
|
|
@ -43,7 +43,7 @@ class LDAPSyncTests(TestCase):
|
|||
connection = MagicMock(return_value=raw_conn)
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
user_sync = UserLDAPSynchronizer(self.source)
|
||||
user_sync.sync()
|
||||
user_sync.sync_full()
|
||||
|
||||
user = User.objects.get(username="user0_sn")
|
||||
# auth_user_by_bind = Mock(return_value=user)
|
||||
|
@ -71,7 +71,7 @@ class LDAPSyncTests(TestCase):
|
|||
connection = MagicMock(return_value=mock_ad_connection(LDAP_PASSWORD))
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
user_sync = UserLDAPSynchronizer(self.source)
|
||||
user_sync.sync()
|
||||
user_sync.sync_full()
|
||||
|
||||
user = User.objects.get(username="user0_sn")
|
||||
auth_user_by_bind = Mock(return_value=user)
|
||||
|
@ -98,7 +98,7 @@ class LDAPSyncTests(TestCase):
|
|||
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
user_sync = UserLDAPSynchronizer(self.source)
|
||||
user_sync.sync()
|
||||
user_sync.sync_full()
|
||||
|
||||
user = User.objects.get(username="user0_sn")
|
||||
auth_user_by_bind = Mock(return_value=user)
|
||||
|
|
|
@ -51,7 +51,7 @@ class LDAPSyncTests(TestCase):
|
|||
connection = MagicMock(return_value=mock_ad_connection(LDAP_PASSWORD))
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
user_sync = UserLDAPSynchronizer(self.source)
|
||||
user_sync.sync()
|
||||
user_sync.sync_full()
|
||||
self.assertFalse(User.objects.filter(username="user0_sn").exists())
|
||||
self.assertFalse(User.objects.filter(username="user1_sn").exists())
|
||||
events = Event.objects.filter(
|
||||
|
@ -87,7 +87,7 @@ class LDAPSyncTests(TestCase):
|
|||
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
user_sync = UserLDAPSynchronizer(self.source)
|
||||
user_sync.sync()
|
||||
user_sync.sync_full()
|
||||
user = User.objects.filter(username="user0_sn").first()
|
||||
self.assertEqual(user.attributes["foo"], "bar")
|
||||
self.assertFalse(user.is_active)
|
||||
|
@ -106,7 +106,7 @@ class LDAPSyncTests(TestCase):
|
|||
connection = MagicMock(return_value=mock_slapd_connection(LDAP_PASSWORD))
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
user_sync = UserLDAPSynchronizer(self.source)
|
||||
user_sync.sync()
|
||||
user_sync.sync_full()
|
||||
self.assertTrue(User.objects.filter(username="user0_sn").exists())
|
||||
self.assertFalse(User.objects.filter(username="user1_sn").exists())
|
||||
|
||||
|
@ -128,9 +128,9 @@ class LDAPSyncTests(TestCase):
|
|||
self.source.sync_parent_group = parent_group
|
||||
self.source.save()
|
||||
group_sync = GroupLDAPSynchronizer(self.source)
|
||||
group_sync.sync()
|
||||
group_sync.sync_full()
|
||||
membership_sync = MembershipLDAPSynchronizer(self.source)
|
||||
membership_sync.sync()
|
||||
membership_sync.sync_full()
|
||||
group: Group = Group.objects.filter(name="test-group").first()
|
||||
self.assertIsNotNone(group)
|
||||
self.assertEqual(group.parent, parent_group)
|
||||
|
@ -152,9 +152,9 @@ class LDAPSyncTests(TestCase):
|
|||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
self.source.save()
|
||||
group_sync = GroupLDAPSynchronizer(self.source)
|
||||
group_sync.sync()
|
||||
group_sync.sync_full()
|
||||
membership_sync = MembershipLDAPSynchronizer(self.source)
|
||||
membership_sync.sync()
|
||||
membership_sync.sync_full()
|
||||
group = Group.objects.filter(name="group1")
|
||||
self.assertTrue(group.exists())
|
||||
|
||||
|
@ -177,11 +177,11 @@ class LDAPSyncTests(TestCase):
|
|||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
self.source.save()
|
||||
user_sync = UserLDAPSynchronizer(self.source)
|
||||
user_sync.sync()
|
||||
user_sync.sync_full()
|
||||
group_sync = GroupLDAPSynchronizer(self.source)
|
||||
group_sync.sync()
|
||||
group_sync.sync_full()
|
||||
membership_sync = MembershipLDAPSynchronizer(self.source)
|
||||
membership_sync.sync()
|
||||
membership_sync.sync_full()
|
||||
# Test if membership mapping based on memberUid works.
|
||||
posix_group = Group.objects.filter(name="group-posix").first()
|
||||
self.assertTrue(posix_group.users.filter(name="user-posix").exists())
|
||||
|
|
|
@ -63,7 +63,7 @@ class TestSourceLDAPSamba(SeleniumTestCase):
|
|||
source.property_mappings_group.set(
|
||||
LDAPPropertyMapping.objects.filter(name="goauthentik.io/sources/ldap/default-name")
|
||||
)
|
||||
UserLDAPSynchronizer(source).sync()
|
||||
UserLDAPSynchronizer(source).sync_full()
|
||||
self.assertTrue(User.objects.filter(username="bob").exists())
|
||||
self.assertTrue(User.objects.filter(username="james").exists())
|
||||
self.assertTrue(User.objects.filter(username="john").exists())
|
||||
|
@ -94,9 +94,9 @@ class TestSourceLDAPSamba(SeleniumTestCase):
|
|||
source.property_mappings_group.set(
|
||||
LDAPPropertyMapping.objects.filter(managed="goauthentik.io/sources/ldap/default-name")
|
||||
)
|
||||
GroupLDAPSynchronizer(source).sync()
|
||||
UserLDAPSynchronizer(source).sync()
|
||||
MembershipLDAPSynchronizer(source).sync()
|
||||
GroupLDAPSynchronizer(source).sync_full()
|
||||
UserLDAPSynchronizer(source).sync_full()
|
||||
MembershipLDAPSynchronizer(source).sync_full()
|
||||
self.assertIsNotNone(User.objects.get(username="bob"))
|
||||
self.assertIsNotNone(User.objects.get(username="james"))
|
||||
self.assertIsNotNone(User.objects.get(username="john"))
|
||||
|
@ -137,7 +137,7 @@ class TestSourceLDAPSamba(SeleniumTestCase):
|
|||
source.property_mappings_group.set(
|
||||
LDAPPropertyMapping.objects.filter(name="goauthentik.io/sources/ldap/default-name")
|
||||
)
|
||||
UserLDAPSynchronizer(source).sync()
|
||||
UserLDAPSynchronizer(source).sync_full()
|
||||
username = "bob"
|
||||
password = generate_id()
|
||||
result = self.container.exec_run(
|
||||
|
@ -160,7 +160,7 @@ class TestSourceLDAPSamba(SeleniumTestCase):
|
|||
)
|
||||
self.assertEqual(result.exit_code, 0)
|
||||
# Sync again
|
||||
UserLDAPSynchronizer(source).sync()
|
||||
UserLDAPSynchronizer(source).sync_full()
|
||||
user.refresh_from_db()
|
||||
# Since password in samba was checked, it should be invalidated here too
|
||||
self.assertFalse(user.has_usable_password())
|
||||
|
|
Reference in a new issue