mirror of https://github.com/jumpserver/jumpserver
feat: LDAP HA
parent
512e727ac6
commit
c2784c44ad
|
@ -1,6 +1,6 @@
|
||||||
# coding:utf-8
|
# coding:utf-8
|
||||||
#
|
#
|
||||||
|
import abc
|
||||||
import ldap
|
import ldap
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
||||||
|
@ -15,13 +15,16 @@ from .base import JMSBaseAuthBackend
|
||||||
logger = _LDAPConfig.get_logger()
|
logger = _LDAPConfig.get_logger()
|
||||||
|
|
||||||
|
|
||||||
class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend):
|
class LDAPBaseBackend(LDAPBackend):
|
||||||
"""
|
|
||||||
Override this class to override _LDAPUser to LDAPUser
|
@abc.abstractmethod
|
||||||
"""
|
def is_enabled(self):
|
||||||
@staticmethod
|
raise NotImplementedError('is_enabled')
|
||||||
def is_enabled():
|
|
||||||
return settings.AUTH_LDAP
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def is_user_login_only_in_users(self):
|
||||||
|
raise NotImplementedError('is_authenticated')
|
||||||
|
|
||||||
def get_or_build_user(self, username, ldap_user):
|
def get_or_build_user(self, username, ldap_user):
|
||||||
"""
|
"""
|
||||||
|
@ -56,38 +59,6 @@ class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend):
|
||||||
|
|
||||||
return user, built
|
return user, built
|
||||||
|
|
||||||
def pre_check(self, username, password):
|
|
||||||
if not settings.AUTH_LDAP:
|
|
||||||
error = 'Not enabled auth ldap'
|
|
||||||
return False, error
|
|
||||||
if not username:
|
|
||||||
error = 'Username is None'
|
|
||||||
return False, error
|
|
||||||
if not password:
|
|
||||||
error = 'Password is None'
|
|
||||||
return False, error
|
|
||||||
if settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS:
|
|
||||||
user_model = self.get_user_model()
|
|
||||||
exist = user_model.objects.filter(username=username).exists()
|
|
||||||
if not exist:
|
|
||||||
error = 'user ({}) is not in the user list'.format(username)
|
|
||||||
return False, error
|
|
||||||
return True, ''
|
|
||||||
|
|
||||||
def authenticate(self, request=None, username=None, password=None, **kwargs):
|
|
||||||
logger.info('Authentication LDAP backend')
|
|
||||||
if username is None or password is None:
|
|
||||||
logger.info('No username or password')
|
|
||||||
return None
|
|
||||||
match, msg = self.pre_check(username, password)
|
|
||||||
if not match:
|
|
||||||
logger.info('Authenticate failed: {}'.format(msg))
|
|
||||||
return None
|
|
||||||
ldap_user = LDAPUser(self, username=username.strip(), request=request)
|
|
||||||
user = self.authenticate_ldap_user(ldap_user, password)
|
|
||||||
logger.info('Authenticate user: {}'.format(user))
|
|
||||||
return user if self.user_can_authenticate(user) else None
|
|
||||||
|
|
||||||
def get_user(self, user_id):
|
def get_user(self, user_id):
|
||||||
user = None
|
user = None
|
||||||
try:
|
try:
|
||||||
|
@ -111,6 +82,67 @@ class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBackend):
|
||||||
user = ldap_user.populate_user()
|
user = ldap_user.populate_user()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def authenticate(self, request=None, username=None, password=None, **kwargs):
|
||||||
|
logger.info('Authentication LDAP backend')
|
||||||
|
if username is None or password is None:
|
||||||
|
logger.info('No username or password')
|
||||||
|
return None
|
||||||
|
match, msg = self.pre_check(username, password)
|
||||||
|
if not match:
|
||||||
|
logger.info('Authenticate failed: {}'.format(msg))
|
||||||
|
return None
|
||||||
|
ldap_user = LDAPUser(self, username=username.strip(), request=request)
|
||||||
|
user = self.authenticate_ldap_user(ldap_user, password)
|
||||||
|
logger.info('Authenticate user: {}'.format(user))
|
||||||
|
return user if self.user_can_authenticate(user) else None
|
||||||
|
|
||||||
|
def pre_check(self, username, password):
|
||||||
|
if not self.is_enabled():
|
||||||
|
error = 'Not enabled auth ldap'
|
||||||
|
return False, error
|
||||||
|
if not username:
|
||||||
|
error = 'Username is None'
|
||||||
|
return False, error
|
||||||
|
if not password:
|
||||||
|
error = 'Password is None'
|
||||||
|
return False, error
|
||||||
|
if self.is_user_login_only_in_users:
|
||||||
|
user_model = self.get_user_model()
|
||||||
|
exist = user_model.objects.filter(username=username).exists()
|
||||||
|
if not exist:
|
||||||
|
error = 'user ({}) is not in the user list'.format(username)
|
||||||
|
return False, error
|
||||||
|
return True, ''
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPAuthorizationBackend(JMSBaseAuthBackend, LDAPBaseBackend):
|
||||||
|
"""
|
||||||
|
Override this class to override _LDAPUser to LDAPUser
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_enabled():
|
||||||
|
return settings.AUTH_LDAP
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_user_login_only_in_users(self):
|
||||||
|
return settings.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPHAAuthorizationBackend(JMSBaseAuthBackend, LDAPBaseBackend):
|
||||||
|
"""
|
||||||
|
Override this class to override _LDAPUser to LDAPUser
|
||||||
|
"""
|
||||||
|
settings_prefix = "AUTH_LDAP_HA_"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_enabled():
|
||||||
|
return settings.AUTH_LDAP_HA
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_user_login_only_in_users(self):
|
||||||
|
return settings.AUTH_LDAP_HA_USER_LOGIN_ONLY_IN_USERS
|
||||||
|
|
||||||
|
|
||||||
class LDAPUser(_LDAPUser):
|
class LDAPUser(_LDAPUser):
|
||||||
|
|
||||||
|
@ -126,13 +158,18 @@ class LDAPUser(_LDAPUser):
|
||||||
configuration in the settings.py file
|
configuration in the settings.py file
|
||||||
is configured with a `lambda` problem value
|
is configured with a `lambda` problem value
|
||||||
"""
|
"""
|
||||||
|
if isinstance(self.backend, LDAPAuthorizationBackend):
|
||||||
|
search_filter = settings.AUTH_LDAP_SEARCH_FILTER
|
||||||
|
search_ou = settings.AUTH_LDAP_SEARCH_OU
|
||||||
|
else:
|
||||||
|
search_filter = settings.AUTH_LDAP_HA_SEARCH_FILTER
|
||||||
|
search_ou = settings.AUTH_LDAP_HA_SEARCH_OU
|
||||||
user_search_union = [
|
user_search_union = [
|
||||||
LDAPSearch(
|
LDAPSearch(
|
||||||
USER_SEARCH, ldap.SCOPE_SUBTREE,
|
USER_SEARCH, ldap.SCOPE_SUBTREE,
|
||||||
settings.AUTH_LDAP_SEARCH_FILTER
|
search_filter
|
||||||
)
|
)
|
||||||
for USER_SEARCH in str(settings.AUTH_LDAP_SEARCH_OU).split("|")
|
for USER_SEARCH in str(search_ou).split("|")
|
||||||
]
|
]
|
||||||
|
|
||||||
search = LDAPSearchUnion(*user_search_union)
|
search = LDAPSearchUnion(*user_search_union)
|
||||||
|
@ -169,7 +206,8 @@ class LDAPUser(_LDAPUser):
|
||||||
else:
|
else:
|
||||||
value = is_true(value)
|
value = is_true(value)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
logger.warning("{} does not have a value for the attribute {}".format(self.dn, attr))
|
logger.warning(
|
||||||
|
"{} does not have a value for the attribute {}".format(self.dn, attr))
|
||||||
else:
|
else:
|
||||||
if not hasattr(self._user, field):
|
if not hasattr(self._user, field):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -7,7 +7,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: JumpServer 0.3.3\n"
|
"Project-Id-Version: JumpServer 0.3.3\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-09-10 16:58+0800\n"
|
"POT-Creation-Date: 2024-09-11 18:15+0800\n"
|
||||||
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
"PO-Revision-Date: 2021-05-20 10:54+0800\n"
|
||||||
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
"Last-Translator: ibuler <ibuler@qq.com>\n"
|
||||||
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
"Language-Team: JumpServer team<ibuler@qq.com>\n"
|
||||||
|
@ -124,9 +124,10 @@ msgstr "成功: %s, 失败: %s, 总数: %s"
|
||||||
#: authentication/forms.py:28
|
#: authentication/forms.py:28
|
||||||
#: authentication/templates/authentication/login.html:362
|
#: authentication/templates/authentication/login.html:362
|
||||||
#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:52
|
#: settings/serializers/auth/ldap.py:26 settings/serializers/auth/ldap.py:52
|
||||||
#: settings/serializers/msg.py:37 settings/serializers/terminal.py:28
|
#: settings/serializers/auth/ldap_ha.py:34 settings/serializers/msg.py:37
|
||||||
#: terminal/serializers/storage.py:123 terminal/serializers/storage.py:142
|
#: settings/serializers/terminal.py:28 terminal/serializers/storage.py:123
|
||||||
#: users/forms/profile.py:21 users/serializers/user.py:144
|
#: terminal/serializers/storage.py:142 users/forms/profile.py:21
|
||||||
|
#: users/serializers/user.py:144
|
||||||
#: users/templates/users/_msg_user_created.html:13
|
#: users/templates/users/_msg_user_created.html:13
|
||||||
#: users/templates/users/user_password_verify.html:18
|
#: users/templates/users/user_password_verify.html:18
|
||||||
#: xpack/plugins/cloud/serializers/account_attrs.py:28
|
#: xpack/plugins/cloud/serializers/account_attrs.py:28
|
||||||
|
@ -549,7 +550,8 @@ msgstr "SSH 密钥推送方式"
|
||||||
#: accounts/models/automations/gather_account.py:58
|
#: accounts/models/automations/gather_account.py:58
|
||||||
#: accounts/serializers/account/backup.py:40
|
#: accounts/serializers/account/backup.py:40
|
||||||
#: accounts/serializers/automations/change_secret.py:58
|
#: accounts/serializers/automations/change_secret.py:58
|
||||||
#: settings/serializers/auth/ldap.py:100 settings/serializers/msg.py:45
|
#: settings/serializers/auth/ldap.py:100
|
||||||
|
#: settings/serializers/auth/ldap_ha.py:82 settings/serializers/msg.py:45
|
||||||
msgid "Recipient"
|
msgid "Recipient"
|
||||||
msgstr "收件人"
|
msgstr "收件人"
|
||||||
|
|
||||||
|
@ -707,8 +709,8 @@ msgstr "密码规则"
|
||||||
#: authentication/models/ssh_key.py:12
|
#: authentication/models/ssh_key.py:12
|
||||||
#: authentication/serializers/connect_token_secret.py:113
|
#: authentication/serializers/connect_token_secret.py:113
|
||||||
#: authentication/serializers/connect_token_secret.py:169 labels/models.py:11
|
#: authentication/serializers/connect_token_secret.py:169 labels/models.py:11
|
||||||
#: ops/mixin.py:28 ops/models/adhoc.py:20 ops/models/celery.py:15
|
#: ops/mixin.py:28 ops/models/adhoc.py:19 ops/models/celery.py:15
|
||||||
#: ops/models/celery.py:81 ops/models/job.py:142 ops/models/playbook.py:28
|
#: ops/models/celery.py:81 ops/models/job.py:142 ops/models/playbook.py:30
|
||||||
#: ops/serializers/job.py:18 orgs/models.py:82
|
#: ops/serializers/job.py:18 orgs/models.py:82
|
||||||
#: perms/models/asset_permission.py:61 rbac/models/role.py:29
|
#: perms/models/asset_permission.py:61 rbac/models/role.py:29
|
||||||
#: rbac/serializers/role.py:28 settings/models.py:35 settings/models.py:184
|
#: rbac/serializers/role.py:28 settings/models.py:35 settings/models.py:184
|
||||||
|
@ -1037,8 +1039,8 @@ msgid ""
|
||||||
msgstr "关联平台,可配置推送参数,如果不关联,将使用默认参数"
|
msgstr "关联平台,可配置推送参数,如果不关联,将使用默认参数"
|
||||||
|
|
||||||
#: accounts/serializers/account/virtual.py:19 assets/models/cmd_filter.py:40
|
#: accounts/serializers/account/virtual.py:19 assets/models/cmd_filter.py:40
|
||||||
#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:26
|
#: assets/models/cmd_filter.py:88 common/db/models.py:36 ops/models/adhoc.py:25
|
||||||
#: ops/models/job.py:158 ops/models/playbook.py:31 rbac/models/role.py:37
|
#: ops/models/job.py:158 ops/models/playbook.py:33 rbac/models/role.py:37
|
||||||
#: settings/models.py:40 terminal/models/applet/applet.py:46
|
#: settings/models.py:40 terminal/models/applet/applet.py:46
|
||||||
#: terminal/models/applet/applet.py:332 terminal/models/applet/host.py:143
|
#: terminal/models/applet/applet.py:332 terminal/models/applet/host.py:143
|
||||||
#: terminal/models/component/endpoint.py:25
|
#: terminal/models/component/endpoint.py:25
|
||||||
|
@ -2974,9 +2976,9 @@ msgstr "用户会话"
|
||||||
msgid "Offline user session"
|
msgid "Offline user session"
|
||||||
msgstr "下线用户会话"
|
msgstr "下线用户会话"
|
||||||
|
|
||||||
#: audits/serializers.py:33 ops/models/adhoc.py:25 ops/models/base.py:16
|
#: audits/serializers.py:33 ops/models/adhoc.py:24 ops/models/base.py:16
|
||||||
#: ops/models/base.py:53 ops/models/celery.py:87 ops/models/job.py:151
|
#: ops/models/base.py:53 ops/models/celery.py:87 ops/models/job.py:151
|
||||||
#: ops/models/job.py:240 ops/models/playbook.py:30
|
#: ops/models/job.py:240 ops/models/playbook.py:32
|
||||||
#: terminal/models/session/sharing.py:25
|
#: terminal/models/session/sharing.py:25
|
||||||
msgid "Creator"
|
msgid "Creator"
|
||||||
msgstr "创建者"
|
msgstr "创建者"
|
||||||
|
@ -3031,7 +3033,7 @@ msgstr "认证令牌"
|
||||||
#: audits/signal_handlers/login_log.py:37 authentication/notifications.py:73
|
#: audits/signal_handlers/login_log.py:37 authentication/notifications.py:73
|
||||||
#: authentication/views/login.py:78 notifications/backends/__init__.py:11
|
#: authentication/views/login.py:78 notifications/backends/__init__.py:11
|
||||||
#: settings/serializers/auth/wecom.py:11 settings/serializers/auth/wecom.py:16
|
#: settings/serializers/auth/wecom.py:11 settings/serializers/auth/wecom.py:16
|
||||||
#: users/models/user/__init__.py:122 users/models/user/_source.py:18
|
#: users/models/user/__init__.py:122 users/models/user/_source.py:19
|
||||||
msgid "WeCom"
|
msgid "WeCom"
|
||||||
msgstr "企业微信"
|
msgstr "企业微信"
|
||||||
|
|
||||||
|
@ -3039,21 +3041,21 @@ msgstr "企业微信"
|
||||||
#: authentication/views/login.py:90 notifications/backends/__init__.py:14
|
#: authentication/views/login.py:90 notifications/backends/__init__.py:14
|
||||||
#: settings/serializers/auth/feishu.py:12
|
#: settings/serializers/auth/feishu.py:12
|
||||||
#: settings/serializers/auth/feishu.py:14 users/models/user/__init__.py:128
|
#: settings/serializers/auth/feishu.py:14 users/models/user/__init__.py:128
|
||||||
#: users/models/user/_source.py:20
|
#: users/models/user/_source.py:21
|
||||||
msgid "FeiShu"
|
msgid "FeiShu"
|
||||||
msgstr "飞书"
|
msgstr "飞书"
|
||||||
|
|
||||||
#: audits/signal_handlers/login_log.py:40 authentication/views/login.py:102
|
#: audits/signal_handlers/login_log.py:40 authentication/views/login.py:102
|
||||||
#: authentication/views/slack.py:79 notifications/backends/__init__.py:16
|
#: authentication/views/slack.py:79 notifications/backends/__init__.py:16
|
||||||
#: settings/serializers/auth/slack.py:11 settings/serializers/auth/slack.py:13
|
#: settings/serializers/auth/slack.py:11 settings/serializers/auth/slack.py:13
|
||||||
#: users/models/user/__init__.py:134 users/models/user/_source.py:22
|
#: users/models/user/__init__.py:134 users/models/user/_source.py:23
|
||||||
msgid "Slack"
|
msgid "Slack"
|
||||||
msgstr "Slack"
|
msgstr "Slack"
|
||||||
|
|
||||||
#: audits/signal_handlers/login_log.py:41 authentication/views/dingtalk.py:151
|
#: audits/signal_handlers/login_log.py:41 authentication/views/dingtalk.py:151
|
||||||
#: authentication/views/login.py:84 notifications/backends/__init__.py:12
|
#: authentication/views/login.py:84 notifications/backends/__init__.py:12
|
||||||
#: settings/serializers/auth/dingtalk.py:11 users/models/user/__init__.py:125
|
#: settings/serializers/auth/dingtalk.py:11 users/models/user/__init__.py:125
|
||||||
#: users/models/user/_source.py:19
|
#: users/models/user/_source.py:20
|
||||||
msgid "DingTalk"
|
msgid "DingTalk"
|
||||||
msgstr "钉钉"
|
msgstr "钉钉"
|
||||||
|
|
||||||
|
@ -3500,7 +3502,7 @@ msgstr "设置手机号码启用"
|
||||||
msgid "Clear phone number to disable"
|
msgid "Clear phone number to disable"
|
||||||
msgstr "清空手机号码禁用"
|
msgstr "清空手机号码禁用"
|
||||||
|
|
||||||
#: authentication/middleware.py:94 settings/utils/ldap.py:681
|
#: authentication/middleware.py:94 settings/utils/ldap.py:691
|
||||||
msgid "Authentication failed (before login check failed): {}"
|
msgid "Authentication failed (before login check failed): {}"
|
||||||
msgstr "认证失败 (登录前检查失败): {}"
|
msgstr "认证失败 (登录前检查失败): {}"
|
||||||
|
|
||||||
|
@ -3818,7 +3820,7 @@ msgstr "代码错误"
|
||||||
#: authentication/templates/authentication/_msg_oauth_bind.html:3
|
#: authentication/templates/authentication/_msg_oauth_bind.html:3
|
||||||
#: authentication/templates/authentication/_msg_reset_password.html:3
|
#: authentication/templates/authentication/_msg_reset_password.html:3
|
||||||
#: authentication/templates/authentication/_msg_reset_password_code.html:9
|
#: authentication/templates/authentication/_msg_reset_password_code.html:9
|
||||||
#: jumpserver/conf.py:502
|
#: jumpserver/conf.py:522
|
||||||
#: perms/templates/perms/_msg_item_permissions_expire.html:3
|
#: perms/templates/perms/_msg_item_permissions_expire.html:3
|
||||||
#: tickets/templates/tickets/approve_check_password.html:32
|
#: tickets/templates/tickets/approve_check_password.html:32
|
||||||
#: users/templates/users/_msg_account_expire_reminder.html:4
|
#: users/templates/users/_msg_account_expire_reminder.html:4
|
||||||
|
@ -4578,16 +4580,16 @@ msgstr "不能包含特殊字符"
|
||||||
msgid "The mobile phone number format is incorrect"
|
msgid "The mobile phone number format is incorrect"
|
||||||
msgstr "手机号格式不正确"
|
msgstr "手机号格式不正确"
|
||||||
|
|
||||||
#: jumpserver/conf.py:496
|
#: jumpserver/conf.py:516
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "The verification code is: {code}"
|
msgid "The verification code is: {code}"
|
||||||
msgstr "验证码为: {code}"
|
msgstr "验证码为: {code}"
|
||||||
|
|
||||||
#: jumpserver/conf.py:501
|
#: jumpserver/conf.py:521
|
||||||
msgid "Create account successfully"
|
msgid "Create account successfully"
|
||||||
msgstr "创建账号成功"
|
msgstr "创建账号成功"
|
||||||
|
|
||||||
#: jumpserver/conf.py:503
|
#: jumpserver/conf.py:523
|
||||||
msgid "Your account has been created successfully"
|
msgid "Your account has been created successfully"
|
||||||
msgstr "你的账号已创建成功"
|
msgstr "你的账号已创建成功"
|
||||||
|
|
||||||
|
@ -4749,31 +4751,31 @@ msgid ""
|
||||||
"The task is being created and cannot be interrupted. Please try again later."
|
"The task is being created and cannot be interrupted. Please try again later."
|
||||||
msgstr "正在创建任务,无法中断,请稍后重试。"
|
msgstr "正在创建任务,无法中断,请稍后重试。"
|
||||||
|
|
||||||
#: ops/api/playbook.py:39
|
#: ops/api/playbook.py:50
|
||||||
msgid "Currently playbook is being used in a job"
|
msgid "Currently playbook is being used in a job"
|
||||||
msgstr "当前 playbook 正在作业中使用"
|
msgstr "当前 playbook 正在作业中使用"
|
||||||
|
|
||||||
#: ops/api/playbook.py:97
|
#: ops/api/playbook.py:113
|
||||||
msgid "Unsupported file content"
|
msgid "Unsupported file content"
|
||||||
msgstr "不支持的文件内容"
|
msgstr "不支持的文件内容"
|
||||||
|
|
||||||
#: ops/api/playbook.py:99 ops/api/playbook.py:145 ops/api/playbook.py:193
|
#: ops/api/playbook.py:115 ops/api/playbook.py:161 ops/api/playbook.py:209
|
||||||
msgid "Invalid file path"
|
msgid "Invalid file path"
|
||||||
msgstr "无效的文件路径"
|
msgstr "无效的文件路径"
|
||||||
|
|
||||||
#: ops/api/playbook.py:171
|
#: ops/api/playbook.py:187
|
||||||
msgid "This file can not be rename"
|
msgid "This file can not be rename"
|
||||||
msgstr "该文件不能重命名"
|
msgstr "该文件不能重命名"
|
||||||
|
|
||||||
#: ops/api/playbook.py:190
|
#: ops/api/playbook.py:206
|
||||||
msgid "File already exists"
|
msgid "File already exists"
|
||||||
msgstr "文件已存在"
|
msgstr "文件已存在"
|
||||||
|
|
||||||
#: ops/api/playbook.py:208
|
#: ops/api/playbook.py:224
|
||||||
msgid "File key is required"
|
msgid "File key is required"
|
||||||
msgstr "文件密钥该字段是必填项。"
|
msgstr "文件密钥该字段是必填项。"
|
||||||
|
|
||||||
#: ops/api/playbook.py:211
|
#: ops/api/playbook.py:227
|
||||||
msgid "This file can not be delete"
|
msgid "This file can not be delete"
|
||||||
msgstr "无法删除此文件"
|
msgstr "无法删除此文件"
|
||||||
|
|
||||||
|
@ -4817,7 +4819,7 @@ msgstr "VCS"
|
||||||
msgid "Adhoc"
|
msgid "Adhoc"
|
||||||
msgstr "命令"
|
msgstr "命令"
|
||||||
|
|
||||||
#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:88
|
#: ops/const.py:39 ops/models/job.py:149 ops/models/playbook.py:91
|
||||||
msgid "Playbook"
|
msgid "Playbook"
|
||||||
msgstr "Playbook"
|
msgstr "Playbook"
|
||||||
|
|
||||||
|
@ -4881,21 +4883,31 @@ msgstr "超时"
|
||||||
msgid "Command execution disabled"
|
msgid "Command execution disabled"
|
||||||
msgstr "命令执行已禁用"
|
msgstr "命令执行已禁用"
|
||||||
|
|
||||||
|
#: ops/const.py:86
|
||||||
|
msgctxt "scope"
|
||||||
|
msgid "Public"
|
||||||
|
msgstr "公有
|
||||||
|
|
||||||
|
#: ops/const.py:87
|
||||||
|
msgid "Private"
|
||||||
|
msgstr "私有"
|
||||||
|
|
||||||
#: ops/exception.py:6
|
#: ops/exception.py:6
|
||||||
msgid "no valid program entry found."
|
msgid "no valid program entry found."
|
||||||
msgstr "没有可用程序入口"
|
msgstr "没有可用程序入口"
|
||||||
|
|
||||||
#: ops/mixin.py:30 ops/mixin.py:110 settings/serializers/auth/ldap.py:73
|
#: ops/mixin.py:30 ops/mixin.py:110 settings/serializers/auth/ldap.py:73
|
||||||
|
#: settings/serializers/auth/ldap_ha.py:55
|
||||||
msgid "Periodic run"
|
msgid "Periodic run"
|
||||||
msgstr "周期执行"
|
msgstr "周期执行"
|
||||||
|
|
||||||
#: ops/mixin.py:32 ops/mixin.py:96 ops/mixin.py:116
|
#: ops/mixin.py:32 ops/mixin.py:96 ops/mixin.py:116
|
||||||
#: settings/serializers/auth/ldap.py:80
|
#: settings/serializers/auth/ldap.py:80 settings/serializers/auth/ldap_ha.py:62
|
||||||
msgid "Interval"
|
msgid "Interval"
|
||||||
msgstr "间隔"
|
msgstr "间隔"
|
||||||
|
|
||||||
#: ops/mixin.py:35 ops/mixin.py:94 ops/mixin.py:113
|
#: ops/mixin.py:35 ops/mixin.py:94 ops/mixin.py:113
|
||||||
#: settings/serializers/auth/ldap.py:77
|
#: settings/serializers/auth/ldap.py:77 settings/serializers/auth/ldap_ha.py:59
|
||||||
msgid "Crontab"
|
msgid "Crontab"
|
||||||
msgstr "Crontab"
|
msgstr "Crontab"
|
||||||
|
|
||||||
|
@ -4915,19 +4927,25 @@ msgstr "输入在 {} - {} 范围之间"
|
||||||
msgid "Require interval or crontab setting"
|
msgid "Require interval or crontab setting"
|
||||||
msgstr "需要周期或定期设置"
|
msgstr "需要周期或定期设置"
|
||||||
|
|
||||||
#: ops/models/adhoc.py:21
|
#: ops/models/adhoc.py:20
|
||||||
msgid "Pattern"
|
msgid "Pattern"
|
||||||
msgstr "模式"
|
msgstr "模式"
|
||||||
|
|
||||||
#: ops/models/adhoc.py:23 ops/models/job.py:146
|
#: ops/models/adhoc.py:22 ops/models/job.py:146
|
||||||
msgid "Module"
|
msgid "Module"
|
||||||
msgstr "模块"
|
msgstr "模块"
|
||||||
|
|
||||||
#: ops/models/adhoc.py:24 ops/models/celery.py:82 ops/models/job.py:144
|
#: ops/models/adhoc.py:23 ops/models/celery.py:82 ops/models/job.py:144
|
||||||
#: terminal/models/component/task.py:14
|
#: terminal/models/component/task.py:14
|
||||||
msgid "Args"
|
msgid "Args"
|
||||||
msgstr "参数"
|
msgstr "参数"
|
||||||
|
|
||||||
|
#: ops/models/adhoc.py:26 ops/models/playbook.py:36 ops/serializers/mixin.py:10
|
||||||
|
#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
|
||||||
|
#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
|
||||||
|
msgid "Scope"
|
||||||
|
msgstr "范围"
|
||||||
|
|
||||||
#: ops/models/base.py:19
|
#: ops/models/base.py:19
|
||||||
msgid "Account policy"
|
msgid "Account policy"
|
||||||
msgstr "账号策略"
|
msgstr "账号策略"
|
||||||
|
@ -5015,11 +5033,11 @@ msgstr "Material 类型"
|
||||||
msgid "Job Execution"
|
msgid "Job Execution"
|
||||||
msgstr "作业执行"
|
msgstr "作业执行"
|
||||||
|
|
||||||
#: ops/models/playbook.py:33
|
#: ops/models/playbook.py:35
|
||||||
msgid "CreateMethod"
|
msgid "CreateMethod"
|
||||||
msgstr "创建方式"
|
msgstr "创建方式"
|
||||||
|
|
||||||
#: ops/models/playbook.py:34
|
#: ops/models/playbook.py:37
|
||||||
msgid "VCS URL"
|
msgid "VCS URL"
|
||||||
msgstr "VCS URL"
|
msgstr "VCS URL"
|
||||||
|
|
||||||
|
@ -5262,7 +5280,7 @@ msgstr "请选择一个组织后再保存"
|
||||||
|
|
||||||
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:91
|
#: orgs/mixins/models.py:57 orgs/mixins/serializers.py:25 orgs/models.py:91
|
||||||
#: rbac/const.py:7 rbac/models/rolebinding.py:56
|
#: rbac/const.py:7 rbac/models/rolebinding.py:56
|
||||||
#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:52
|
#: rbac/serializers/rolebinding.py:44 settings/serializers/auth/base.py:53
|
||||||
#: terminal/templates/terminal/_msg_command_warning.html:21
|
#: terminal/templates/terminal/_msg_command_warning.html:21
|
||||||
#: terminal/templates/terminal/_msg_session_sharing.html:14
|
#: terminal/templates/terminal/_msg_session_sharing.html:14
|
||||||
#: tickets/models/ticket/general.py:303 tickets/serializers/ticket/ticket.py:60
|
#: tickets/models/ticket/general.py:303 tickets/serializers/ticket/ticket.py:60
|
||||||
|
@ -5578,11 +5596,6 @@ msgstr "内容类型"
|
||||||
msgid "Permissions"
|
msgid "Permissions"
|
||||||
msgstr "授权"
|
msgstr "授权"
|
||||||
|
|
||||||
#: rbac/models/role.py:31 rbac/models/rolebinding.py:46
|
|
||||||
#: rbac/serializers/role.py:12 settings/serializers/auth/oauth2.py:37
|
|
||||||
msgid "Scope"
|
|
||||||
msgstr "范围"
|
|
||||||
|
|
||||||
#: rbac/models/role.py:46 rbac/models/rolebinding.py:52
|
#: rbac/models/role.py:46 rbac/models/rolebinding.py:52
|
||||||
#: users/models/user/__init__.py:66
|
#: users/models/user/__init__.py:66
|
||||||
msgid "Role"
|
msgid "Role"
|
||||||
|
@ -5736,7 +5749,7 @@ msgstr "邮件已经发送{}, 请检查"
|
||||||
msgid "Test smtp setting"
|
msgid "Test smtp setting"
|
||||||
msgstr "测试 smtp 设置"
|
msgstr "测试 smtp 设置"
|
||||||
|
|
||||||
#: settings/api/ldap.py:90
|
#: settings/api/ldap.py:92
|
||||||
msgid ""
|
msgid ""
|
||||||
"Users are not synchronized, please click the user synchronization button"
|
"Users are not synchronized, please click the user synchronization button"
|
||||||
msgstr "用户未同步,请点击同步用户按钮"
|
msgstr "用户未同步,请点击同步用户按钮"
|
||||||
|
@ -5834,58 +5847,62 @@ msgid "LDAP Auth"
|
||||||
msgstr "LDAP 认证"
|
msgstr "LDAP 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:14
|
#: settings/serializers/auth/base.py:14
|
||||||
|
msgid "LDAP Auth HA"
|
||||||
|
msgstr "LDAP HA 认证"
|
||||||
|
|
||||||
|
#: settings/serializers/auth/base.py:15
|
||||||
msgid "CAS Auth"
|
msgid "CAS Auth"
|
||||||
msgstr "CAS 认证"
|
msgstr "CAS 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:15
|
#: settings/serializers/auth/base.py:16
|
||||||
msgid "OPENID Auth"
|
msgid "OPENID Auth"
|
||||||
msgstr "OIDC 认证"
|
msgstr "OIDC 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:16
|
#: settings/serializers/auth/base.py:17
|
||||||
msgid "SAML2 Auth"
|
msgid "SAML2 Auth"
|
||||||
msgstr "SAML2 认证"
|
msgstr "SAML2 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:17
|
#: settings/serializers/auth/base.py:18
|
||||||
msgid "OAuth2 Auth"
|
msgid "OAuth2 Auth"
|
||||||
msgstr "OAuth2 认证"
|
msgstr "OAuth2 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:18
|
#: settings/serializers/auth/base.py:19
|
||||||
msgid "RADIUS Auth"
|
msgid "RADIUS Auth"
|
||||||
msgstr "RADIUS 认证"
|
msgstr "RADIUS 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:19
|
#: settings/serializers/auth/base.py:20
|
||||||
msgid "DingTalk Auth"
|
msgid "DingTalk Auth"
|
||||||
msgstr "钉钉 认证"
|
msgstr "钉钉 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:20
|
#: settings/serializers/auth/base.py:21
|
||||||
msgid "FeiShu Auth"
|
msgid "FeiShu Auth"
|
||||||
msgstr "飞书 认证"
|
msgstr "飞书 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:21
|
#: settings/serializers/auth/base.py:22
|
||||||
msgid "Lark Auth"
|
msgid "Lark Auth"
|
||||||
msgstr "Lark 认证"
|
msgstr "Lark 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:22
|
#: settings/serializers/auth/base.py:23
|
||||||
msgid "Slack Auth"
|
msgid "Slack Auth"
|
||||||
msgstr "Slack 认证"
|
msgstr "Slack 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:23
|
#: settings/serializers/auth/base.py:24
|
||||||
msgid "WeCom Auth"
|
msgid "WeCom Auth"
|
||||||
msgstr "企业微信 认证"
|
msgstr "企业微信 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:24
|
#: settings/serializers/auth/base.py:25
|
||||||
msgid "SSO Auth"
|
msgid "SSO Auth"
|
||||||
msgstr "SSO 令牌认证"
|
msgstr "SSO 令牌认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:25
|
#: settings/serializers/auth/base.py:26
|
||||||
msgid "Passkey Auth"
|
msgid "Passkey Auth"
|
||||||
msgstr "Passkey 认证"
|
msgstr "Passkey 认证"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:27
|
#: settings/serializers/auth/base.py:28
|
||||||
msgid "Email suffix"
|
msgid "Email suffix"
|
||||||
msgstr "邮件后缀"
|
msgstr "邮件后缀"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:29
|
#: settings/serializers/auth/base.py:30
|
||||||
msgid ""
|
msgid ""
|
||||||
"After third-party user authentication is successful, if the third-party "
|
"After third-party user authentication is successful, if the third-party "
|
||||||
"authentication service platform does not return the user's email "
|
"authentication service platform does not return the user's email "
|
||||||
|
@ -5895,19 +5912,19 @@ msgstr ""
|
||||||
"第三方用户认证成功后,若第三方认证服务平台未返回该用户的邮箱信息,系统将自动"
|
"第三方用户认证成功后,若第三方认证服务平台未返回该用户的邮箱信息,系统将自动"
|
||||||
"以此邮箱后缀创建用户"
|
"以此邮箱后缀创建用户"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:36
|
#: settings/serializers/auth/base.py:37
|
||||||
msgid "Forgot Password URL"
|
msgid "Forgot Password URL"
|
||||||
msgstr "忘记密码链接"
|
msgstr "忘记密码链接"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:37
|
#: settings/serializers/auth/base.py:38
|
||||||
msgid "The URL for Forgotten Password on the user login page"
|
msgid "The URL for Forgotten Password on the user login page"
|
||||||
msgstr "用户登录页面忘记密码的 URL"
|
msgstr "用户登录页面忘记密码的 URL"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:40
|
#: settings/serializers/auth/base.py:41
|
||||||
msgid "Login redirection"
|
msgid "Login redirection"
|
||||||
msgstr "启用登录跳转提示"
|
msgstr "启用登录跳转提示"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:42
|
#: settings/serializers/auth/base.py:43
|
||||||
msgid ""
|
msgid ""
|
||||||
"Should an flash page be displayed before the user is redirected to third-"
|
"Should an flash page be displayed before the user is redirected to third-"
|
||||||
"party authentication when the administrator enables third-party redirect "
|
"party authentication when the administrator enables third-party redirect "
|
||||||
|
@ -5916,7 +5933,7 @@ msgstr ""
|
||||||
"当管理员启用第三方重定向身份验证时,在用户重定向到第三方身份验证之前是否显示 "
|
"当管理员启用第三方重定向身份验证时,在用户重定向到第三方身份验证之前是否显示 "
|
||||||
"Flash 页面"
|
"Flash 页面"
|
||||||
|
|
||||||
#: settings/serializers/auth/base.py:54
|
#: settings/serializers/auth/base.py:55
|
||||||
msgid ""
|
msgid ""
|
||||||
"When you create a user, you associate the user to the organization of your "
|
"When you create a user, you associate the user to the organization of your "
|
||||||
"choice. Users always belong to the Default organization."
|
"choice. Users always belong to the Default organization."
|
||||||
|
@ -5928,7 +5945,7 @@ msgid "CAS"
|
||||||
msgstr "CAS"
|
msgstr "CAS"
|
||||||
|
|
||||||
#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:44
|
#: settings/serializers/auth/cas.py:15 settings/serializers/auth/ldap.py:44
|
||||||
#: settings/serializers/auth/oidc.py:61
|
#: settings/serializers/auth/ldap_ha.py:26 settings/serializers/auth/oidc.py:61
|
||||||
msgid "Server"
|
msgid "Server"
|
||||||
msgstr "服务端地址"
|
msgstr "服务端地址"
|
||||||
|
|
||||||
|
@ -5955,9 +5972,10 @@ msgstr "启用属性映射"
|
||||||
|
|
||||||
#: settings/serializers/auth/cas.py:34 settings/serializers/auth/dingtalk.py:18
|
#: settings/serializers/auth/cas.py:34 settings/serializers/auth/dingtalk.py:18
|
||||||
#: settings/serializers/auth/feishu.py:18 settings/serializers/auth/lark.py:17
|
#: settings/serializers/auth/feishu.py:18 settings/serializers/auth/lark.py:17
|
||||||
#: settings/serializers/auth/ldap.py:66 settings/serializers/auth/oauth2.py:60
|
#: settings/serializers/auth/ldap.py:66 settings/serializers/auth/ldap_ha.py:48
|
||||||
#: settings/serializers/auth/oidc.py:39 settings/serializers/auth/saml2.py:35
|
#: settings/serializers/auth/oauth2.py:60 settings/serializers/auth/oidc.py:39
|
||||||
#: settings/serializers/auth/slack.py:18 settings/serializers/auth/wecom.py:18
|
#: settings/serializers/auth/saml2.py:35 settings/serializers/auth/slack.py:18
|
||||||
|
#: settings/serializers/auth/wecom.py:18
|
||||||
msgid "User attribute"
|
msgid "User attribute"
|
||||||
msgstr "映射属性"
|
msgstr "映射属性"
|
||||||
|
|
||||||
|
@ -5999,7 +6017,7 @@ msgstr ""
|
||||||
"用户属性映射,其中 `key` 是 JumpServer 用户属性名称,`value` 是飞书服务用户属"
|
"用户属性映射,其中 `key` 是 JumpServer 用户属性名称,`value` 是飞书服务用户属"
|
||||||
"性名称"
|
"性名称"
|
||||||
|
|
||||||
#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:21
|
#: settings/serializers/auth/lark.py:13 users/models/user/_source.py:22
|
||||||
msgid "Lark"
|
msgid "Lark"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -6019,38 +6037,38 @@ msgstr "LDAP"
|
||||||
msgid "LDAP server URI"
|
msgid "LDAP server URI"
|
||||||
msgstr "LDAP 服务域名"
|
msgstr "LDAP 服务域名"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:48
|
#: settings/serializers/auth/ldap.py:48 settings/serializers/auth/ldap_ha.py:30
|
||||||
msgid "Bind DN"
|
msgid "Bind DN"
|
||||||
msgstr "绑定 DN"
|
msgstr "绑定 DN"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:49
|
#: settings/serializers/auth/ldap.py:49 settings/serializers/auth/ldap_ha.py:31
|
||||||
msgid "Binding Distinguished Name"
|
msgid "Binding Distinguished Name"
|
||||||
msgstr "绑定目录管理员"
|
msgstr "绑定目录管理员"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:53
|
#: settings/serializers/auth/ldap.py:53 settings/serializers/auth/ldap_ha.py:35
|
||||||
msgid "Binding password"
|
msgid "Binding password"
|
||||||
msgstr "绑定密码"
|
msgstr "绑定密码"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:56
|
#: settings/serializers/auth/ldap.py:56 settings/serializers/auth/ldap_ha.py:38
|
||||||
msgid "Search OU"
|
msgid "Search OU"
|
||||||
msgstr "用户 OU"
|
msgstr "用户 OU"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:58
|
#: settings/serializers/auth/ldap.py:58 settings/serializers/auth/ldap_ha.py:40
|
||||||
msgid ""
|
msgid ""
|
||||||
"User Search Base, if there are multiple OUs, you can separate them with the "
|
"User Search Base, if there are multiple OUs, you can separate them with the "
|
||||||
"`|` symbol"
|
"`|` symbol"
|
||||||
msgstr "用户搜索库,如果有多个OU,可以用`|`符号分隔"
|
msgstr "用户搜索库,如果有多个OU,可以用`|`符号分隔"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:62
|
#: settings/serializers/auth/ldap.py:62 settings/serializers/auth/ldap_ha.py:44
|
||||||
msgid "Search filter"
|
msgid "Search filter"
|
||||||
msgstr "用户过滤器"
|
msgstr "用户过滤器"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:63
|
#: settings/serializers/auth/ldap.py:63 settings/serializers/auth/ldap_ha.py:45
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Selection could include (cn|uid|sAMAccountName=%(user)s)"
|
msgid "Selection could include (cn|uid|sAMAccountName=%(user)s)"
|
||||||
msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)"
|
msgstr "可能的选项是(cn或uid或sAMAccountName=%(user)s)"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:68
|
#: settings/serializers/auth/ldap.py:68 settings/serializers/auth/ldap_ha.py:50
|
||||||
msgid ""
|
msgid ""
|
||||||
"User attribute mapping, where the `key` is the JumpServer user attribute "
|
"User attribute mapping, where the `key` is the JumpServer user attribute "
|
||||||
"name and the `value` is the LDAP service user attribute name"
|
"name and the `value` is the LDAP service user attribute name"
|
||||||
|
@ -6058,11 +6076,11 @@ msgstr ""
|
||||||
"用户属性映射,其中 `key` 是 JumpServer 用户属性名称,`value` 是 LDAP 服务用户"
|
"用户属性映射,其中 `key` 是 JumpServer 用户属性名称,`value` 是 LDAP 服务用户"
|
||||||
"属性名称"
|
"属性名称"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:84
|
#: settings/serializers/auth/ldap.py:84 settings/serializers/auth/ldap_ha.py:66
|
||||||
msgid "Connect timeout (s)"
|
msgid "Connect timeout (s)"
|
||||||
msgstr "连接超时时间 (秒)"
|
msgstr "连接超时时间 (秒)"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:89
|
#: settings/serializers/auth/ldap.py:89 settings/serializers/auth/ldap_ha.py:71
|
||||||
msgid "User DN cache timeout (s)"
|
msgid "User DN cache timeout (s)"
|
||||||
msgstr "User DN 缓存超时时间 (秒)"
|
msgstr "User DN 缓存超时时间 (秒)"
|
||||||
|
|
||||||
|
@ -6076,10 +6094,29 @@ msgstr ""
|
||||||
"对用户登录认证时查询出的 User DN 进行缓存,可以有效提高用户认证的速度<br>如果"
|
"对用户登录认证时查询出的 User DN 进行缓存,可以有效提高用户认证的速度<br>如果"
|
||||||
"用户 OU 架构有调整,点击提交即可清除用户 DN 缓存"
|
"用户 OU 架构有调整,点击提交即可清除用户 DN 缓存"
|
||||||
|
|
||||||
#: settings/serializers/auth/ldap.py:97
|
#: settings/serializers/auth/ldap.py:97 settings/serializers/auth/ldap_ha.py:79
|
||||||
msgid "Search paged size (piece)"
|
msgid "Search paged size (piece)"
|
||||||
msgstr "搜索分页数量 (条)"
|
msgstr "搜索分页数量 (条)"
|
||||||
|
|
||||||
|
#: settings/serializers/auth/ldap_ha.py:23
|
||||||
|
#: settings/serializers/auth/ldap_ha.py:85
|
||||||
|
msgid "LDAP HA"
|
||||||
|
msgstr "LDAP 认证"
|
||||||
|
|
||||||
|
#: settings/serializers/auth/ldap_ha.py:27
|
||||||
|
msgid "LDAP HA server URI"
|
||||||
|
msgstr "LDAP HA 服务域名"
|
||||||
|
|
||||||
|
#: settings/serializers/auth/ldap_ha.py:73
|
||||||
|
msgid ""
|
||||||
|
"Caching the User DN obtained during user login authentication can "
|
||||||
|
"effectivelyimprove the speed of user authentication., 0 means no cache<br>If "
|
||||||
|
"the user OU structure has been adjusted, click Submit to clear the user DN "
|
||||||
|
"cache"
|
||||||
|
msgstr ""
|
||||||
|
"对用户登录认证时查询出的 User DN 进行缓存,可以有效提高用户认证的速度<br>如果"
|
||||||
|
"用户 OU 架构有调整,点击提交即可清除用户 DN 缓存"
|
||||||
|
|
||||||
#: settings/serializers/auth/oauth2.py:19
|
#: settings/serializers/auth/oauth2.py:19
|
||||||
#: settings/serializers/auth/oauth2.py:22
|
#: settings/serializers/auth/oauth2.py:22
|
||||||
msgid "OAuth2"
|
msgid "OAuth2"
|
||||||
|
@ -7085,11 +7122,11 @@ msgid ""
|
||||||
"in the workbench"
|
"in the workbench"
|
||||||
msgstr "*! 如果启用,具有 RBAC 权限的用户将能够使用工作台中的所有工具"
|
msgstr "*! 如果启用,具有 RBAC 权限的用户将能够使用工作台中的所有工具"
|
||||||
|
|
||||||
#: settings/tasks/ldap.py:29
|
#: settings/tasks/ldap.py:72
|
||||||
msgid "Periodic import ldap user"
|
msgid "Periodic import ldap user"
|
||||||
msgstr "周期导入 LDAP 用户"
|
msgstr "周期导入 LDAP 用户"
|
||||||
|
|
||||||
#: settings/tasks/ldap.py:31
|
#: settings/tasks/ldap.py:74 settings/tasks/ldap.py:86
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
" When LDAP auto-sync is configured, this task will be invoked to "
|
" When LDAP auto-sync is configured, this task will be invoked to "
|
||||||
|
@ -7099,11 +7136,16 @@ msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
"当设置了LDAP自动同步,将调用该任务进行用户同步"
|
"当设置了LDAP自动同步,将调用该任务进行用户同步"
|
||||||
|
|
||||||
#: settings/tasks/ldap.py:74
|
#: settings/tasks/ldap.py:84
|
||||||
|
|
||||||
|
msgid "Periodic import ldap ha user"
|
||||||
|
msgstr "周期导入 LDAP HA 用户"
|
||||||
|
|
||||||
|
#: settings/tasks/ldap.py:120
|
||||||
msgid "Registration periodic import ldap user task"
|
msgid "Registration periodic import ldap user task"
|
||||||
msgstr "注册周期导入 LDAP 用户 任务"
|
msgstr "注册周期导入 LDAP 用户 任务"
|
||||||
|
|
||||||
#: settings/tasks/ldap.py:76
|
#: settings/tasks/ldap.py:122
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
" When LDAP auto-sync parameters change, such as Crontab parameters, "
|
" When LDAP auto-sync parameters change, such as Crontab parameters, "
|
||||||
|
@ -7113,7 +7155,23 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
"当设置了LDAP自动同步参数发生变化时,比如Crontab参数,重新注册或更新ldap同步任"
|
"当设置了LDAP自动同步参数发生变化时,比如Crontab参数,重新注册或更新ldap同步任"
|
||||||
"务调用该任务"
|
"务将调用该任务"
|
||||||
|
|
||||||
|
#: settings/tasks/ldap.py:138
|
||||||
|
msgid "Registration periodic import ldap ha user task"
|
||||||
|
msgstr "注册周期导入 LDAP HA 用户 任务"
|
||||||
|
|
||||||
|
#: settings/tasks/ldap.py:140
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" When LDAP HA auto-sync parameters change, such as Crontab "
|
||||||
|
"parameters, the LDAP HA sync task \n"
|
||||||
|
" will be re-registered or updated, and this task will be invoked\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
"\n"
|
||||||
|
"当设置了LDAP HA 自动同步参数发生变化时,比如Crontab参数,重新注册或更新ldap ha 同步任"
|
||||||
|
"务将调用该任务"
|
||||||
|
|
||||||
#: settings/templates/ldap/_msg_import_ldap_user.html:2
|
#: settings/templates/ldap/_msg_import_ldap_user.html:2
|
||||||
msgid "Sync task finish"
|
msgid "Sync task finish"
|
||||||
|
@ -7131,108 +7189,108 @@ msgstr "已同步用户"
|
||||||
msgid "No user synchronization required"
|
msgid "No user synchronization required"
|
||||||
msgstr "没有用户需要同步"
|
msgstr "没有用户需要同步"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:496
|
#: settings/utils/ldap.py:509
|
||||||
msgid "ldap:// or ldaps:// protocol is used."
|
msgid "ldap:// or ldaps:// protocol is used."
|
||||||
msgstr "使用 ldap:// 或 ldaps:// 协议"
|
msgstr "使用 ldap:// 或 ldaps:// 协议"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:507
|
#: settings/utils/ldap.py:520
|
||||||
msgid "Host or port is disconnected: {}"
|
msgid "Host or port is disconnected: {}"
|
||||||
msgstr "主机或端口不可连接: {}"
|
msgstr "主机或端口不可连接: {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:509
|
#: settings/utils/ldap.py:522
|
||||||
msgid "The port is not the port of the LDAP service: {}"
|
msgid "The port is not the port of the LDAP service: {}"
|
||||||
msgstr "端口不是LDAP服务端口: {}"
|
msgstr "端口不是LDAP服务端口: {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:511
|
#: settings/utils/ldap.py:524
|
||||||
msgid "Please add certificate: {}"
|
msgid "Please add certificate: {}"
|
||||||
msgstr "请添加证书"
|
msgstr "请添加证书"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:515 settings/utils/ldap.py:542
|
#: settings/utils/ldap.py:528 settings/utils/ldap.py:555
|
||||||
#: settings/utils/ldap.py:572 settings/utils/ldap.py:600
|
#: settings/utils/ldap.py:585 settings/utils/ldap.py:613
|
||||||
msgid "Unknown error: {}"
|
msgid "Unknown error: {}"
|
||||||
msgstr "未知错误: {}"
|
msgstr "未知错误: {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:529
|
#: settings/utils/ldap.py:542
|
||||||
msgid "Bind DN or Password incorrect"
|
msgid "Bind DN or Password incorrect"
|
||||||
msgstr "绑定DN或密码错误"
|
msgstr "绑定DN或密码错误"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:536
|
#: settings/utils/ldap.py:549
|
||||||
msgid "Please enter Bind DN: {}"
|
msgid "Please enter Bind DN: {}"
|
||||||
msgstr "请输入绑定DN: {}"
|
msgstr "请输入绑定DN: {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:538
|
#: settings/utils/ldap.py:551
|
||||||
msgid "Please enter Password: {}"
|
msgid "Please enter Password: {}"
|
||||||
msgstr "请输入密码: {}"
|
msgstr "请输入密码: {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:540
|
#: settings/utils/ldap.py:553
|
||||||
msgid "Please enter correct Bind DN and Password: {}"
|
msgid "Please enter correct Bind DN and Password: {}"
|
||||||
msgstr "请输入正确的绑定DN和密码: {}"
|
msgstr "请输入正确的绑定DN和密码: {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:558
|
#: settings/utils/ldap.py:571
|
||||||
msgid "Invalid User OU or User search filter: {}"
|
msgid "Invalid User OU or User search filter: {}"
|
||||||
msgstr "不合法的用户OU或用户过滤器: {}"
|
msgstr "不合法的用户OU或用户过滤器: {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:589
|
#: settings/utils/ldap.py:602
|
||||||
msgid "LDAP User attr map not include: {}"
|
msgid "LDAP User attr map not include: {}"
|
||||||
msgstr "LDAP属性映射没有包含: {}"
|
msgstr "LDAP属性映射没有包含: {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:596
|
#: settings/utils/ldap.py:609
|
||||||
msgid "LDAP User attr map is not dict"
|
msgid "LDAP User attr map is not dict"
|
||||||
msgstr "LDAP属性映射不合法"
|
msgstr "LDAP属性映射不合法"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:615
|
#: settings/utils/ldap.py:628
|
||||||
msgid "LDAP authentication is not enabled"
|
msgid "LDAP authentication is not enabled"
|
||||||
msgstr "LDAP认证没有启用"
|
msgstr "LDAP认证没有启用"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:633
|
#: settings/utils/ldap.py:646
|
||||||
msgid "Error (Invalid LDAP server): {}"
|
msgid "Error (Invalid LDAP server): {}"
|
||||||
msgstr "错误 (不合法的LDAP服务器地址): {}"
|
msgstr "错误 (不合法的LDAP服务器地址): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:635
|
#: settings/utils/ldap.py:648
|
||||||
msgid "Error (Invalid Bind DN): {}"
|
msgid "Error (Invalid Bind DN): {}"
|
||||||
msgstr "错误 (不合法的绑定DN): {}"
|
msgstr "错误 (不合法的绑定DN): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:637
|
#: settings/utils/ldap.py:650
|
||||||
msgid "Error (Invalid LDAP User attr map): {}"
|
msgid "Error (Invalid LDAP User attr map): {}"
|
||||||
msgstr "错误 (不合法的LDAP属性映射): {}"
|
msgstr "错误 (不合法的LDAP属性映射): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:639
|
#: settings/utils/ldap.py:652
|
||||||
msgid "Error (Invalid User OU or User search filter): {}"
|
msgid "Error (Invalid User OU or User search filter): {}"
|
||||||
msgstr "错误 (不合法的用户OU或用户过滤器): {}"
|
msgstr "错误 (不合法的用户OU或用户过滤器): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:641
|
#: settings/utils/ldap.py:654
|
||||||
msgid "Error (Not enabled LDAP authentication): {}"
|
msgid "Error (Not enabled LDAP authentication): {}"
|
||||||
msgstr "错误 (没有启用LDAP认证): {}"
|
msgstr "错误 (没有启用LDAP认证): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:643
|
#: settings/utils/ldap.py:656
|
||||||
msgid "Error (Unknown): {}"
|
msgid "Error (Unknown): {}"
|
||||||
msgstr "错误 (未知): {}"
|
msgstr "错误 (未知): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:646
|
#: settings/utils/ldap.py:659
|
||||||
msgid "Succeed: Match {} users"
|
msgid "Succeed: Match {} users"
|
||||||
msgstr "成功匹配 {} 个用户"
|
msgstr "成功匹配 {} 个用户"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:679
|
#: settings/utils/ldap.py:689
|
||||||
msgid "Authentication failed (configuration incorrect): {}"
|
msgid "Authentication failed (configuration incorrect): {}"
|
||||||
msgstr "认证失败 (配置错误): {}"
|
msgstr "认证失败 (配置错误): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:683
|
#: settings/utils/ldap.py:693
|
||||||
msgid "Authentication failed (username or password incorrect): {}"
|
msgid "Authentication failed (username or password incorrect): {}"
|
||||||
msgstr "认证失败 (用户名或密码不正确): {}"
|
msgstr "认证失败 (用户名或密码不正确): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:685
|
#: settings/utils/ldap.py:695
|
||||||
msgid "Authentication failed (Unknown): {}"
|
msgid "Authentication failed (Unknown): {}"
|
||||||
msgstr "认证失败: (未知): {}"
|
msgstr "认证失败: (未知): {}"
|
||||||
|
|
||||||
#: settings/utils/ldap.py:688
|
#: settings/utils/ldap.py:698
|
||||||
msgid "Authentication success: {}"
|
msgid "Authentication success: {}"
|
||||||
msgstr "认证成功: {}"
|
msgstr "认证成功: {}"
|
||||||
|
|
||||||
#: settings/ws.py:203
|
#: settings/ws.py:201
|
||||||
msgid "No LDAP user was found"
|
msgid "No LDAP user was found"
|
||||||
msgstr "没有获取到 LDAP 用户"
|
msgstr "没有获取到 LDAP 用户"
|
||||||
|
|
||||||
#: settings/ws.py:209
|
#: settings/ws.py:207
|
||||||
msgid "Total {}, success {}, failure {}"
|
msgid "Total {}, success {}, failure {}"
|
||||||
msgstr "总共 {},成功 {},失败 {}"
|
msgstr "总共 {},成功 {},失败 {}"
|
||||||
|
|
||||||
|
|
|
@ -288,6 +288,26 @@ class Config(dict):
|
||||||
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
|
'AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS': False,
|
||||||
'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1,
|
'AUTH_LDAP_OPTIONS_OPT_REFERRALS': -1,
|
||||||
|
|
||||||
|
# Auth LDAP HA settings
|
||||||
|
'AUTH_LDAP_HA': False,
|
||||||
|
'AUTH_LDAP_HA_SERVER_URI': 'ldap://localhost:389',
|
||||||
|
'AUTH_LDAP_HA_BIND_DN': 'cn=admin,dc=jumpserver,dc=org',
|
||||||
|
'AUTH_LDAP_HA_BIND_PASSWORD': '',
|
||||||
|
'AUTH_LDAP_HA_SEARCH_OU': 'ou=tech,dc=jumpserver,dc=org',
|
||||||
|
'AUTH_LDAP_HA_SEARCH_FILTER': '(cn=%(user)s)',
|
||||||
|
'AUTH_LDAP_HA_START_TLS': False,
|
||||||
|
'AUTH_LDAP_HA_USER_ATTR_MAP': {"username": "cn", "name": "sn", "email": "mail"},
|
||||||
|
'AUTH_LDAP_HA_CONNECT_TIMEOUT': 10,
|
||||||
|
'AUTH_LDAP_HA_CACHE_TIMEOUT': 3600 * 24 * 30,
|
||||||
|
'AUTH_LDAP_HA_SEARCH_PAGED_SIZE': 1000,
|
||||||
|
'AUTH_LDAP_HA_SYNC_IS_PERIODIC': False,
|
||||||
|
'AUTH_LDAP_HA_SYNC_INTERVAL': None,
|
||||||
|
'AUTH_LDAP_HA_SYNC_CRONTAB': None,
|
||||||
|
'AUTH_LDAP_HA_SYNC_ORG_IDS': [DEFAULT_ID],
|
||||||
|
'AUTH_LDAP_HA_SYNC_RECEIVERS': [],
|
||||||
|
'AUTH_LDAP_HA_USER_LOGIN_ONLY_IN_USERS': False,
|
||||||
|
'AUTH_LDAP_HA_OPTIONS_OPT_REFERRALS': -1,
|
||||||
|
|
||||||
# OpenID 配置参数
|
# OpenID 配置参数
|
||||||
# OpenID 公有配置参数 (version <= 1.5.8 或 version >= 1.5.8)
|
# OpenID 公有配置参数 (version <= 1.5.8 或 version >= 1.5.8)
|
||||||
'AUTH_OPENID': False,
|
'AUTH_OPENID': False,
|
||||||
|
|
|
@ -53,6 +53,44 @@ AUTH_LDAP_SYNC_ORG_IDS = CONFIG.AUTH_LDAP_SYNC_ORG_IDS
|
||||||
AUTH_LDAP_SYNC_RECEIVERS = CONFIG.AUTH_LDAP_SYNC_RECEIVERS
|
AUTH_LDAP_SYNC_RECEIVERS = CONFIG.AUTH_LDAP_SYNC_RECEIVERS
|
||||||
AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
|
AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_USER_LOGIN_ONLY_IN_USERS
|
||||||
|
|
||||||
|
# Auth LDAP HA settings
|
||||||
|
AUTH_LDAP_HA = CONFIG.AUTH_LDAP_HA
|
||||||
|
AUTH_LDAP_HA_SERVER_URI = CONFIG.AUTH_LDAP_HA_SERVER_URI
|
||||||
|
AUTH_LDAP_HA_BIND_DN = CONFIG.AUTH_LDAP_HA_BIND_DN
|
||||||
|
AUTH_LDAP_HA_BIND_PASSWORD = CONFIG.AUTH_LDAP_HA_BIND_PASSWORD
|
||||||
|
AUTH_LDAP_HA_SEARCH_OU = CONFIG.AUTH_LDAP_HA_SEARCH_OU
|
||||||
|
AUTH_LDAP_HA_SEARCH_FILTER = CONFIG.AUTH_LDAP_HA_SEARCH_FILTER
|
||||||
|
AUTH_LDAP_HA_START_TLS = CONFIG.AUTH_LDAP_HA_START_TLS
|
||||||
|
AUTH_LDAP_HA_USER_ATTR_MAP = CONFIG.AUTH_LDAP_HA_USER_ATTR_MAP
|
||||||
|
AUTH_LDAP_HA_USER_QUERY_FIELD = 'username'
|
||||||
|
AUTH_LDAP_HA_GLOBAL_OPTIONS = {
|
||||||
|
ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER,
|
||||||
|
ldap.OPT_REFERRALS: CONFIG.AUTH_LDAP_HA_OPTIONS_OPT_REFERRALS
|
||||||
|
}
|
||||||
|
LDAP_HA_CACERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ha_ca.pem")
|
||||||
|
if os.path.isfile(LDAP_HA_CACERT_FILE):
|
||||||
|
AUTH_LDAP_HA_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CACERTFILE] = LDAP_CACERT_FILE
|
||||||
|
LDAP_HA_CERT_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ha_cert.pem")
|
||||||
|
if os.path.isfile(LDAP_HA_CERT_FILE):
|
||||||
|
AUTH_LDAP_HA_GLOBAL_OPTIONS[ldap.OPT_X_TLS_CERTFILE] = LDAP_HA_CERT_FILE
|
||||||
|
LDAP_HA_KEY_FILE = os.path.join(PROJECT_DIR, "data", "certs", "ldap_ha_cert.key")
|
||||||
|
if os.path.isfile(LDAP_HA_KEY_FILE):
|
||||||
|
AUTH_LDAP_HA_GLOBAL_OPTIONS[ldap.OPT_X_TLS_KEYFILE] = LDAP_HA_KEY_FILE
|
||||||
|
AUTH_LDAP_HA_CONNECTION_OPTIONS = {
|
||||||
|
ldap.OPT_TIMEOUT: CONFIG.AUTH_LDAP_HA_CONNECT_TIMEOUT,
|
||||||
|
ldap.OPT_NETWORK_TIMEOUT: CONFIG.AUTH_LDAP_HA_CONNECT_TIMEOUT
|
||||||
|
}
|
||||||
|
AUTH_LDAP_HA_CACHE_TIMEOUT = CONFIG.AUTH_LDAP_HA_CACHE_TIMEOUT
|
||||||
|
AUTH_LDAP_HA_ALWAYS_UPDATE_USER = True
|
||||||
|
|
||||||
|
AUTH_LDAP_HA_SEARCH_PAGED_SIZE = CONFIG.AUTH_LDAP_HA_SEARCH_PAGED_SIZE
|
||||||
|
AUTH_LDAP_HA_SYNC_IS_PERIODIC = CONFIG.AUTH_LDAP_HA_SYNC_IS_PERIODIC
|
||||||
|
AUTH_LDAP_HA_SYNC_INTERVAL = CONFIG.AUTH_LDAP_HA_SYNC_INTERVAL
|
||||||
|
AUTH_LDAP_HA_SYNC_CRONTAB = CONFIG.AUTH_LDAP_HA_SYNC_CRONTAB
|
||||||
|
AUTH_LDAP_HA_SYNC_ORG_IDS = CONFIG.AUTH_LDAP_HA_SYNC_ORG_IDS
|
||||||
|
AUTH_LDAP_HA_SYNC_RECEIVERS = CONFIG.AUTH_LDAP_HA_SYNC_RECEIVERS
|
||||||
|
AUTH_LDAP_HA_USER_LOGIN_ONLY_IN_USERS = CONFIG.AUTH_LDAP_HA_USER_LOGIN_ONLY_IN_USERS
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# 认证 OpenID 配置参数
|
# 认证 OpenID 配置参数
|
||||||
# 参考: https://django-oidc-rp.readthedocs.io/en/stable/settings.html
|
# 参考: https://django-oidc-rp.readthedocs.io/en/stable/settings.html
|
||||||
|
@ -212,6 +250,7 @@ RBAC_BACKEND = 'rbac.backends.RBACBackend'
|
||||||
AUTH_BACKEND_MODEL = 'authentication.backends.base.JMSModelBackend'
|
AUTH_BACKEND_MODEL = 'authentication.backends.base.JMSModelBackend'
|
||||||
AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend'
|
AUTH_BACKEND_PUBKEY = 'authentication.backends.pubkey.PublicKeyAuthBackend'
|
||||||
AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend'
|
AUTH_BACKEND_LDAP = 'authentication.backends.ldap.LDAPAuthorizationBackend'
|
||||||
|
AUTH_BACKEND_LDAP_HA = 'authentication.backends.ldap.LDAPHAAuthorizationBackend'
|
||||||
AUTH_BACKEND_OIDC_PASSWORD = 'authentication.backends.oidc.OIDCAuthPasswordBackend'
|
AUTH_BACKEND_OIDC_PASSWORD = 'authentication.backends.oidc.OIDCAuthPasswordBackend'
|
||||||
AUTH_BACKEND_OIDC_CODE = 'authentication.backends.oidc.OIDCAuthCodeBackend'
|
AUTH_BACKEND_OIDC_CODE = 'authentication.backends.oidc.OIDCAuthCodeBackend'
|
||||||
AUTH_BACKEND_RADIUS = 'authentication.backends.radius.RadiusBackend'
|
AUTH_BACKEND_RADIUS = 'authentication.backends.radius.RadiusBackend'
|
||||||
|
@ -232,7 +271,7 @@ AUTHENTICATION_BACKENDS = [
|
||||||
# 只做权限校验
|
# 只做权限校验
|
||||||
RBAC_BACKEND,
|
RBAC_BACKEND,
|
||||||
# 密码形式
|
# 密码形式
|
||||||
AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_LDAP, AUTH_BACKEND_RADIUS,
|
AUTH_BACKEND_MODEL, AUTH_BACKEND_PUBKEY, AUTH_BACKEND_LDAP, AUTH_BACKEND_LDAP_HA, AUTH_BACKEND_RADIUS,
|
||||||
# 跳转形式
|
# 跳转形式
|
||||||
AUTH_BACKEND_CAS, AUTH_BACKEND_OIDC_PASSWORD, AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_SAML2,
|
AUTH_BACKEND_CAS, AUTH_BACKEND_OIDC_PASSWORD, AUTH_BACKEND_OIDC_CODE, AUTH_BACKEND_SAML2,
|
||||||
AUTH_BACKEND_OAUTH2,
|
AUTH_BACKEND_OAUTH2,
|
||||||
|
|
|
@ -26,12 +26,14 @@ class LDAPUserListApi(generics.ListAPIView):
|
||||||
|
|
||||||
def get_queryset_from_cache(self):
|
def get_queryset_from_cache(self):
|
||||||
search_value = self.request.query_params.get('search')
|
search_value = self.request.query_params.get('search')
|
||||||
users = LDAPCacheUtil().search(search_value=search_value)
|
category = self.request.query_params.get('category')
|
||||||
|
users = LDAPCacheUtil(category=category).search(search_value=search_value)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def get_queryset_from_server(self):
|
def get_queryset_from_server(self):
|
||||||
search_value = self.request.query_params.get('search')
|
search_value = self.request.query_params.get('search')
|
||||||
users = LDAPServerUtil().search(search_value=search_value)
|
category = self.request.query_params.get('category')
|
||||||
|
users = LDAPServerUtil(category=category).search(search_value=search_value)
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
@ -36,6 +36,7 @@ class SettingsApi(generics.RetrieveUpdateAPIView):
|
||||||
'security_password': serializers.SecurityPasswordRuleSerializer,
|
'security_password': serializers.SecurityPasswordRuleSerializer,
|
||||||
'security_login_limit': serializers.SecurityLoginLimitSerializer,
|
'security_login_limit': serializers.SecurityLoginLimitSerializer,
|
||||||
'ldap': serializers.LDAPSettingSerializer,
|
'ldap': serializers.LDAPSettingSerializer,
|
||||||
|
'ldap_ha': serializers.LDAPHASettingSerializer,
|
||||||
'email': serializers.EmailSettingSerializer,
|
'email': serializers.EmailSettingSerializer,
|
||||||
'email_content': serializers.EmailContentSettingSerializer,
|
'email_content': serializers.EmailContentSettingSerializer,
|
||||||
'wecom': serializers.WeComSettingSerializer,
|
'wecom': serializers.WeComSettingSerializer,
|
||||||
|
|
|
@ -4,6 +4,7 @@ from .dingtalk import *
|
||||||
from .feishu import *
|
from .feishu import *
|
||||||
from .lark import *
|
from .lark import *
|
||||||
from .ldap import *
|
from .ldap import *
|
||||||
|
from .ldap_ha import *
|
||||||
from .oauth2 import *
|
from .oauth2 import *
|
||||||
from .oidc import *
|
from .oidc import *
|
||||||
from .passkey import *
|
from .passkey import *
|
||||||
|
|
|
@ -11,6 +11,7 @@ class AuthSettingSerializer(serializers.Serializer):
|
||||||
PREFIX_TITLE = _('Authentication')
|
PREFIX_TITLE = _('Authentication')
|
||||||
|
|
||||||
AUTH_LDAP = serializers.BooleanField(required=False, label=_('LDAP Auth'))
|
AUTH_LDAP = serializers.BooleanField(required=False, label=_('LDAP Auth'))
|
||||||
|
AUTH_LDAP_HA = serializers.BooleanField(required=False, label=_('LDAP Auth HA'))
|
||||||
AUTH_CAS = serializers.BooleanField(required=False, label=_('CAS Auth'))
|
AUTH_CAS = serializers.BooleanField(required=False, label=_('CAS Auth'))
|
||||||
AUTH_OPENID = serializers.BooleanField(required=False, label=_('OPENID Auth'))
|
AUTH_OPENID = serializers.BooleanField(required=False, label=_('OPENID Auth'))
|
||||||
AUTH_SAML2 = serializers.BooleanField(default=False, label=_("SAML2 Auth"))
|
AUTH_SAML2 = serializers.BooleanField(default=False, label=_("SAML2 Auth"))
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from common.serializers.fields import EncryptedField
|
||||||
|
from .base import OrgListField
|
||||||
|
|
||||||
|
__all__ = ['LDAPHATestConfigSerializer', 'LDAPHASettingSerializer']
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPHATestConfigSerializer(serializers.Serializer):
|
||||||
|
AUTH_LDAP_HA_SERVER_URI = serializers.CharField(max_length=1024)
|
||||||
|
AUTH_LDAP_HA_BIND_DN = serializers.CharField(max_length=1024, required=False, allow_blank=True)
|
||||||
|
AUTH_LDAP_HA_BIND_PASSWORD = EncryptedField(required=False, allow_blank=True)
|
||||||
|
AUTH_LDAP_HA_SEARCH_OU = serializers.CharField()
|
||||||
|
AUTH_LDAP_HA_SEARCH_FILTER = serializers.CharField()
|
||||||
|
AUTH_LDAP_HA_USER_ATTR_MAP = serializers.JSONField()
|
||||||
|
AUTH_LDAP_HA_START_TLS = serializers.BooleanField(required=False)
|
||||||
|
AUTH_LDAP_HA = serializers.BooleanField(required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class LDAPHASettingSerializer(serializers.Serializer):
|
||||||
|
# encrypt_fields 现在使用 write_only 来判断了
|
||||||
|
PREFIX_TITLE = _('LDAP HA')
|
||||||
|
|
||||||
|
AUTH_LDAP_HA_SERVER_URI = serializers.CharField(
|
||||||
|
required=True, max_length=1024, label=_('Server'),
|
||||||
|
help_text=_('LDAP HA server URI')
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_BIND_DN = serializers.CharField(
|
||||||
|
required=False, max_length=1024, label=_('Bind DN'),
|
||||||
|
help_text=_('Binding Distinguished Name')
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_BIND_PASSWORD = EncryptedField(
|
||||||
|
max_length=1024, required=False, label=_('Password'),
|
||||||
|
help_text=_('Binding password')
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_SEARCH_OU = serializers.CharField(
|
||||||
|
max_length=1024, allow_blank=True, required=False, label=_('Search OU'),
|
||||||
|
help_text=_(
|
||||||
|
'User Search Base, if there are multiple OUs, you can separate them with the `|` symbol'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_SEARCH_FILTER = serializers.CharField(
|
||||||
|
max_length=1024, required=True, label=_('Search filter'),
|
||||||
|
help_text=_('Selection could include (cn|uid|sAMAccountName=%(user)s)')
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_USER_ATTR_MAP = serializers.JSONField(
|
||||||
|
required=True, label=_('User attribute'),
|
||||||
|
help_text=_(
|
||||||
|
'User attribute mapping, where the `key` is the JumpServer user attribute name and the '
|
||||||
|
'`value` is the LDAP service user attribute name'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_SYNC_IS_PERIODIC = serializers.BooleanField(
|
||||||
|
required=False, label=_('Periodic run')
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_SYNC_CRONTAB = serializers.CharField(
|
||||||
|
required=False, max_length=128, allow_null=True, allow_blank=True,
|
||||||
|
label=_('Crontab')
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_SYNC_INTERVAL = serializers.IntegerField(
|
||||||
|
required=False, default=24, allow_null=True, label=_('Interval')
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_CONNECT_TIMEOUT = serializers.IntegerField(
|
||||||
|
min_value=1, max_value=300,
|
||||||
|
required=False, label=_('Connect timeout (s)'),
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_CACHE_TIMEOUT = serializers.IntegerField(
|
||||||
|
min_value=0, max_value=3600 * 24 * 30 * 12,
|
||||||
|
default=3600 * 24 * 30,
|
||||||
|
required=False, label=_('User DN cache timeout (s)'),
|
||||||
|
help_text=_(
|
||||||
|
'Caching the User DN obtained during user login authentication can effectively'
|
||||||
|
'improve the speed of user authentication., 0 means no cache<br>'
|
||||||
|
'If the user OU structure has been adjusted, click Submit to clear the user DN cache'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_SEARCH_PAGED_SIZE = serializers.IntegerField(
|
||||||
|
required=False, label=_('Search paged size (piece)')
|
||||||
|
)
|
||||||
|
AUTH_LDAP_HA_SYNC_RECEIVERS = serializers.ListField(
|
||||||
|
required=False, label=_('Recipient'), max_length=36
|
||||||
|
)
|
||||||
|
|
||||||
|
AUTH_LDAP_HA = serializers.BooleanField(required=False, label=_('LDAP HA'))
|
||||||
|
AUTH_LDAP_HA_SYNC_ORG_IDS = OrgListField()
|
||||||
|
|
||||||
|
def post_save(self):
|
||||||
|
keys = ['AUTH_LDAP_HA_SYNC_IS_PERIODIC', 'AUTH_LDAP_HA_SYNC_INTERVAL', 'AUTH_LDAP_HA_SYNC_CRONTAB']
|
||||||
|
kwargs = {k: self.validated_data[k] for k in keys if k in self.validated_data}
|
||||||
|
if not kwargs:
|
||||||
|
return
|
||||||
|
from settings.tasks import import_ldap_ha_user_periodic
|
||||||
|
import_ldap_ha_user_periodic(**kwargs)
|
|
@ -1,5 +1,4 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
#
|
|
||||||
import time
|
import time
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -16,45 +15,44 @@ from settings.notifications import LDAPImportMessage
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from ..utils import LDAPSyncUtil, LDAPServerUtil, LDAPImportUtil
|
from ..utils import LDAPSyncUtil, LDAPServerUtil, LDAPImportUtil
|
||||||
|
|
||||||
__all__ = ['sync_ldap_user', 'import_ldap_user_periodic', 'import_ldap_user']
|
__all__ = [
|
||||||
|
'sync_ldap_user', 'import_ldap_user_periodic', 'import_ldap_ha_user_periodic',
|
||||||
|
'import_ldap_user', 'import_ldap_ha_user'
|
||||||
|
]
|
||||||
|
|
||||||
logger = get_logger(__file__)
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
|
||||||
def sync_ldap_user():
|
def sync_ldap_user(category='ldap'):
|
||||||
LDAPSyncUtil().perform_sync()
|
LDAPSyncUtil(category=category).perform_sync()
|
||||||
|
|
||||||
|
|
||||||
@shared_task(
|
def perform_import(category, util_server):
|
||||||
verbose_name=_('Periodic import ldap user'),
|
|
||||||
description=_(
|
|
||||||
"""
|
|
||||||
When LDAP auto-sync is configured, this task will be invoked to synchronize users
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def import_ldap_user():
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
time_start_display = local_now_display()
|
time_start_display = local_now_display()
|
||||||
logger.info("Start import ldap user task")
|
logger.info(f"Start import {category} ldap user task")
|
||||||
util_server = LDAPServerUtil()
|
|
||||||
util_import = LDAPImportUtil()
|
util_import = LDAPImportUtil()
|
||||||
users = util_server.search()
|
users = util_server.search()
|
||||||
|
|
||||||
if settings.XPACK_ENABLED:
|
if settings.XPACK_ENABLED:
|
||||||
org_ids = settings.AUTH_LDAP_SYNC_ORG_IDS
|
org_ids = getattr(settings, f"AUTH_{category.upper()}_SYNC_ORG_IDS")
|
||||||
default_org = None
|
default_org = None
|
||||||
else:
|
else:
|
||||||
# 社区版默认导入Default组织
|
|
||||||
org_ids = [Organization.DEFAULT_ID]
|
org_ids = [Organization.DEFAULT_ID]
|
||||||
default_org = Organization.default()
|
default_org = Organization.default()
|
||||||
|
|
||||||
orgs = list(set([Organization.get_instance(org_id, default=default_org) for org_id in org_ids]))
|
orgs = list(set([Organization.get_instance(org_id, default=default_org) for org_id in org_ids]))
|
||||||
new_users, errors = util_import.perform_import(users, orgs)
|
new_users, errors = util_import.perform_import(users, orgs)
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
logger.error("Imported LDAP users errors: {}".format(errors))
|
logger.error(f"Imported {category} LDAP users errors: {errors}")
|
||||||
else:
|
else:
|
||||||
logger.info('Imported {} users successfully'.format(len(users)))
|
logger.info(f"Imported {len(users)} {category} users successfully")
|
||||||
if settings.AUTH_LDAP_SYNC_RECEIVERS:
|
|
||||||
user_ids = settings.AUTH_LDAP_SYNC_RECEIVERS
|
receivers_setting = f"AUTH_{category.upper()}_SYNC_RECEIVERS"
|
||||||
|
if getattr(settings, receivers_setting, None):
|
||||||
|
user_ids = getattr(settings, receivers_setting)
|
||||||
recipient_list = User.objects.filter(id__in=list(user_ids))
|
recipient_list = User.objects.filter(id__in=list(user_ids))
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
|
@ -70,6 +68,54 @@ def import_ldap_user():
|
||||||
LDAPImportMessage(user, extra_kwargs).publish()
|
LDAPImportMessage(user, extra_kwargs).publish()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(
|
||||||
|
verbose_name=_('Periodic import ldap user'),
|
||||||
|
description=_(
|
||||||
|
"""
|
||||||
|
When LDAP auto-sync is configured, this task will be invoked to synchronize users
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
def import_ldap_user():
|
||||||
|
perform_import('ldap', LDAPServerUtil())
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task(
|
||||||
|
verbose_name=_('Periodic import ldap ha user'),
|
||||||
|
description=_(
|
||||||
|
"""
|
||||||
|
When LDAP auto-sync is configured, this task will be invoked to synchronize users
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
def import_ldap_ha_user():
|
||||||
|
perform_import('ldap_ha', LDAPServerUtil(category='ldap_ha'))
|
||||||
|
|
||||||
|
|
||||||
|
def register_periodic_task(task_name, task_func, interval_key, enabled_key, crontab_key, **kwargs):
|
||||||
|
interval = kwargs.get(interval_key, settings.AUTH_LDAP_SYNC_INTERVAL)
|
||||||
|
enabled = kwargs.get(enabled_key, settings.AUTH_LDAP_SYNC_IS_PERIODIC)
|
||||||
|
crontab = kwargs.get(crontab_key, settings.AUTH_LDAP_SYNC_CRONTAB)
|
||||||
|
|
||||||
|
if isinstance(interval, int):
|
||||||
|
interval = interval * 3600
|
||||||
|
else:
|
||||||
|
interval = None
|
||||||
|
|
||||||
|
if crontab:
|
||||||
|
interval = None # 优先使用 crontab
|
||||||
|
|
||||||
|
tasks = {
|
||||||
|
task_name: {
|
||||||
|
'task': task_func.name,
|
||||||
|
'interval': interval,
|
||||||
|
'crontab': crontab,
|
||||||
|
'enabled': enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
create_or_update_celery_periodic_tasks(tasks)
|
||||||
|
|
||||||
|
|
||||||
@shared_task(
|
@shared_task(
|
||||||
verbose_name=_('Registration periodic import ldap user task'),
|
verbose_name=_('Registration periodic import ldap user task'),
|
||||||
description=_(
|
description=_(
|
||||||
|
@ -81,23 +127,26 @@ def import_ldap_user():
|
||||||
)
|
)
|
||||||
@after_app_ready_start
|
@after_app_ready_start
|
||||||
def import_ldap_user_periodic(**kwargs):
|
def import_ldap_user_periodic(**kwargs):
|
||||||
task_name = 'import_ldap_user_periodic'
|
register_periodic_task(
|
||||||
interval = kwargs.get('AUTH_LDAP_SYNC_INTERVAL', settings.AUTH_LDAP_SYNC_INTERVAL)
|
'import_ldap_user_periodic', import_ldap_user,
|
||||||
enabled = kwargs.get('AUTH_LDAP_SYNC_IS_PERIODIC', settings.AUTH_LDAP_SYNC_IS_PERIODIC)
|
'AUTH_LDAP_SYNC_INTERVAL', 'AUTH_LDAP_SYNC_IS_PERIODIC',
|
||||||
crontab = kwargs.get('AUTH_LDAP_SYNC_CRONTAB', settings.AUTH_LDAP_SYNC_CRONTAB)
|
'AUTH_LDAP_SYNC_CRONTAB', **kwargs
|
||||||
if isinstance(interval, int):
|
)
|
||||||
interval = interval * 3600
|
|
||||||
else:
|
|
||||||
interval = None
|
@shared_task(
|
||||||
if crontab:
|
verbose_name=_('Registration periodic import ldap ha user task'),
|
||||||
# 优先使用 crontab
|
description=_(
|
||||||
interval = None
|
"""
|
||||||
tasks = {
|
When LDAP HA auto-sync parameters change, such as Crontab parameters, the LDAP HA sync task
|
||||||
task_name: {
|
will be re-registered or updated, and this task will be invoked
|
||||||
'task': import_ldap_user.name,
|
"""
|
||||||
'interval': interval,
|
)
|
||||||
'crontab': crontab,
|
)
|
||||||
'enabled': enabled
|
@after_app_ready_start
|
||||||
}
|
def import_ldap_ha_user_periodic(**kwargs):
|
||||||
}
|
register_periodic_task(
|
||||||
create_or_update_celery_periodic_tasks(tasks)
|
'import_ldap_ha_user_periodic', import_ldap_ha_user,
|
||||||
|
'AUTH_LDAP_HA_SYNC_INTERVAL', 'AUTH_LDAP_HA_SYNC_IS_PERIODIC',
|
||||||
|
'AUTH_LDAP_HA_SYNC_CRONTAB', **kwargs
|
||||||
|
)
|
||||||
|
|
|
@ -24,7 +24,8 @@ from ldap3.core.exceptions import (
|
||||||
LDAPAttributeError,
|
LDAPAttributeError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from authentication.backends.ldap import LDAPAuthorizationBackend, LDAPUser
|
from authentication.backends.ldap import LDAPAuthorizationBackend, LDAPUser, \
|
||||||
|
LDAPHAAuthorizationBackend
|
||||||
from common.const import LDAP_AD_ACCOUNT_DISABLE
|
from common.const import LDAP_AD_ACCOUNT_DISABLE
|
||||||
from common.db.utils import close_old_connections
|
from common.db.utils import close_old_connections
|
||||||
from common.utils import timeit, get_logger
|
from common.utils import timeit, get_logger
|
||||||
|
@ -46,7 +47,7 @@ LDAP_USE_CACHE_FLAGS = [1, '1', 'true', 'True', True]
|
||||||
|
|
||||||
class LDAPConfig(object):
|
class LDAPConfig(object):
|
||||||
|
|
||||||
def __init__(self, config=None):
|
def __init__(self, config=None, category='ldap'):
|
||||||
self.server_uri = None
|
self.server_uri = None
|
||||||
self.bind_dn = None
|
self.bind_dn = None
|
||||||
self.password = None
|
self.password = None
|
||||||
|
@ -55,6 +56,7 @@ class LDAPConfig(object):
|
||||||
self.search_filter = None
|
self.search_filter = None
|
||||||
self.attr_map = None
|
self.attr_map = None
|
||||||
self.auth_ldap = None
|
self.auth_ldap = None
|
||||||
|
self.category = category
|
||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
self.load_from_config(config)
|
self.load_from_config(config)
|
||||||
else:
|
else:
|
||||||
|
@ -71,25 +73,26 @@ class LDAPConfig(object):
|
||||||
self.auth_ldap = config.get('auth_ldap')
|
self.auth_ldap = config.get('auth_ldap')
|
||||||
|
|
||||||
def load_from_settings(self):
|
def load_from_settings(self):
|
||||||
self.server_uri = settings.AUTH_LDAP_SERVER_URI
|
prefix = 'AUTH_LDAP' if self.category == 'ldap' else 'AUTH_LDAP_HA'
|
||||||
self.bind_dn = settings.AUTH_LDAP_BIND_DN
|
self.server_uri = getattr(settings, f"{prefix}_SERVER_URI")
|
||||||
self.password = settings.AUTH_LDAP_BIND_PASSWORD
|
self.bind_dn = getattr(settings, f"{prefix}_BIND_DN")
|
||||||
self.use_ssl = settings.AUTH_LDAP_START_TLS
|
self.password = getattr(settings, f"{prefix}_BIND_PASSWORD")
|
||||||
self.search_ou = settings.AUTH_LDAP_SEARCH_OU
|
self.use_ssl = getattr(settings, f"{prefix}_START_TLS")
|
||||||
self.search_filter = settings.AUTH_LDAP_SEARCH_FILTER
|
self.search_ou = getattr(settings, f"{prefix})_SEARCH_OU")
|
||||||
self.attr_map = settings.AUTH_LDAP_USER_ATTR_MAP
|
self.search_filter = getattr(settings, f"{prefix}_SEARCH_FILTER")
|
||||||
self.auth_ldap = settings.AUTH_LDAP
|
self.attr_map = getattr(settings, f"{prefix}_USER_ATTR_MAP")
|
||||||
|
self.auth_ldap = getattr(settings, prefix)
|
||||||
|
|
||||||
|
|
||||||
class LDAPServerUtil(object):
|
class LDAPServerUtil(object):
|
||||||
|
|
||||||
def __init__(self, config=None):
|
def __init__(self, config=None, category='ldap'):
|
||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
self.config = LDAPConfig(config=config)
|
self.config = LDAPConfig(config=config)
|
||||||
elif isinstance(config, LDAPConfig):
|
elif isinstance(config, LDAPConfig):
|
||||||
self.config = config
|
self.config = config
|
||||||
else:
|
else:
|
||||||
self.config = LDAPConfig()
|
self.config = LDAPConfig(category=category)
|
||||||
self._conn = None
|
self._conn = None
|
||||||
self._paged_size = self.get_paged_size()
|
self._paged_size = self.get_paged_size()
|
||||||
self.search_users = None
|
self.search_users = None
|
||||||
|
@ -230,25 +233,29 @@ class LDAPServerUtil(object):
|
||||||
|
|
||||||
|
|
||||||
class LDAPCacheUtil(object):
|
class LDAPCacheUtil(object):
|
||||||
CACHE_KEY_USERS = 'CACHE_KEY_LDAP_USERS'
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, category='ldap'):
|
||||||
self.search_users = None
|
self.search_users = None
|
||||||
self.search_value = None
|
self.search_value = None
|
||||||
|
self.category = category
|
||||||
|
if self.category == 'ldap':
|
||||||
|
self.cache_key_users = 'CACHE_KEY_LDAP_USERS'
|
||||||
|
else:
|
||||||
|
self.cache_key_users = 'CACHE_KEY_LDAP_HA_USERS'
|
||||||
|
|
||||||
def set_users(self, users):
|
def set_users(self, users):
|
||||||
logger.info('Set ldap users to cache, count: {}'.format(len(users)))
|
logger.info('Set ldap users to cache, count: {}'.format(len(users)))
|
||||||
cache.set(self.CACHE_KEY_USERS, users, None)
|
cache.set(self.cache_key_users, users, None)
|
||||||
|
|
||||||
def get_users(self):
|
def get_users(self):
|
||||||
users = cache.get(self.CACHE_KEY_USERS)
|
users = cache.get(self.cache_key_users)
|
||||||
count = users if users is None else len(users)
|
count = users if users is None else len(users)
|
||||||
logger.info('Get ldap users from cache, count: {}'.format(count))
|
logger.info('Get ldap users from cache, count: {}'.format(count))
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def delete_users(self):
|
def delete_users(self):
|
||||||
logger.info('Delete ldap users from cache')
|
logger.info('Delete ldap users from cache')
|
||||||
cache.delete(self.CACHE_KEY_USERS)
|
cache.delete(self.cache_key_users)
|
||||||
|
|
||||||
def filter_users(self, users):
|
def filter_users(self, users):
|
||||||
if users is None:
|
if users is None:
|
||||||
|
@ -288,10 +295,11 @@ class LDAPSyncUtil(object):
|
||||||
TASK_STATUS_IS_RUNNING = 'RUNNING'
|
TASK_STATUS_IS_RUNNING = 'RUNNING'
|
||||||
TASK_STATUS_IS_OVER = 'OVER'
|
TASK_STATUS_IS_OVER = 'OVER'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, category='ldap'):
|
||||||
self.server_util = LDAPServerUtil()
|
self.server_util = LDAPServerUtil(category=category)
|
||||||
self.cache_util = LDAPCacheUtil()
|
self.cache_util = LDAPCacheUtil(category=category)
|
||||||
self.task_error_msg = None
|
self.task_error_msg = None
|
||||||
|
self.category = category
|
||||||
|
|
||||||
def clear_cache(self):
|
def clear_cache(self):
|
||||||
logger.info('Clear ldap sync cache')
|
logger.info('Clear ldap sync cache')
|
||||||
|
@ -347,7 +355,7 @@ class LDAPSyncUtil(object):
|
||||||
def perform_sync(self):
|
def perform_sync(self):
|
||||||
logger.info('Start perform sync ldap users from server to cache')
|
logger.info('Start perform sync ldap users from server to cache')
|
||||||
try:
|
try:
|
||||||
ok, msg = LDAPTestUtil().test_config()
|
ok, msg = LDAPTestUtil(category=self.category).test_config()
|
||||||
if not ok:
|
if not ok:
|
||||||
raise self.LDAPSyncUtilException(msg)
|
raise self.LDAPSyncUtilException(msg)
|
||||||
self.sync()
|
self.sync()
|
||||||
|
@ -377,6 +385,7 @@ class LDAPImportUtil(object):
|
||||||
user['email'] = self.get_user_email(user)
|
user['email'] = self.get_user_email(user)
|
||||||
if user['username'] not in ['admin']:
|
if user['username'] not in ['admin']:
|
||||||
user['source'] = User.Source.ldap.value
|
user['source'] = User.Source.ldap.value
|
||||||
|
user.pop('status', None)
|
||||||
obj, created = User.objects.update_or_create(
|
obj, created = User.objects.update_or_create(
|
||||||
username=user['username'], defaults=user
|
username=user['username'], defaults=user
|
||||||
)
|
)
|
||||||
|
@ -476,9 +485,13 @@ class LDAPTestUtil(object):
|
||||||
class LDAPBeforeLoginCheckError(LDAPExceptionError):
|
class LDAPBeforeLoginCheckError(LDAPExceptionError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, config=None):
|
def __init__(self, config=None, category='ldap'):
|
||||||
self.config = LDAPConfig(config)
|
self.config = LDAPConfig(config, category)
|
||||||
self.user_entries = []
|
self.user_entries = []
|
||||||
|
if category == 'ldap':
|
||||||
|
self.backend = LDAPAuthorizationBackend()
|
||||||
|
else:
|
||||||
|
self.backend = LDAPHAAuthorizationBackend()
|
||||||
|
|
||||||
def _test_connection_bind(self, authentication=None, user=None, password=None):
|
def _test_connection_bind(self, authentication=None, user=None, password=None):
|
||||||
server = Server(self.config.server_uri)
|
server = Server(self.config.server_uri)
|
||||||
|
@ -656,15 +669,12 @@ class LDAPTestUtil(object):
|
||||||
if not cache.get(CACHE_KEY_LDAP_TEST_CONFIG_TASK_STATUS):
|
if not cache.get(CACHE_KEY_LDAP_TEST_CONFIG_TASK_STATUS):
|
||||||
self.test_config()
|
self.test_config()
|
||||||
|
|
||||||
backend = LDAPAuthorizationBackend()
|
ok, msg = self.backend.pre_check(username, password)
|
||||||
ok, msg = backend.pre_check(username, password)
|
|
||||||
if not ok:
|
if not ok:
|
||||||
raise self.LDAPBeforeLoginCheckError(msg)
|
raise self.LDAPBeforeLoginCheckError(msg)
|
||||||
|
|
||||||
@staticmethod
|
def _test_login_auth(self, username, password):
|
||||||
def _test_login_auth(username, password):
|
ldap_user = LDAPUser(self.backend, username=username.strip())
|
||||||
backend = LDAPAuthorizationBackend()
|
|
||||||
ldap_user = LDAPUser(backend, username=username.strip())
|
|
||||||
ldap_user._authenticate_user_dn(password)
|
ldap_user._authenticate_user_dn(password)
|
||||||
|
|
||||||
def _test_login(self, username, password):
|
def _test_login(self, username, password):
|
||||||
|
|
|
@ -3,16 +3,17 @@
|
||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from asgiref.sync import sync_to_async
|
|
||||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext_lazy as _, activate
|
from django.utils.translation import gettext_lazy as _, activate
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
from urllib.parse import parse_qs
|
||||||
|
|
||||||
from common.db.utils import close_old_connections
|
from common.db.utils import close_old_connections
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
from settings.serializers import (
|
from settings.serializers import (
|
||||||
|
LDAPHATestConfigSerializer,
|
||||||
LDAPTestConfigSerializer,
|
LDAPTestConfigSerializer,
|
||||||
LDAPTestLoginSerializer
|
LDAPTestLoginSerializer
|
||||||
)
|
)
|
||||||
|
@ -101,8 +102,12 @@ class ToolsWebsocket(AsyncJsonWebsocketConsumer):
|
||||||
|
|
||||||
|
|
||||||
class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||||
|
category: str
|
||||||
|
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
user = self.scope["user"]
|
user = self.scope["user"]
|
||||||
|
query = parse_qs(self.scope['query_string'].decode())
|
||||||
|
self.category = query.get('category', ['ldap'])[0]
|
||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
await self.accept()
|
await self.accept()
|
||||||
else:
|
else:
|
||||||
|
@ -125,30 +130,21 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||||
await self.close()
|
await self.close()
|
||||||
close_old_connections()
|
close_old_connections()
|
||||||
|
|
||||||
@staticmethod
|
def get_ldap_config(self, serializer):
|
||||||
def get_ldap_config(serializer):
|
prefix = 'AUTH_LDAP_' if self.category == 'ldap' else 'AUTH_LDAP_HA_'
|
||||||
server_uri = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
|
|
||||||
bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
|
|
||||||
password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
|
|
||||||
use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
|
|
||||||
search_ou = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
|
|
||||||
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
|
|
||||||
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
|
|
||||||
auth_ldap = serializer.validated_data.get('AUTH_LDAP', False)
|
|
||||||
|
|
||||||
if not password:
|
|
||||||
password = settings.AUTH_LDAP_BIND_PASSWORD
|
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
'server_uri': server_uri,
|
'server_uri': serializer.validated_data.get(f"{prefix}SERVER_URI"),
|
||||||
'bind_dn': bind_dn,
|
'bind_dn': serializer.validated_data.get(f"{prefix}BIND_DN"),
|
||||||
'password': password,
|
'password': (serializer.validated_data.get(f"{prefix}BIND_PASSWORD") or
|
||||||
'use_ssl': use_ssl,
|
getattr(settings, f"{prefix}BIND_PASSWORD")),
|
||||||
'search_ou': search_ou,
|
'use_ssl': serializer.validated_data.get(f"{prefix}START_TLS", False),
|
||||||
'search_filter': search_filter,
|
'search_ou': serializer.validated_data.get(f"{prefix}SEARCH_OU"),
|
||||||
'attr_map': attr_map,
|
'search_filter': serializer.validated_data.get(f"{prefix}SEARCH_FILTER"),
|
||||||
'auth_ldap': auth_ldap
|
'attr_map': serializer.validated_data.get(f"{prefix}USER_ATTR_MAP"),
|
||||||
|
'auth_ldap': serializer.validated_data.get(f"{prefix.rstrip('_')}", False)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -160,7 +156,10 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||||
cache.set(task_key, TASK_STATUS_IS_OVER, ttl)
|
cache.set(task_key, TASK_STATUS_IS_OVER, ttl)
|
||||||
|
|
||||||
def run_testing_config(self, data):
|
def run_testing_config(self, data):
|
||||||
serializer = LDAPTestConfigSerializer(data=data)
|
if self.category == 'ldap':
|
||||||
|
serializer = LDAPTestConfigSerializer(data=data)
|
||||||
|
else:
|
||||||
|
serializer = LDAPHATestConfigSerializer(data=data)
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
self.send_msg(msg=f'error: {str(serializer.errors)}')
|
self.send_msg(msg=f'error: {str(serializer.errors)}')
|
||||||
config = self.get_ldap_config(serializer)
|
config = self.get_ldap_config(serializer)
|
||||||
|
@ -175,14 +174,13 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||||
self.send_msg(msg=f'error: {str(serializer.errors)}')
|
self.send_msg(msg=f'error: {str(serializer.errors)}')
|
||||||
username = serializer.validated_data['username']
|
username = serializer.validated_data['username']
|
||||||
password = serializer.validated_data['password']
|
password = serializer.validated_data['password']
|
||||||
ok, msg = LDAPTestUtil().test_login(username, password)
|
ok, msg = LDAPTestUtil(category=self.category).test_login(username, password)
|
||||||
return ok, msg
|
return ok, msg
|
||||||
|
|
||||||
@staticmethod
|
def run_sync_user(self, data):
|
||||||
def run_sync_user(data):
|
sync_util = LDAPSyncUtil(category=self.category)
|
||||||
sync_util = LDAPSyncUtil()
|
|
||||||
sync_util.clear_cache()
|
sync_util.clear_cache()
|
||||||
sync_ldap_user()
|
sync_ldap_user(category=self.category)
|
||||||
msg = sync_util.get_task_error_msg()
|
msg = sync_util.get_task_error_msg()
|
||||||
ok = False if msg else True
|
ok = False if msg else True
|
||||||
return ok, msg
|
return ok, msg
|
||||||
|
@ -215,7 +213,7 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||||
return ok, msg
|
return ok, msg
|
||||||
|
|
||||||
def set_users_status(self, import_users, errors):
|
def set_users_status(self, import_users, errors):
|
||||||
util = LDAPCacheUtil()
|
util = LDAPCacheUtil(category=self.category)
|
||||||
all_users = util.get_users()
|
all_users = util.get_users()
|
||||||
import_usernames = [u['username'] for u in import_users]
|
import_usernames = [u['username'] for u in import_users]
|
||||||
errors_mapper = {k: v for err in errors for k, v in err.items()}
|
errors_mapper = {k: v for err in errors for k, v in err.items()}
|
||||||
|
@ -225,7 +223,7 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||||
user['status'] = {'error': errors_mapper[username]}
|
user['status'] = {'error': errors_mapper[username]}
|
||||||
elif username in import_usernames:
|
elif username in import_usernames:
|
||||||
user['status'] = ImportStatus.ok
|
user['status'] = ImportStatus.ok
|
||||||
LDAPCacheUtil().set_users(all_users)
|
LDAPCacheUtil(category=self.category).set_users(all_users)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_orgs(org_ids):
|
def get_orgs(org_ids):
|
||||||
|
@ -235,12 +233,11 @@ class LdapWebsocket(AsyncJsonWebsocketConsumer):
|
||||||
orgs = [current_org]
|
orgs = [current_org]
|
||||||
return orgs
|
return orgs
|
||||||
|
|
||||||
@staticmethod
|
def get_ldap_users(self, username_list, cache_police):
|
||||||
def get_ldap_users(username_list, cache_police):
|
|
||||||
if '*' in username_list:
|
if '*' in username_list:
|
||||||
users = LDAPServerUtil().search()
|
users = LDAPServerUtil(category=self.category).search()
|
||||||
elif cache_police in LDAP_USE_CACHE_FLAGS:
|
elif cache_police in LDAP_USE_CACHE_FLAGS:
|
||||||
users = LDAPCacheUtil().search(search_users=username_list)
|
users = LDAPCacheUtil(category=self.category).search(search_users=username_list)
|
||||||
else:
|
else:
|
||||||
users = LDAPServerUtil().search(search_users=username_list)
|
users = LDAPServerUtil(category=self.category).search(search_users=username_list)
|
||||||
return users
|
return users
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
class Source(models.TextChoices):
|
class Source(models.TextChoices):
|
||||||
local = "local", _("Local")
|
local = "local", _("Local")
|
||||||
ldap = "ldap", "LDAP/AD"
|
ldap = "ldap", "LDAP/AD"
|
||||||
|
ldap_ha = "ldap_ha", "LDAP/AD (HA)"
|
||||||
openid = "openid", "OpenID"
|
openid = "openid", "OpenID"
|
||||||
radius = "radius", "Radius"
|
radius = "radius", "Radius"
|
||||||
cas = "cas", "CAS"
|
cas = "cas", "CAS"
|
||||||
|
@ -55,6 +56,7 @@ class SourceMixin:
|
||||||
mapper = {
|
mapper = {
|
||||||
cls.Source.local: True,
|
cls.Source.local: True,
|
||||||
cls.Source.ldap: settings.AUTH_LDAP,
|
cls.Source.ldap: settings.AUTH_LDAP,
|
||||||
|
cls.Source.ldap_ha: settings.AUTH_LDAP_HA,
|
||||||
cls.Source.openid: settings.AUTH_OPENID,
|
cls.Source.openid: settings.AUTH_OPENID,
|
||||||
cls.Source.radius: settings.AUTH_RADIUS,
|
cls.Source.radius: settings.AUTH_RADIUS,
|
||||||
cls.Source.cas: settings.AUTH_CAS,
|
cls.Source.cas: settings.AUTH_CAS,
|
||||||
|
|
Loading…
Reference in New Issue