mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-09-06 09:51:00 +00:00
1.5.7 Merge to dev (#3766)
* [Update] 暂存,优化解决不了问题 * [Update] 待续(小白) * [Update] 修改asset user * [Update] 计划再次更改 * [Update] 修改asset user * [Update] 暂存与喜爱 * [Update] Add id in * [Update] 阶段性完成ops task该做 * [Update] 修改asset user api * [Update] 修改asset user 任务,查看认证等 * [Update] 基本完成asset user改造 * [Update] dynamic user only allow 1 * [Update] 修改asset user task * [Update] 修改node admin user task api * [Update] remove file header license * [Update] 添加sftp root * [Update] 暂存 * [Update] 暂存 * [Update] 修改翻译 * [Update] 修改系统用户改为同名后,用户名改为空 * [Update] 基本完成CAS调研 * [Update] 支持cas server * [Update] 支持cas server * [Update] 添加requirements * [Update] 为方便调试添加mysql ipython到包中 * [Update] 添加huaweiyun翻译 * [Update] 增加下载session 录像 * [Update] 只有第一次通知replay离线的使用方法 * [Update] 暂存一下 * [Bugfix] 获取系统用户信息报错 * [Bugfix] 修改system user info * [Update] 改成清理10天git status * [Update] 修改celery日志保留时间 * [Update]修复部分pip包依赖的版本不兼容问题 (#3672) * [Update] 修复用户更新页面会清空用户public_key的问题 * Fix broken dependencies Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> * [Update] 修改获取系统用户auth info * [Update] Remove log * [Bugfix] 修复sftp home设置的bug * [Update] 授权的系统用户添加sftp root * [Update] 修改系统用户关联的用户 * [Update] 修改placeholder * [Update] 优化获取授权的系统用户 * [Update] 修改tasks * [Update] tree service update * [Update] 暂存 * [Update] 基本完成用户授权树和资产树改造 * [Update] Dashbaord perf * [update] Add huawei cloud sdk requirements * [Updte] 优化dashboard页面 * [Update] system user auth info 添加id * [Update] 修改系统用户serializer * [Update] 优化api * [Update] LDAP Test Util (#3720) * [Update] LDAPTestUtil 1 * [Update] LDAPTestUtil 2 * [Update] LDAPTestUtil 3 * [Update] LDAPTestUtil 4 * [Update] LDAPTestUtil 5 * [Update] LDAPTestUtil 6 * [Update] LDAPTestUtil 7 * [Update] session 已添加is success,并且添加display serializer * [Bugfix] 修复无法删除空节点的bug * [Update] 命令记录分组织显示 * [Update] Session is_success 添加迁移文件 * [Update] 批量命令添加org_id * [Update] 修复一些文案,修改不绑定MFA,不能ssh登录 * [Update] 修改replay api, 返回session信息 * [Update] 解决无效es导致访问命令记录页面失败的问题 * [Update] 拆分profile view * [Update] 修改一个翻译 * [Update] 修改aysnc api框架 * [Update] 命令列表添加risk level * [Update] 完成录像打包下载 * [Update] 更改登陆otp页面 * [Update] 修改command 存储redis_level * [Update] 修改翻译 * [Update] 修改系统用户的用户列表字段 * [Update] 使用新logo和统一Jumpserver为JumpServer * [Update] 优化cloud task * [Update] 统一period task * [Update] 统一period form serializer字段 * [Update] 修改period task * [Update] 修改资产网关信息 * [Update] 用户授权资产树资产信息添加domain * [Update] 修改翻译 * [Update] 测试可连接性 * 1.5.7 bai (#3764) * [Update] 修复index页面Bug;修复测试资产用户可连接性问题; * [Update] 修改测试资产用户可连接 * [Bugfix] 修复backends问题 * [Update] 修改marksafe依赖版本 * [Update] 修改测试资产用户可连接性 * [Update] 修改检测服务器性能时获取percent值 * [Update] 更新依赖boto3=1.12.14 Co-authored-by: Yanzhe Lee <lee.yanzhe@yanzhe.org> Co-authored-by: BaiJiangJie <32935519+BaiJiangJie@users.noreply.github.com> Co-authored-by: Bai <bugatti_it@163.com>
This commit is contained in:
@@ -1,21 +1,38 @@
|
||||
# coding: utf-8
|
||||
#
|
||||
|
||||
from ldap3 import Server, Connection
|
||||
import json
|
||||
from ldap3 import Server, Connection, SIMPLE
|
||||
from ldap3.core.exceptions import (
|
||||
LDAPSocketOpenError,
|
||||
LDAPSocketReceiveError,
|
||||
LDAPSessionTerminatedByServerError,
|
||||
LDAPUserNameIsMandatoryError,
|
||||
LDAPPasswordIsMandatoryError,
|
||||
LDAPInvalidDnError,
|
||||
LDAPInvalidServerError,
|
||||
LDAPBindError,
|
||||
LDAPInvalidFilterError,
|
||||
LDAPExceptionError,
|
||||
LDAPConfigurationError,
|
||||
LDAPAttributeError,
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from copy import deepcopy
|
||||
|
||||
from common.const import LDAP_AD_ACCOUNT_DISABLE
|
||||
from common.utils import timeit, get_logger
|
||||
from users.utils import construct_user_email
|
||||
from users.models import User
|
||||
from authentication.backends.ldap import LDAPAuthorizationBackend, LDAPUser
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
__all__ = [
|
||||
'LDAPConfig', 'LDAPServerUtil', 'LDAPCacheUtil', 'LDAPImportUtil',
|
||||
'LDAPSyncUtil', 'LDAP_USE_CACHE_FLAGS'
|
||||
'LDAPSyncUtil', 'LDAP_USE_CACHE_FLAGS', 'LDAPTestUtil',
|
||||
]
|
||||
|
||||
LDAP_USE_CACHE_FLAGS = [1, '1', 'true', 'True', True]
|
||||
@@ -28,9 +45,10 @@ class LDAPConfig(object):
|
||||
self.bind_dn = None
|
||||
self.password = None
|
||||
self.use_ssl = None
|
||||
self.search_ougroup = None
|
||||
self.search_ou = None
|
||||
self.search_filter = None
|
||||
self.attr_map = None
|
||||
self.auth_ldap = None
|
||||
if isinstance(config, dict):
|
||||
self.load_from_config(config)
|
||||
else:
|
||||
@@ -41,18 +59,20 @@ class LDAPConfig(object):
|
||||
self.bind_dn = config.get('bind_dn')
|
||||
self.password = config.get('password')
|
||||
self.use_ssl = config.get('use_ssl')
|
||||
self.search_ougroup = config.get('search_ougroup')
|
||||
self.search_ou = config.get('search_ou')
|
||||
self.search_filter = config.get('search_filter')
|
||||
self.attr_map = config.get('attr_map')
|
||||
self.auth_ldap = config.get('auth_ldap')
|
||||
|
||||
def load_from_settings(self):
|
||||
self.server_uri = settings.AUTH_LDAP_SERVER_URI
|
||||
self.bind_dn = settings.AUTH_LDAP_BIND_DN
|
||||
self.password = settings.AUTH_LDAP_BIND_PASSWORD
|
||||
self.use_ssl = settings.AUTH_LDAP_START_TLS
|
||||
self.search_ougroup = settings.AUTH_LDAP_SEARCH_OU
|
||||
self.search_ou = settings.AUTH_LDAP_SEARCH_OU
|
||||
self.search_filter = settings.AUTH_LDAP_SEARCH_FILTER
|
||||
self.attr_map = settings.AUTH_LDAP_USER_ATTR_MAP
|
||||
self.auth_ldap = settings.AUTH_LDAP
|
||||
|
||||
|
||||
class LDAPServerUtil(object):
|
||||
@@ -93,7 +113,7 @@ class LDAPServerUtil(object):
|
||||
cookie = self.connection.result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
|
||||
return cookie
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
logger.error(e, exc_info=True)
|
||||
return None
|
||||
|
||||
def get_search_filter_extra(self):
|
||||
@@ -129,7 +149,7 @@ class LDAPServerUtil(object):
|
||||
def search_user_entries(self):
|
||||
logger.info("Search user entries")
|
||||
user_entries = list()
|
||||
search_ous = str(self.config.search_ougroup).split('|')
|
||||
search_ous = str(self.config.search_ou).split('|')
|
||||
for search_ou in search_ous:
|
||||
logger.info("Search user entries ou: {}".format(search_ou))
|
||||
self.search_user_entries_ou(search_ou)
|
||||
@@ -332,4 +352,221 @@ class LDAPImportUtil(object):
|
||||
return errors
|
||||
|
||||
|
||||
class LDAPTestUtil(object):
|
||||
class LDAPInvalidSearchOuOrFilterError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
class LDAPInvalidAttributeMapError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
class LDAPNotEnabledAuthError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
class LDAPBeforeLoginCheckError(LDAPExceptionError):
|
||||
pass
|
||||
|
||||
def __init__(self, config=None):
|
||||
self.config = LDAPConfig(config)
|
||||
self.user_entries = []
|
||||
|
||||
def _test_connection_bind(self, authentication=None, user=None, password=None):
|
||||
server = Server(self.config.server_uri)
|
||||
connection = Connection(
|
||||
server, user=user, password=password, authentication=authentication
|
||||
)
|
||||
ret = connection.bind()
|
||||
return ret
|
||||
|
||||
# test server uri
|
||||
|
||||
def _test_server_uri(self):
|
||||
self._test_connection_bind()
|
||||
|
||||
def test_server_uri(self):
|
||||
try:
|
||||
self._test_server_uri()
|
||||
except LDAPSocketOpenError as e:
|
||||
error = _("Host or port is disconnected: {}".format(e))
|
||||
except LDAPSessionTerminatedByServerError as e:
|
||||
error = _('The port is not the port of the LDAP service: {}'.format(e))
|
||||
except LDAPSocketReceiveError as e:
|
||||
error = _('Please enter the certificate: {}'.format(e))
|
||||
except Exception as e:
|
||||
error = _('Unknown error: {}'.format(e))
|
||||
else:
|
||||
return
|
||||
raise LDAPInvalidServerError(error)
|
||||
|
||||
# test bind dn
|
||||
|
||||
def _test_bind_dn(self):
|
||||
user = self.config.bind_dn
|
||||
password = self.config.password
|
||||
ret = self._test_connection_bind(
|
||||
authentication=SIMPLE, user=user, password=password
|
||||
)
|
||||
if not ret:
|
||||
msg = _('bind dn or password incorrect')
|
||||
raise LDAPInvalidDnError(msg)
|
||||
|
||||
def test_bind_dn(self):
|
||||
try:
|
||||
self._test_bind_dn()
|
||||
except LDAPUserNameIsMandatoryError as e:
|
||||
error = _('Please enter bind dn: {}'.format(e))
|
||||
except LDAPPasswordIsMandatoryError as e:
|
||||
error = _('Please enter password: {}'.format(e))
|
||||
except LDAPInvalidDnError as e:
|
||||
error = _('Please enter correct bind dn and password: {}'.format(e))
|
||||
except Exception as e:
|
||||
error = _('Unknown error: {}'.format(e))
|
||||
else:
|
||||
return
|
||||
raise LDAPBindError(error)
|
||||
|
||||
# test search ou
|
||||
|
||||
def _test_search_ou_and_filter(self):
|
||||
config = deepcopy(self.config)
|
||||
util = LDAPServerUtil(config=config)
|
||||
search_ous = str(self.config.search_ou).split('|')
|
||||
for search_ou in search_ous:
|
||||
util.config.search_ou = search_ou
|
||||
user_entries = util.search_user_entries()
|
||||
logger.debug('Search ou: {}, count user: {}'.format(search_ou, len(user_entries)))
|
||||
if len(user_entries) == 0:
|
||||
error = _('Invalid search ou or filter: {}'.format(search_ou))
|
||||
raise self.LDAPInvalidSearchOuOrFilterError(error)
|
||||
|
||||
def test_search_ou_and_filter(self):
|
||||
try:
|
||||
self._test_search_ou_and_filter()
|
||||
except LDAPInvalidFilterError as e:
|
||||
error = e
|
||||
except self.LDAPInvalidSearchOuOrFilterError as e:
|
||||
error = e
|
||||
except LDAPAttributeError as e:
|
||||
error = e
|
||||
raise self.LDAPInvalidAttributeMapError(error)
|
||||
except Exception as e:
|
||||
error = _('Unknown error: {}'.format(e))
|
||||
else:
|
||||
return
|
||||
raise self.LDAPInvalidSearchOuOrFilterError(error)
|
||||
|
||||
# test attr map
|
||||
|
||||
def _test_attr_map(self):
|
||||
attr_map = self.config.attr_map
|
||||
if not isinstance(attr_map, dict):
|
||||
attr_map = json.loads(attr_map)
|
||||
self.config.attr_map = attr_map
|
||||
|
||||
should_contain_attr = {'username', 'name', 'email'}
|
||||
actually_contain_attr = set(attr_map.keys())
|
||||
result = should_contain_attr - actually_contain_attr
|
||||
if len(result) != 0:
|
||||
error = _('LDAP attribute not include: {}'.format(result))
|
||||
raise self.LDAPInvalidAttributeMapError(error)
|
||||
|
||||
def test_attr_map(self):
|
||||
try:
|
||||
self._test_attr_map()
|
||||
except json.JSONDecodeError:
|
||||
error = _('LDAP attribute map is not dict')
|
||||
except self.LDAPInvalidAttributeMapError as e:
|
||||
error = e
|
||||
except Exception as e:
|
||||
error = _('Unknown error: {}'.format(e))
|
||||
else:
|
||||
return
|
||||
raise self.LDAPInvalidAttributeMapError(error)
|
||||
|
||||
# test search
|
||||
|
||||
def test_search(self):
|
||||
util = LDAPServerUtil(config=self.config)
|
||||
self.user_entries = util.search_user_entries()
|
||||
|
||||
# test auth ldap enabled
|
||||
|
||||
def test_enabled_auth_ldap(self):
|
||||
if not self.config.auth_ldap:
|
||||
error = _('LDAP authentication is not enabled')
|
||||
raise self.LDAPNotEnabledAuthError(error)
|
||||
|
||||
# test config
|
||||
|
||||
def _test_config(self):
|
||||
self.test_server_uri()
|
||||
self.test_bind_dn()
|
||||
self.test_attr_map()
|
||||
self.test_search_ou_and_filter()
|
||||
self.test_search()
|
||||
self.test_enabled_auth_ldap()
|
||||
|
||||
def test_config(self):
|
||||
status = False
|
||||
try:
|
||||
self._test_config()
|
||||
except LDAPInvalidServerError as e:
|
||||
msg = _('Error (Invalid server uri): {}'.format(e))
|
||||
except LDAPBindError as e:
|
||||
msg = _('Error (Invalid bind dn): {}'.format(e))
|
||||
except self.LDAPInvalidAttributeMapError as e:
|
||||
msg = _('Error (Invalid attribute map): {}'.format(e))
|
||||
except self.LDAPInvalidSearchOuOrFilterError as e:
|
||||
msg = _('Error (Invalid search ou or filter): {}'.format(e))
|
||||
except self.LDAPNotEnabledAuthError as e:
|
||||
msg = _('Error (Not enabled LDAP authentication): {}'.format(e))
|
||||
except Exception as e:
|
||||
msg = _('Error (Unknown): {}').format(e)
|
||||
else:
|
||||
status = True
|
||||
msg = _('Succeed: Match {} s user'.format(len(self.user_entries)))
|
||||
|
||||
if not status:
|
||||
logger.error(msg, exc_info=True)
|
||||
return status, msg
|
||||
|
||||
# test login
|
||||
|
||||
def _test_before_login_check(self, username, password):
|
||||
ok, msg = self.test_config()
|
||||
if not ok:
|
||||
raise LDAPConfigurationError(msg)
|
||||
|
||||
backend = LDAPAuthorizationBackend()
|
||||
ok, msg = backend.pre_check(username, password)
|
||||
if not ok:
|
||||
raise self.LDAPBeforeLoginCheckError(msg)
|
||||
|
||||
@staticmethod
|
||||
def _test_login_auth(username, password):
|
||||
backend = LDAPAuthorizationBackend()
|
||||
ldap_user = LDAPUser(backend, username=username.strip())
|
||||
ldap_user._authenticate_user_dn(password)
|
||||
|
||||
def _test_login(self, username, password):
|
||||
self._test_before_login_check(username, password)
|
||||
self._test_login_auth(username, password)
|
||||
|
||||
def test_login(self, username, password):
|
||||
status = False
|
||||
try:
|
||||
self._test_login(username, password)
|
||||
except LDAPConfigurationError as e:
|
||||
msg = _('Authentication failed (configuration incorrect): {}'.format(e))
|
||||
except self.LDAPBeforeLoginCheckError as e:
|
||||
msg = _('Authentication failed (before login check failed): {}'.format(e))
|
||||
except LDAPUser.AuthenticationFailed as e:
|
||||
msg = _('Authentication failed (username or password incorrect): {}'.format(e))
|
||||
except Exception as e:
|
||||
msg = _("Authentication failed (Unknown): {}".format(e))
|
||||
else:
|
||||
status = True
|
||||
msg = _("Authentication success: {}".format(username))
|
||||
|
||||
if not status:
|
||||
logger.error(msg, exc_info=True)
|
||||
return status, msg
|
||||
|
Reference in New Issue
Block a user