sources/ldap: implement MonitoredTask
This commit is contained in:
parent
482da81522
commit
4b3e0f0f96
|
@ -65,7 +65,7 @@ LOGIN_URL = "passbook_flows:default-authentication"
|
||||||
AUTH_USER_MODEL = "passbook_core.User"
|
AUTH_USER_MODEL = "passbook_core.User"
|
||||||
|
|
||||||
_cookie_suffix = "_debug" if DEBUG else ""
|
_cookie_suffix = "_debug" if DEBUG else ""
|
||||||
CSRF_COOKIE_NAME = f"passbook_csrf"
|
CSRF_COOKIE_NAME = "passbook_csrf"
|
||||||
LANGUAGE_COOKIE_NAME = f"passbook_language{_cookie_suffix}"
|
LANGUAGE_COOKIE_NAME = f"passbook_language{_cookie_suffix}"
|
||||||
SESSION_COOKIE_NAME = f"passbook_session{_cookie_suffix}"
|
SESSION_COOKIE_NAME = f"passbook_session{_cookie_suffix}"
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ AUTHENTICATION_BACKENDS = [
|
||||||
|
|
||||||
CELERY_BEAT_SCHEDULE = {
|
CELERY_BEAT_SCHEDULE = {
|
||||||
"sources_ldap_sync": {
|
"sources_ldap_sync": {
|
||||||
"task": "passbook.sources.ldap.tasks.sync",
|
"task": "passbook.sources.ldap.tasks.ldap_sync_all",
|
||||||
"schedule": crontab(minute=0), # Run every hour
|
"schedule": crontab(minute=0), # Run every hour
|
||||||
"options": {"queue": "passbook_scheduled"},
|
"options": {"queue": "passbook_scheduled"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ from passbook.core.signals import password_changed
|
||||||
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
|
from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER
|
||||||
from passbook.sources.ldap.models import LDAPSource
|
from passbook.sources.ldap.models import LDAPSource
|
||||||
from passbook.sources.ldap.password import LDAPPasswordChanger
|
from passbook.sources.ldap.password import LDAPPasswordChanger
|
||||||
from passbook.sources.ldap.tasks import sync_single
|
from passbook.sources.ldap.tasks import ldap_sync
|
||||||
from passbook.stages.prompt.signals import password_validate
|
from passbook.stages.prompt.signals import password_validate
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ from passbook.stages.prompt.signals import password_validate
|
||||||
def sync_ldap_source_on_save(sender, instance: LDAPSource, **_):
|
def sync_ldap_source_on_save(sender, instance: LDAPSource, **_):
|
||||||
"""Ensure that source is synced on save (if enabled)"""
|
"""Ensure that source is synced on save (if enabled)"""
|
||||||
if instance.enabled:
|
if instance.enabled:
|
||||||
sync_single.delay(instance.pk)
|
ldap_sync.delay(instance.pk)
|
||||||
|
|
||||||
|
|
||||||
@receiver(password_validate)
|
@receiver(password_validate)
|
||||||
|
|
|
@ -35,17 +35,18 @@ class LDAPSynchronizer:
|
||||||
return f"{self._source.additional_group_dn},{self._source.base_dn}"
|
return f"{self._source.additional_group_dn},{self._source.base_dn}"
|
||||||
return self._source.base_dn
|
return self._source.base_dn
|
||||||
|
|
||||||
def sync_groups(self):
|
def sync_groups(self) -> int:
|
||||||
"""Iterate over all LDAP Groups and create passbook_core.Group instances"""
|
"""Iterate over all LDAP Groups and create passbook_core.Group instances"""
|
||||||
if not self._source.sync_groups:
|
if not self._source.sync_groups:
|
||||||
LOGGER.warning("Group syncing is disabled for this Source")
|
LOGGER.warning("Group syncing is disabled for this Source")
|
||||||
return
|
return -1
|
||||||
groups = self._source.connection.extend.standard.paged_search(
|
groups = self._source.connection.extend.standard.paged_search(
|
||||||
search_base=self.base_dn_groups,
|
search_base=self.base_dn_groups,
|
||||||
search_filter=self._source.group_object_filter,
|
search_filter=self._source.group_object_filter,
|
||||||
search_scope=ldap3.SUBTREE,
|
search_scope=ldap3.SUBTREE,
|
||||||
attributes=ldap3.ALL_ATTRIBUTES,
|
attributes=ldap3.ALL_ATTRIBUTES,
|
||||||
)
|
)
|
||||||
|
group_count = 0
|
||||||
for group in groups:
|
for group in groups:
|
||||||
attributes = group.get("attributes", {})
|
attributes = group.get("attributes", {})
|
||||||
if self._source.object_uniqueness_field not in attributes:
|
if self._source.object_uniqueness_field not in attributes:
|
||||||
|
@ -68,18 +69,21 @@ class LDAPSynchronizer:
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"Synced group", group=attributes.get("name", ""), created=created
|
"Synced group", group=attributes.get("name", ""), created=created
|
||||||
)
|
)
|
||||||
|
group_count += 1
|
||||||
|
return group_count
|
||||||
|
|
||||||
def sync_users(self):
|
def sync_users(self) -> int:
|
||||||
"""Iterate over all LDAP Users and create passbook_core.User instances"""
|
"""Iterate over all LDAP Users and create passbook_core.User instances"""
|
||||||
if not self._source.sync_users:
|
if not self._source.sync_users:
|
||||||
LOGGER.warning("User syncing is disabled for this Source")
|
LOGGER.warning("User syncing is disabled for this Source")
|
||||||
return
|
return -1
|
||||||
users = self._source.connection.extend.standard.paged_search(
|
users = self._source.connection.extend.standard.paged_search(
|
||||||
search_base=self.base_dn_users,
|
search_base=self.base_dn_users,
|
||||||
search_filter=self._source.user_object_filter,
|
search_filter=self._source.user_object_filter,
|
||||||
search_scope=ldap3.SUBTREE,
|
search_scope=ldap3.SUBTREE,
|
||||||
attributes=ldap3.ALL_ATTRIBUTES,
|
attributes=ldap3.ALL_ATTRIBUTES,
|
||||||
)
|
)
|
||||||
|
user_count = 0
|
||||||
for user in users:
|
for user in users:
|
||||||
attributes = user.get("attributes", {})
|
attributes = user.get("attributes", {})
|
||||||
if self._source.object_uniqueness_field not in attributes:
|
if self._source.object_uniqueness_field not in attributes:
|
||||||
|
@ -109,6 +113,8 @@ class LDAPSynchronizer:
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"Synced User", user=attributes.get("name", ""), created=created
|
"Synced User", user=attributes.get("name", ""), created=created
|
||||||
)
|
)
|
||||||
|
user_count += 1
|
||||||
|
return user_count
|
||||||
|
|
||||||
def sync_membership(self):
|
def sync_membership(self):
|
||||||
"""Iterate over all Users and assign Groups using memberOf Field"""
|
"""Iterate over all Users and assign Groups using memberOf Field"""
|
||||||
|
|
|
@ -2,26 +2,37 @@
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from ldap3.core.exceptions import LDAPException
|
||||||
|
|
||||||
|
from passbook.lib.tasks import MonitoredTask, TaskResult, TaskResultStatus
|
||||||
from passbook.root.celery import CELERY_APP
|
from passbook.root.celery import CELERY_APP
|
||||||
from passbook.sources.ldap.models import LDAPSource
|
from passbook.sources.ldap.models import LDAPSource
|
||||||
from passbook.sources.ldap.sync import LDAPSynchronizer
|
from passbook.sources.ldap.sync import LDAPSynchronizer
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task()
|
||||||
def sync():
|
def ldap_sync_all():
|
||||||
"""Sync all sources"""
|
"""Sync all sources"""
|
||||||
for source in LDAPSource.objects.filter(enabled=True):
|
for source in LDAPSource.objects.filter(enabled=True):
|
||||||
sync_single.delay(source.pk)
|
ldap_sync.delay(source.pk)
|
||||||
|
|
||||||
|
|
||||||
@CELERY_APP.task()
|
@CELERY_APP.task(bind=True, base=MonitoredTask)
|
||||||
def sync_single(source_pk):
|
def ldap_sync(self: MonitoredTask, source_pk: int):
|
||||||
"""Sync a single source"""
|
"""Sync a single source"""
|
||||||
source: LDAPSource = LDAPSource.objects.get(pk=source_pk)
|
source: LDAPSource = LDAPSource.objects.get(pk=source_pk)
|
||||||
syncer = LDAPSynchronizer(source)
|
try:
|
||||||
syncer.sync_users()
|
syncer = LDAPSynchronizer(source)
|
||||||
syncer.sync_groups()
|
user_count = syncer.sync_users()
|
||||||
syncer.sync_membership()
|
group_count = syncer.sync_groups()
|
||||||
cache_key = source.state_cache_prefix("last_sync")
|
syncer.sync_membership()
|
||||||
cache.set(cache_key, time(), timeout=60 * 60)
|
cache_key = source.state_cache_prefix("last_sync")
|
||||||
|
cache.set(cache_key, time(), timeout=60 * 60)
|
||||||
|
self.set_status(
|
||||||
|
TaskResult(
|
||||||
|
TaskResultStatus.SUCCESSFUL,
|
||||||
|
[f"Synced {user_count} users", f"Synced {group_count} groups"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except LDAPException as exc:
|
||||||
|
self.set_status(TaskResult(TaskResultStatus.ERROR, [str(exc)], exc))
|
||||||
|
|
|
@ -7,7 +7,7 @@ from passbook.core.models import Group, User
|
||||||
from passbook.providers.oauth2.generators import generate_client_secret
|
from passbook.providers.oauth2.generators import generate_client_secret
|
||||||
from passbook.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
from passbook.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
||||||
from passbook.sources.ldap.sync import LDAPSynchronizer
|
from passbook.sources.ldap.sync import LDAPSynchronizer
|
||||||
from passbook.sources.ldap.tasks import sync
|
from passbook.sources.ldap.tasks import ldap_sync_all
|
||||||
from passbook.sources.ldap.tests.utils import _build_mock_connection
|
from passbook.sources.ldap.tests.utils import _build_mock_connection
|
||||||
|
|
||||||
LDAP_PASSWORD = generate_client_secret()
|
LDAP_PASSWORD = generate_client_secret()
|
||||||
|
@ -48,4 +48,4 @@ class LDAPSyncTests(TestCase):
|
||||||
@patch("passbook.sources.ldap.models.LDAPSource.connection", LDAP_CONNECTION_PATCH)
|
@patch("passbook.sources.ldap.models.LDAPSource.connection", LDAP_CONNECTION_PATCH)
|
||||||
def test_tasks(self):
|
def test_tasks(self):
|
||||||
"""Test Scheduled tasks"""
|
"""Test Scheduled tasks"""
|
||||||
sync()
|
ldap_sync_all.delay()
|
||||||
|
|
Reference in New Issue