Merge pull request !2768 from rhodecode-enterprise-ce feature/RCCE-226LDAP-Active-Directory]---periodic-sync-feature,-sync-active/inactive-user-status

feature: initial implementation of LDAP/AD sync
This commit is contained in:
Andrii Verbytskyi 2025-08-04 10:45:27 +00:00
commit 51bb9e8d14
8 changed files with 433 additions and 213 deletions

View file

@ -21,28 +21,22 @@ RhodeCode authentication plugin for LDAP
"""
import logging
import traceback
import colander
from rhodecode.authentication.plugins.services.ldap_dao import LdapDao
from rhodecode.translation import _
from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin, AuthLdapBase, hybrid_property
from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin, hybrid_property
from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase, TwoFactorAuthnPluginSettingsSchemaMixin
from rhodecode.authentication.routes import AuthnPluginResourceBase
from rhodecode.lib.colander_utils import strip_whitespace
from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
from rhodecode.lib.exceptions import LdapUsernameError, LdapPasswordError, LdapImportError
from rhodecode.lib.str_utils import safe_str
from rhodecode.model.db import User
from rhodecode.model.validators import Missing
log = logging.getLogger(__name__)
try:
import ldap
except ImportError:
# means that python-ldap is not installed, we use Missing object to mark
# ldap lib is Missing
ldap = Missing
class LdapError(Exception):
pass
@ -61,188 +55,6 @@ class LdapAuthnResource(AuthnPluginResourceBase):
pass
class AuthLdap(AuthLdapBase):
default_tls_cert_dir = "/etc/openldap/cacerts"
scope_labels = {
ldap.SCOPE_BASE: "SCOPE_BASE",
ldap.SCOPE_ONELEVEL: "SCOPE_ONELEVEL",
ldap.SCOPE_SUBTREE: "SCOPE_SUBTREE",
}
def __init__(
self,
server,
base_dn,
port=389,
bind_dn="",
bind_pass="",
tls_kind="PLAIN",
tls_reqcert="DEMAND",
tls_cert_file=None,
tls_cert_dir=None,
ldap_version=3,
search_scope="SUBTREE",
attr_login="uid",
ldap_filter="",
timeout=None,
):
if ldap == Missing:
raise LdapImportError("Missing or incompatible ldap library")
self.debug = False
self.timeout = timeout or 60 * 5
self.ldap_version = ldap_version
self.ldap_server_type = "ldap"
self.TLS_KIND = tls_kind
if self.TLS_KIND == "LDAPS":
port = port or 636
self.ldap_server_type += "s"
OPT_X_TLS_DEMAND = 2
self.TLS_REQCERT = getattr(ldap, "OPT_X_TLS_%s" % tls_reqcert, OPT_X_TLS_DEMAND)
self.TLS_CERT_FILE = tls_cert_file or ""
self.TLS_CERT_DIR = tls_cert_dir or self.default_tls_cert_dir
# split server into list
self.SERVER_ADDRESSES = self._get_server_list(server)
self.LDAP_SERVER_PORT = port
# USE FOR READ ONLY BIND TO LDAP SERVER
self.attr_login = attr_login
self.LDAP_BIND_DN = safe_str(bind_dn)
self.LDAP_BIND_PASS = safe_str(bind_pass)
self.SEARCH_SCOPE = getattr(ldap, "SCOPE_%s" % search_scope)
self.BASE_DN = safe_str(base_dn)
self.LDAP_FILTER = safe_str(ldap_filter)
def _get_ldap_conn(self):
if self.debug:
ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
if self.TLS_CERT_FILE and hasattr(ldap, "OPT_X_TLS_CACERTFILE"):
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.TLS_CERT_FILE)
elif hasattr(ldap, "OPT_X_TLS_CACERTDIR"):
ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.TLS_CERT_DIR)
if self.TLS_KIND != "PLAIN":
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
# init connection now
ldap_servers = self._build_servers(self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
log.debug("initializing LDAP connection to:%s", ldap_servers)
ldap_conn = ldap.initialize(ldap_servers)
ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
ldap_conn.timeout = self.timeout
if self.ldap_version == 2:
ldap_conn.protocol = ldap.VERSION2
else:
ldap_conn.protocol = ldap.VERSION3
if self.TLS_KIND == "START_TLS":
ldap_conn.start_tls_s()
if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
log.debug("Trying simple_bind with password and given login DN: %r", self.LDAP_BIND_DN)
ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
log.debug("simple_bind successful")
return ldap_conn
def fetch_attrs_from_simple_bind(self, ldap_conn, dn, username, password):
scope = ldap.SCOPE_BASE
scope_label = self.scope_labels.get(scope)
ldap_filter = "(objectClass=*)"
try:
log.debug(
"Trying authenticated search bind with dn: %r SCOPE: %s (and filter: %s)", dn, scope_label, ldap_filter
)
ldap_conn.simple_bind_s(dn, safe_str(password))
response = ldap_conn.search_ext_s(dn, scope, ldap_filter, attrlist=["*", "+"])
if not response:
log.error("search bind returned empty results: %r", response)
return {}
else:
_dn, attrs = response[0]
return attrs
except ldap.INVALID_CREDENTIALS:
log.debug("LDAP rejected password for user '%s': %s, org_exc:", username, dn, exc_info=True)
def authenticate_ldap(self, username, password):
"""
Authenticate a user via LDAP and return his/her LDAP properties.
Raises AuthenticationError if the credentials are rejected, or
EnvironmentError if the LDAP server can't be reached.
:param username: username
:param password: password
"""
uid = self.get_uid(username, self.SERVER_ADDRESSES)
user_attrs = {}
dn = ""
self.validate_password(username, password)
self.validate_username(username)
scope_label = self.scope_labels.get(self.SEARCH_SCOPE)
ldap_conn = None
try:
ldap_conn = self._get_ldap_conn()
filter_ = "(&{}({}={}))".format(self.LDAP_FILTER, self.attr_login, username)
log.debug("Authenticating %r filter %s and scope: %s", self.BASE_DN, filter_, scope_label)
ldap_objects = ldap_conn.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, filter_, attrlist=["*", "+"])
if not ldap_objects:
log.debug("No matching LDAP objects for authentication of UID:'%s' username:(%s)", uid, username)
raise ldap.NO_SUCH_OBJECT()
log.debug("Found %s matching ldap object[s], trying to authenticate on each one now...", len(ldap_objects))
for dn, _attrs in ldap_objects:
if dn is None:
continue
user_attrs = self.fetch_attrs_from_simple_bind(ldap_conn, dn, username, password)
if user_attrs:
log.debug("Got authenticated user attributes from DN:%s", dn)
break
else:
raise LdapPasswordError(f"Failed to authenticate user `{username}` with given password")
except ldap.NO_SUCH_OBJECT:
log.debug("LDAP says no such user '%s' (%s), org_exc:", uid, username, exc_info=True)
raise LdapUsernameError("Unable to find user")
except ldap.SERVER_DOWN:
org_exc = traceback.format_exc()
raise LdapConnectionError("LDAP can't access authentication server, org_exc:%s" % org_exc)
finally:
if ldap_conn:
log.debug("ldap: connection release")
try:
ldap_conn.unbind_s()
except Exception:
# for any reason this can raise exception we must catch it
# to not crush the server
pass
return dn, user_attrs
class LdapSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSettingsSchemaBase):
tls_kind_choices = ["PLAIN", "LDAPS", "START_TLS"]
tls_reqcert_choices = ["NEVER", "ALLOW", "TRY", "DEMAND", "HARD"]
@ -338,7 +150,7 @@ class LdapSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSet
)
tls_cert_dir = colander.SchemaNode(
colander.String(),
default=AuthLdap.default_tls_cert_dir,
default=LdapDao.default_tls_cert_dir,
description=_(
"This specifies the path of a directory that contains individual CA certificates in separate files."
),
@ -420,6 +232,38 @@ class LdapSettingsSchema(TwoFactorAuthnPluginSettingsSchemaMixin, AuthnPluginSet
title=_("Last Name Attribute"),
widget="string",
)
sync_active_directory_users = colander.SchemaNode(
colander.Bool(),
default=False,
description=_(
"A cron job that periodically retrieves all users from an LDAP-based Active Directory server and syncs "
"them with the RhodeCode database.\n"
"Note: This feature is specific to Active Directory. Enabling it for other types of LDAP servers will have no effect."
),
missing=False,
preparer=strip_whitespace,
title=_("Sync Active Directory Users"),
widget="bool",
)
def get_ldap_args(settings: dict):
return {
"server": settings.get("host", ""),
"base_dn": settings.get("base_dn", ""),
"port": settings.get("port"),
"bind_dn": settings.get("dn_user"),
"bind_pass": settings.get("dn_pass"),
"tls_kind": settings.get("tls_kind"),
"tls_reqcert": settings.get("tls_reqcert"),
"tls_cert_file": settings.get("tls_cert_file"),
"tls_cert_dir": settings.get("tls_cert_dir"),
"search_scope": settings.get("search_scope"),
"attr_login": settings.get("attr_login"),
"ldap_version": 3,
"ldap_filter": settings.get("filter"),
"timeout": settings.get("timeout"),
}
class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
@ -509,30 +353,15 @@ class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
log.debug("Empty username or password skipping...")
return None
ldap_args = {
"server": settings.get("host", ""),
"base_dn": settings.get("base_dn", ""),
"port": settings.get("port"),
"bind_dn": settings.get("dn_user"),
"bind_pass": settings.get("dn_pass"),
"tls_kind": settings.get("tls_kind"),
"tls_reqcert": settings.get("tls_reqcert"),
"tls_cert_file": settings.get("tls_cert_file"),
"tls_cert_dir": settings.get("tls_cert_dir"),
"search_scope": settings.get("search_scope"),
"attr_login": settings.get("attr_login"),
"ldap_version": 3,
"ldap_filter": settings.get("filter"),
"timeout": settings.get("timeout"),
}
ldap_args = get_ldap_args(settings)
ldap_attrs = self.try_dynamic_binding(username, password, ldap_args)
log.debug("Checking for ldap authentication.")
try:
auth_ldap = AuthLdap(**ldap_args)
(user_dn, ldap_attrs) = auth_ldap.authenticate_ldap(username, password)
ldap_dao = LdapDao(**ldap_args)
(user_dn, ldap_attrs) = ldap_dao.authenticate_ldap(username, password)
log.debug("Got ldap DN response %s", user_dn)
def get_ldap_attr(k) -> str:

View file

@ -0,0 +1,219 @@
import logging
import traceback
from dataclasses import dataclass
from typing import List, Optional
from rhodecode.model.validators import Missing
from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, LdapPasswordError, LdapImportError
from rhodecode.authentication.base import AuthLdapBase
from rhodecode.lib.str_utils import safe_str
try:
import ldap
except ImportError:
# means that python-ldap is not installed, we use Missing object to mark
# ldap lib is Missing
ldap = Missing
class LdapDao(AuthLdapBase):
default_tls_cert_dir = "/etc/openldap/cacerts"
scope_labels = {
ldap.SCOPE_BASE: "SCOPE_BASE",
ldap.SCOPE_ONELEVEL: "SCOPE_ONELEVEL",
ldap.SCOPE_SUBTREE: "SCOPE_SUBTREE",
}
def __init__(
self,
server,
base_dn,
port=389,
bind_dn="",
bind_pass="",
tls_kind="PLAIN",
tls_reqcert="DEMAND",
tls_cert_file=None,
tls_cert_dir=None,
ldap_version=3,
search_scope="SUBTREE",
attr_login="uid",
ldap_filter="",
timeout=None,
):
if ldap == Missing:
raise LdapImportError("Missing or incompatible ldap library")
self.log = logging.getLogger(LdapDao.__name__)
self.debug = False
self.timeout = timeout or 60 * 5
self.ldap_version = ldap_version
self.ldap_server_type = "ldap"
self.TLS_KIND = tls_kind
if self.TLS_KIND == "LDAPS":
port = port or 636
self.ldap_server_type += "s"
OPT_X_TLS_DEMAND = 2
self.TLS_REQCERT = getattr(ldap, "OPT_X_TLS_%s" % tls_reqcert, OPT_X_TLS_DEMAND)
self.TLS_CERT_FILE = tls_cert_file or ""
self.TLS_CERT_DIR = tls_cert_dir or self.default_tls_cert_dir
# split server into list
self.SERVER_ADDRESSES = self._get_server_list(server)
self.LDAP_SERVER_PORT = port
# USE FOR READ ONLY BIND TO LDAP SERVER
self.attr_login = attr_login
self.LDAP_BIND_DN = safe_str(bind_dn)
self.LDAP_BIND_PASS = safe_str(bind_pass)
self.SEARCH_SCOPE = getattr(ldap, "SCOPE_%s" % search_scope)
self.BASE_DN = safe_str(base_dn)
self.LDAP_FILTER = safe_str(ldap_filter)
def _get_ldap_conn(self):
if self.debug:
ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
if self.TLS_CERT_FILE and hasattr(ldap, "OPT_X_TLS_CACERTFILE"):
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.TLS_CERT_FILE)
elif hasattr(ldap, "OPT_X_TLS_CACERTDIR"):
ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.TLS_CERT_DIR)
if self.TLS_KIND != "PLAIN":
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
# init connection now
ldap_servers = self._build_servers(self.ldap_server_type, self.SERVER_ADDRESSES, self.LDAP_SERVER_PORT)
self.log.debug("initializing LDAP connection to:%s", ldap_servers)
ldap_conn = ldap.initialize(ldap_servers)
ldap_conn.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
ldap_conn.set_option(ldap.OPT_TIMEOUT, self.timeout)
ldap_conn.timeout = self.timeout
if self.ldap_version == 2:
ldap_conn.protocol = ldap.VERSION2
else:
ldap_conn.protocol = ldap.VERSION3
if self.TLS_KIND == "START_TLS":
ldap_conn.start_tls_s()
if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
self.log.debug("Trying simple_bind with password and given login DN: %r", self.LDAP_BIND_DN)
ldap_conn.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
self.log.debug("simple_bind successful")
return ldap_conn
def fetch_all(self, ldap_filter: str = "(objectClass=*)", attributes: List[str] = None) -> Optional[List[dict]]:
ldap_conn = None
try:
if attributes is None:
attributes = ["*", "+"]
ldap_conn = self._get_ldap_conn()
self.log.debug("fetching users for DN: %s", self.BASE_DN)
ldap_objects = ldap_conn.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, ldap_filter, attrlist=attributes)
return [attrs for _, attrs in ldap_objects]
except Exception as e:
self.log.error("Error fetching users for DN: %s. Error: %s", self.BASE_DN, str(e))
return None
finally:
self._releease_connection(ldap_conn)
def _releease_connection(self, ldap_conn):
if ldap_conn:
self.log.debug("ldap: connection release")
try:
ldap_conn.unbind_s()
except Exception as e:
# for any reason this can raise exception we must catch it
# to not crush the server
self.log.warning("unbind_s failed, error: %s", str(e))
def _fetch_attrs_from_simple_bind(self, ldap_conn, dn, username, password):
scope = ldap.SCOPE_BASE
scope_label = self.scope_labels.get(scope)
ldap_filter = "(objectClass=*)"
try:
self.log.debug(
"Trying authenticated search bind with dn: %r SCOPE: %s (and filter: %s)", dn, scope_label, ldap_filter
)
ldap_conn.simple_bind_s(dn, safe_str(password))
response = ldap_conn.search_ext_s(dn, scope, ldap_filter, attrlist=["*", "+"])
if not response:
self.log.error("search bind returned empty results: %r", response)
return {}
else:
_dn, attrs = response[0]
return attrs
except ldap.INVALID_CREDENTIALS:
self.log.debug("LDAP rejected password for user '%s': %s, org_exc:", username, dn, exc_info=True)
def authenticate_ldap(self, username, password):
"""
Authenticate a user via LDAP and return his/her LDAP properties.
Raises AuthenticationError if the credentials are rejected, or
EnvironmentError if the LDAP server can't be reached.
:param username: username
:param password: password
"""
uid = self.get_uid(username, self.SERVER_ADDRESSES)
self.validate_password(username, password)
self.validate_username(username)
scope_label = self.scope_labels.get(self.SEARCH_SCOPE)
ldap_conn = None
try:
ldap_conn = self._get_ldap_conn()
filter_ = "(&{}({}={}))".format(self.LDAP_FILTER, self.attr_login, username)
self.log.debug("Authenticating %r filter %s and scope: %s", self.BASE_DN, filter_, scope_label)
ldap_objects = ldap_conn.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE, filter_, attrlist=["*", "+"])
if not ldap_objects:
self.log.debug("No matching LDAP objects for authentication of UID:'%s' username:(%s)", uid, username)
raise ldap.NO_SUCH_OBJECT()
self.log.debug(
"Found %s matching ldap object[s], trying to authenticate on each one now...", len(ldap_objects)
)
for dn, _attrs in ldap_objects:
if dn is None:
continue
user_attrs = self._fetch_attrs_from_simple_bind(ldap_conn, dn, username, password)
if user_attrs:
self.log.debug("Got authenticated user attributes from DN:%s", dn)
break
else:
raise LdapPasswordError(f"Failed to authenticate user `{username}` with given password")
except ldap.NO_SUCH_OBJECT:
self.log.debug("LDAP says no such user '%s' (%s), org_exc:", uid, username, exc_info=True)
raise LdapUsernameError("Unable to find user")
except ldap.SERVER_DOWN:
org_exc = traceback.format_exc()
raise LdapConnectionError("LDAP can't access authentication server, org_exc:%s" % org_exc)
finally:
self._releease_connection(ldap_conn)
return dn, user_attrs

View file

@ -0,0 +1,52 @@
import unittest
from unittest.mock import patch, MagicMock
import ldap
from rhodecode.authentication.plugins.services.ldap_dao import LdapDao
from rhodecode.lib.diff_match_patch import patch_obj
@patch("rhodecode.authentication.plugins.services.ldap_dao.ldap")
class TestLdapDao(unittest.TestCase):
def setUp(self):
self._server_list = "test_srv1,test_srv2,test_srv3"
self._base_dn = "test_dn"
self.ldap_dao = LdapDao(
server=self._server_list,
base_dn=self._base_dn,
)
@patch.object(LdapDao, "_get_ldap_conn")
def test_fetch_all_from_ldap_server(self, _get_ldap_conn_mock, ldap_mock):
conn = MagicMock()
no_objects_in_ldap_server = []
conn.search_ext_s.return_value = no_objects_in_ldap_server
_get_ldap_conn_mock.return_value = conn
returned_value = self.ldap_dao.fetch_all()
conn.search_ext_s.assert_called_once_with(self._base_dn, 2, "(objectClass=*)", attrlist=["*", "+"])
assert returned_value == no_objects_in_ldap_server
@patch.object(LdapDao, "_get_ldap_conn")
def test_fetch_all_release_connection(self, _get_ldap_conn_mock, ldap_mock):
conn = MagicMock()
_get_ldap_conn_mock.return_value = conn
self.ldap_dao.fetch_all()
conn.unbind_s.assert_called_once()
@patch.object(LdapDao, "_get_ldap_conn")
def test_fetch_all_release_connection_on_error(self, _get_ldap_conn_mock, ldap_mock):
conn = MagicMock()
_get_ldap_conn_mock.return_value = conn
conn.search_ext_s.side_effect = Exception("Test exception")
res = self.ldap_dao.fetch_all()
assert res is None
conn.unbind_s.assert_called_once()

View file

@ -0,0 +1,63 @@
import unittest
from unittest.mock import MagicMock, patch
import pytest
from rhodecode.authentication.views import AuthnPluginViewBase
from rhodecode.lib.celerylib import tasks
class TestAuthPluginView:
@pytest.mark.parametrize(
"sync_active_directory_users, expected_celery_task",
[
(
True,
tasks.schedule_sync_ldap_ad_users_producer,
),
(
False,
tasks.unschedule_sync_ldap_ad_users_producer,
),
],
)
@patch("rhodecode.apps._base.BaseAppView.__init__", return_value=None)
@patch("rhodecode.authentication.views.h")
@patch("rhodecode.authentication.views.SettingsModel")
@patch.object(AuthnPluginViewBase, "load_default_context")
@patch("rhodecode.authentication.views.run_task")
def test_enable_ad_sync_schedule(
self,
run_task_mock,
load_default_context,
settings_model,
h,
_init_,
sync_active_directory_users,
expected_celery_task,
):
view = self._get_instance()
settings_post = AuthnPluginViewBase.__dict__[ # ignore decorators
"settings_post"
].__wrapped__.__wrapped__.__wrapped__
schema = MagicMock()
schema.deserialize.return_value = {"sync_active_directory_users": sync_active_directory_users}
self._plugin.get_settings_schema.return_value = schema
settings_post(view)
run_task_mock.assert_called_once_with(expected_celery_task)
def _get_instance(self):
# since base __init__ was patched, there is a need to inject mocks manually
self._context = MagicMock()
self._request = MagicMock()
self._plugin = MagicMock()
view = AuthnPluginViewBase(self._context, self._request)
view.request = self._request
view.plugin = self._plugin
view.context = self._context
return view

View file

@ -28,6 +28,7 @@ from rhodecode.apps._base import BaseAppView
from rhodecode.authentication.base import get_authn_registry
from rhodecode.lib import helpers as h
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, CSRFRequired
from rhodecode.lib.celerylib import run_task, tasks
from rhodecode.model.forms import AuthSettingsForm
from rhodecode.model.meta import Session
from rhodecode.model.settings import SettingsModel
@ -90,6 +91,9 @@ class AuthnPluginViewBase(BaseAppView):
# Store validated data.
for name, value in valid_data.items():
self.plugin.create_or_update_setting(name, value)
if name == "sync_active_directory_users":
self._handle_ad_users_sync_schedule(name, value)
Session().commit()
SettingsModel().invalidate_settings_cache()
@ -102,6 +106,17 @@ class AuthnPluginViewBase(BaseAppView):
return HTTPFound(redirect_to)
def _handle_ad_users_sync_schedule(self, name: str, enabled: bool):
if name != "sync_active_directory_users":
return
if enabled:
log.debug("Scheduling AD users sync")
run_task(tasks.schedule_sync_ldap_ad_users_producer)
else:
log.debug("Removing schedule for AD users sync")
run_task(tasks.unschedule_sync_ldap_ad_users_producer)
class AuthSettingsView(BaseAppView):
def load_default_context(self):

View file

@ -24,6 +24,7 @@ by celery daemon
import os
import time
from celery import current_app
from pyramid_mailer.mailer import Mailer
from pyramid_mailer.message import Message
from email.utils import formatdate
@ -35,7 +36,7 @@ from rhodecode.lib import hooks_base
from rhodecode.lib.utils import adopt_for_celery
from rhodecode.lib.utils2 import safe_int, str2bool, aslist
from rhodecode.lib.statsd_client import StatsdClient
from rhodecode.model.db import true, null, Session, IntegrityError, Repository, RepoGroup, User
from rhodecode.model.db import true, null, Session, IntegrityError, Repository, RepoGroup, User, ScheduleEntry
from rhodecode.model.permission import PermissionModel
@ -417,6 +418,47 @@ def beat_check(*args, **kwargs):
return time.time()
@async_task(ignore_result=True)
def schedule_sync_ldap_ad_users_producer():
log = get_logger(schedule_sync_ldap_ad_users_producer)
try:
from rc_ee.lib.celerylib.scheduler import RcScheduler
except ImportError:
log.error("Attempt to schedule EE feature")
return
scheduler = RcScheduler(app=current_app)
scheduler.sync()
task_name = "rc_ee.lib.celerylib.tasks.sync_ldap_ad_users_producer"
if task_name not in scheduler.schedule:
entries = {
task_name: {
"task": task_name,
"schedule_type": "crontab",
"schedule_value": {"hour": 0, "minute": 4},
"options": {"expires": 12 * 3600},
}
}
scheduler.update_from_dict(entries)
@async_task(ignore_result=True)
def unschedule_sync_ldap_ad_users_producer():
log = get_logger(unschedule_sync_ldap_ad_users_producer)
try:
from rc_ee.lib.celerylib.scheduler import RcScheduler
except ImportError:
log.error("Attempt to unschedule EE feature")
return
task_name = "rc_ee.lib.celerylib.tasks.sync_ldap_ad_users_producer"
existing_task = ScheduleEntry.query().filter(ScheduleEntry.task_dot_notation == task_name).first()
if existing_task:
Session().delete(existing_task)
@async_task
@adopt_for_celery
def repo_size(extras):