实现:列出LDAP服务器上的用户属性,方便管理员设置LDAP服务器配置项。

pull/130/head
Apex Liu 2018-10-31 00:37:42 +08:00
parent de047d9a72
commit e62d2b3546
6 changed files with 274 additions and 59 deletions

View File

@ -257,7 +257,7 @@ function tp_error_msg(error_code, message) {
var msg = ''; var msg = '';
switch (error_code) { switch (error_code) {
case TPE_NEED_LOGIN: case TPE_NEED_LOGIN:
msg = '需要登录'; msg = '需要刷新页面,重新登录';
break; break;
case TPE_PRIVILEGE: case TPE_PRIVILEGE:
msg = '没有此操作权限'; msg = '没有此操作权限';

View File

@ -192,6 +192,9 @@ $app.create_controls = function (cb_stack) {
$app.dlg_ldap_config.show(); $app.dlg_ldap_config.show();
}); });
$app.dlg_ldap_list_attr_result = $app.create_dlg_ldap_list_attr_result();
cb_stack.add($app.dlg_ldap_list_attr_result.init);
$app.dlg_ldap_test_result = $app.create_dlg_ldap_test_result(); $app.dlg_ldap_test_result = $app.create_dlg_ldap_test_result();
cb_stack.add($app.dlg_ldap_test_result.init); cb_stack.add($app.dlg_ldap_test_result.init);
@ -1245,11 +1248,13 @@ $app.create_dlg_ldap_config = function () {
btn_switch_password: $('#btn-switch-ldap-password'), btn_switch_password: $('#btn-switch-ldap-password'),
btn_switch_password_icon: $('#btn-switch-ldap-password i'), btn_switch_password_icon: $('#btn-switch-ldap-password i'),
btn_list_attr: $('#btn-ldap-config-list-attr'),
btn_test: $('#btn-ldap-config-test'), btn_test: $('#btn-ldap-config-test'),
btn_save: $('#btn-ldap-config-save') btn_save: $('#btn-ldap-config-save')
}; };
dlg.init = function (cb_stack) { dlg.init = function (cb_stack) {
dlg.dom.btn_list_attr.click(dlg.do_list_attr);
dlg.dom.btn_test.click(dlg.do_test); dlg.dom.btn_test.click(dlg.do_test);
dlg.dom.btn_save.click(dlg.do_save); dlg.dom.btn_save.click(dlg.do_save);
@ -1361,6 +1366,32 @@ $app.create_dlg_ldap_config = function () {
return true; return true;
}; };
dlg.do_list_attr = function () {
if (!dlg.check_fields())
return;
dlg.dom.btn_test.attr('disabled', 'disabled');
$tp.ajax_post_json('/user/do-ldap-config-list-attr', {
c: dlg.ldap_config,
p: dlg.ldap_config_password
},
function (ret) {
dlg.dom.btn_test.removeAttr('disabled');
if (ret.code === TPE_OK) {
$tp.notify_success('列举LDAP用户属性成功');
console.log(ret.data);
$app.dlg_ldap_list_attr_result.show(ret.data.attributes);
} else {
$tp.notify_error('列举LDAP用户属性失败' + tp_error_msg(ret.code, ret.message));
}
},
function () {
dlg.dom.btn_test.removeAttr('disabled');
$tp.notify_error('网络故障列举LDAP用户属性失败');
},
15000
);
};
dlg.do_test = function () { dlg.do_test = function () {
if (!dlg.check_fields()) if (!dlg.check_fields())
return; return;
@ -1372,6 +1403,7 @@ $app.create_dlg_ldap_config = function () {
function (ret) { function (ret) {
dlg.dom.btn_test.removeAttr('disabled'); dlg.dom.btn_test.removeAttr('disabled');
if (ret.code === TPE_OK) { if (ret.code === TPE_OK) {
console.log(ret.data);
$tp.notify_success('LDAP连接测试成功'); $tp.notify_success('LDAP连接测试成功');
$app.dlg_ldap_test_result.show(ret.data); $app.dlg_ldap_test_result.show(ret.data);
} else { } else {
@ -1420,6 +1452,41 @@ $app.create_dlg_ldap_config = function () {
return dlg; return dlg;
}; };
$app.create_dlg_ldap_list_attr_result = function () {
var dlg = {};
dlg.dom_id = 'dlg-ldap-list-attr-result';
dlg.dom = {
dialog: $('#' + dlg.dom_id),
msg_ret: $('#msg-ldap-list-attr-ret')
};
dlg.init = function (cb_stack) {
cb_stack.exec();
};
dlg.show = function (data) {
dlg.dom.msg_ret.html('');
var h = [];
var attr_name;
for (attr_name in data) {
h.push('<div style="white-space:nowrap;"><span class="mono important">' + attr_name + '</span>: ');
// h.push('<span>'+data[attr_name]+'</span></div>');
h.push('<span>');
h.push(data[attr_name].join(', '));
h.push('</span></div>');
}
dlg.dom.msg_ret.html($(h.join('')));
dlg.dom.dialog.modal();
};
return dlg;
};
$app.create_dlg_ldap_test_result = function () { $app.create_dlg_ldap_test_result = function () {
var dlg = {}; var dlg = {};
dlg.dom_id = 'dlg-ldap-test-result'; dlg.dom_id = 'dlg-ldap-test-result';
@ -1437,13 +1504,40 @@ $app.create_dlg_ldap_test_result = function () {
dlg.show = function (data) { dlg.show = function (data) {
dlg.dom.table.empty(); dlg.dom.table.empty();
// var h = [];
// var i, x;
// var th_created = false;
// for (i = 0; i < data.length; ++i) {
// if (!th_created) {
// h.push('<thead>');
// for (x in data[i]) {
// h.push('<th style="text-align:left;" class="mono">');
// h.push(x);
// h.push('</th>');
// }
// h.push('</thead>');
// th_created = true;
// }
//
// h.push('<tr>');
// for (x in data[i]) {
// h.push('<td style="text-align:left;" class="mono">');
// if (!_.isEmpty(data[i][x]))
// h.push(data[i][x]);
// else
// h.push('');
// h.push('</td>');
// }
// h.push('</tr>');
// }
var h = []; var h = [];
var i, x; var dn, x;
var th_created = false; var th_created = false;
for (i = 0; i < data.length; ++i) { for (dn in data) {
if (!th_created) { if (!th_created) {
h.push('<thead>'); h.push('<thead>');
for (x in data[i]) { for (x in data[dn]) {
h.push('<th style="text-align:left;" class="mono">'); h.push('<th style="text-align:left;" class="mono">');
h.push(x); h.push(x);
h.push('</th>'); h.push('</th>');
@ -1453,10 +1547,10 @@ $app.create_dlg_ldap_test_result = function () {
} }
h.push('<tr>'); h.push('<tr>');
for (x in data[i]) { for (x in data[dn]) {
h.push('<td style="text-align:left;" class="mono">'); h.push('<td style="text-align:left;" class="mono">');
if (!_.isEmpty(data[i][x])) if (!_.isEmpty(data[dn][x]))
h.push(data[i][x]); h.push(data[dn][x]);
else else
h.push(''); h.push('');
h.push('</td>'); h.push('</td>');
@ -1464,6 +1558,7 @@ $app.create_dlg_ldap_test_result = function () {
h.push('</tr>'); h.push('</tr>');
} }
dlg.dom.table.append($(h.join(''))); dlg.dom.table.append($(h.join('')));
dlg.dom.dialog.modal(); dlg.dom.dialog.modal();
}; };

View File

@ -389,7 +389,7 @@
<div class="form-horizontal"> <div class="form-horizontal">
<div class="form-group form-group-sm"> <div class="form-group form-group-sm">
<label for="edit-ldap-host" class="col-sm-2 control-label require">主机:</label> <label for="edit-ldap-host" class="col-sm-2 control-label require">LDAP主机:</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input id="edit-ldap-host" type="text" class="form-control" placeholder="LDAP服务器IP或域名" value="192.168.0.10"/> <input id="edit-ldap-host" type="text" class="form-control" placeholder="LDAP服务器IP或域名" value="192.168.0.10"/>
</div> </div>
@ -408,17 +408,17 @@
<input id="edit-ldap-domain" type="text" class="form-control" placeholder="" value="apexnas.com"/> <input id="edit-ldap-domain" type="text" class="form-control" placeholder="" value="apexnas.com"/>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="control-desc">teleport将会用 <span class="important">用户名@域</span> 来访问此LDAP服务器。</div> <div class="control-desc-sm">LDAP的账号使用 <span class="important">用户名@域</span> 来登录teleport。</div>
</div> </div>
</div> </div>
<div class="form-group form-group-sm"> <div class="form-group form-group-sm">
<label for="edit-ldap-admin" class="col-sm-2 control-label require">管理员:</label> <label for="edit-ldap-admin" class="col-sm-2 control-label require">管理员DN</label>
<div class="col-sm-4"> <div class="col-sm-4">
<input id="edit-ldap-admin" type="text" class="form-control" placeholder="" value="cn=admin,dc=apexnas,dc=com"/> <input id="edit-ldap-admin" type="text" class="form-control" placeholder="" value="cn=admin,dc=apexnas,dc=com"/>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="control-desc">LDAP服务的管理员账号用于列举用户、同步账号。</div> <div class="control-desc-sm">LDAP服务的管理员账号用于列举用户、同步账号。</div>
</div> </div>
</div> </div>
@ -430,6 +430,9 @@
<span class="input-group-btn"><button class="btn btn-sm btn-default" type="button" id="btn-switch-ldap-password"><i class="fa fa-eye fa-fw"></i></button></span> <span class="input-group-btn"><button class="btn btn-sm btn-default" type="button" id="btn-switch-ldap-password"><i class="fa fa-eye fa-fw"></i></button></span>
</div> </div>
</div> </div>
<div class="col-sm-6">
<div class="control-desc-sm">LDAP服务的管理员密码。</div>
</div>
</div> </div>
</div> </div>
@ -457,10 +460,10 @@
<div class="form-group form-group-sm"> <div class="form-group form-group-sm">
<label for="edit-ldap-attr-map" class="col-sm-2 control-label require">属性映射:</label> <label for="edit-ldap-attr-map" class="col-sm-2 control-label require">属性映射:</label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea id="edit-ldap-attr-map" class="form-control" style="resize:vertical;height:8em;" placeholder="">tp.username = sAMAccountName <textarea id="edit-ldap-attr-map" class="form-control" style="resize:vertical;height:8em;" placeholder="">tp.username = uid
tp.surname = cn tp.surname = cn
tp.email = mail</textarea> tp.email = mail</textarea>
<div class="control-desc-sm">将LDAP的属性映射到 teleport 的用户属性,例如 <span class="important">LDAP中的用户sAMAccountName 映射到teleport的登录账号</span>。</div> <div class="control-desc-sm">将LDAP的属性映射到 teleport 的用户属性,例如 <span class="important">LDAP中的用户属性 sAMAccountName 映射为teleport的登录账号</span>。如果不清楚此LDAP服务的用户属性可使用下方的“列举属性”按钮进行查询。</div>
</div> </div>
</div> </div>
@ -474,7 +477,8 @@ tp.email = mail</textarea>
<div id="edit-user-message" class="alert alert-danger" style="text-align:left;display:none;"></div> <div id="edit-user-message" class="alert alert-danger" style="text-align:left;display:none;"></div>
</div> </div>
<div class="col-sm-6" style="text-align:right;"> <div class="col-sm-6" style="text-align:right;">
<button type="button" class="btn btn-sm btn-success" id="btn-ldap-config-test"><i class="fa fa-bolt fa-fw"></i> 测试连接</button> <button type="button" class="btn btn-sm btn-success" id="btn-ldap-config-list-attr"><i class="fa fa-list-alt fa-fw"></i> 列举属性</button>
<button type="button" class="btn btn-sm btn-success" id="btn-ldap-config-test"><i class="fa fa-bolt fa-fw"></i> 测试获取用户</button>
<button type="button" class="btn btn-sm btn-primary" id="btn-ldap-config-save"><i class="fa fa-check fa-fw"></i> 保存设置</button> <button type="button" class="btn btn-sm btn-primary" id="btn-ldap-config-save"><i class="fa fa-check fa-fw"></i> 保存设置</button>
<button type="button" class="btn btn-sm btn-default" data-dismiss="modal"><i class="fa fa-times fa-fw"></i> 取消</button> <button type="button" class="btn btn-sm btn-default" data-dismiss="modal"><i class="fa fa-times fa-fw"></i> 取消</button>
</div> </div>
@ -484,6 +488,42 @@ tp.email = mail</textarea>
</div> </div>
</div> </div>
<div class="modal fade" id="dlg-ldap-list-attr-result" tabindex="-1" role="dialog">
<div class="modal-dialog" style="margin-top:50px;">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="fa fa-times-circle fa-fw"></i></button>
<h3 class="modal-title">LDAP用户属性一览</h3>
</div>
<div class="modal-body">
<div class="form-horizontal">
<div style="margin-bottom:8px;">
用户属性列表
</div>
<div class="form-group form-group-sm">
<div class="col-sm-12">
<div id="msg-ldap-list-attr-ret" style="height:20em;overflow:scroll;padding:5px;border: 1px solid #747474;"></div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<div class="row">
<div class="col-sm-12" style="text-align:right;">
<button type="button" class="btn btn-sm btn-primary" data-dismiss="modal"><i class="fa fa-check fa-fw"></i> 确定</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="dlg-ldap-test-result" tabindex="-1" role="dialog"> <div class="modal fade" id="dlg-ldap-test-result" tabindex="-1" role="dialog">
<div class="modal-dialog" style="margin-top:50px;"> <div class="modal-dialog" style="margin-top:50px;">
<div class="modal-content"> <div class="modal-content">

View File

@ -82,6 +82,8 @@ controllers = [
(r'/user/do-unbind-oath', user.DoUnBindOathHandler), (r'/user/do-unbind-oath', user.DoUnBindOathHandler),
# - [json] 测试LDAP的配置 # - [json] 测试LDAP的配置
(r'/user/do-ldap-config-test', user.DoLdapConfigTestHandler), (r'/user/do-ldap-config-test', user.DoLdapConfigTestHandler),
# - [json] 列出LDAP服务器的用户的属性便于管理员做属性映射
(r'/user/do-ldap-config-list-attr', user.DoLdapListUserAttrHandler),
# #
# - 用户组管理页面 # - 用户组管理页面
(r'/user/group', user.GroupListHandler), (r'/user/group', user.GroupListHandler),

View File

@ -929,7 +929,8 @@ class DoLdapConfigTestHandler(TPBaseJsonHandler):
return self.write_json(TPE_PARAM) return self.write_json(TPE_PARAM)
try: try:
ldap = Ldap(cfg['host'], cfg['port'], cfg['base_dn'], cfg['domain']) # ldap = Ldap(cfg['host'], cfg['port'], cfg['base_dn'], cfg['domain'])
ldap = Ldap(cfg['host'], cfg['port'], cfg['base_dn'])
ret, data, err_msg = ldap.list_users(cfg['admin'], password, cfg['filter'], cfg['attr_map'], size_limit=10) ret, data, err_msg = ldap.list_users(cfg['admin'], password, cfg['filter'], cfg['attr_map'], size_limit=10)
if ret != TPE_OK: if ret != TPE_OK:
return self.write_json(ret, message=err_msg) return self.write_json(ret, message=err_msg)
@ -940,3 +941,35 @@ class DoLdapConfigTestHandler(TPBaseJsonHandler):
return self.write_json(TPE_PARAM) return self.write_json(TPE_PARAM)
class DoLdapListUserAttrHandler(TPBaseJsonHandler):
def post(self):
ret = self.check_privilege(TP_PRIVILEGE_USER_CREATE)
if ret != TPE_OK:
return
args = self.get_argument('args', None)
if args is None:
return self.write_json(TPE_PARAM)
try:
args = json.loads(args)
except:
return self.write_json(TPE_JSON_FORMAT)
try:
cfg = args['c']
cfg['port'] = int(cfg['port'])
password = args['p']
except:
return self.write_json(TPE_PARAM)
try:
ldap = Ldap(cfg['host'], cfg['port'], cfg['base_dn'])
ret, data, err_msg = ldap.get_all_attr(cfg['admin'], password, cfg['filter'])
if ret != TPE_OK:
return self.write_json(ret, message=err_msg)
else:
return self.write_json(ret, data=data)
except:
log.e('')
return self.write_json(TPE_PARAM)

View File

@ -1,15 +1,17 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import json
import ldap3 import ldap3
import ldap3.core.exceptions
from app.base.logger import * from app.base.logger import *
from app.const import * from app.const import *
class Ldap(object): class Ldap(object):
def __init__(self, ldap_host, ldap_port, base_dn, domain): def __init__(self, ldap_host, ldap_port, base_dn):
self._server = ldap3.Server(ldap_host, ldap_port, connect_timeout=5, use_ssl=False) self._server = ldap3.Server(ldap_host, ldap_port, connect_timeout=5, use_ssl=False)
self._base_dn = base_dn self._base_dn = base_dn
self._domain = domain # self._domain = domain
pass pass
def _parse_attr_map(self, attr_map): def _parse_attr_map(self, attr_map):
@ -33,6 +35,56 @@ class Ldap(object):
return attrs_ldap, attrs_tp, '' return attrs_ldap, attrs_tp, ''
def get_all_attr(self, admin, password, filter):
# user = '{}@{}'.format(admin, self._domain)
user = admin
conn = ldap3.Connection(self._server, user=user, password=password, check_names=True, lazy=False, raise_exceptions=False)
try:
conn.open()
except Exception as e:
log.e(str(e))
return TPE_FAILED, None, '无法连接到LDAP服务器'
conn.bind()
if not ('result' in conn.result and 0 == conn.result['result'] and 'description' in conn.result and 'success' == conn.result['description']):
return TPE_FAILED, None, 'LDAP管理员认证失败'
ret = conn.search(
search_base=self._base_dn,
size_limit=1,
search_filter=filter, # (&(objectClass=person))
search_scope=ldap3.SUBTREE,
attributes=['*']
)
if not ret:
return TPE_FAILED, None, '未能找到任何用户'
if len(conn.response) == 0:
return TPE_FAILED, None, '未能找到任何用户'
# print(conn.entries[0].entry_to_json())
result = json.loads(conn.entries[0].entry_to_json())
# attrs = conn.response[0]['attributes']
#
# result = {}
# for a in attrs:
# if isinstance(attrs[a], list):
# val = []
# for i in attrs[a]:
# if isinstance(i, bytes):
# val.append(i.decode())
# else:
# val.append(i)
# result[a] = ', '.join(val)
# else:
# if isinstance(attrs[a], bytes):
# result[a] = attrs[a].decode()
# else:
# result[a] = attrs[a].__str__()
return TPE_OK, result, ''
def list_users(self, admin, password, filter, attr_map, size_limit=0): def list_users(self, admin, password, filter, attr_map, size_limit=0):
attrs_ldap, attrs_tp, msg = self._parse_attr_map(attr_map) attrs_ldap, attrs_tp, msg = self._parse_attr_map(attr_map)
if attrs_ldap is None: if attrs_ldap is None:
@ -51,56 +103,49 @@ class Ldap(object):
if not ('result' in conn.result and 0 == conn.result['result'] and 'description' in conn.result and 'success' == conn.result['description']): if not ('result' in conn.result and 0 == conn.result['result'] and 'description' in conn.result and 'success' == conn.result['description']):
return TPE_FAILED, None, 'LDAP管理员认证失败' return TPE_FAILED, None, 'LDAP管理员认证失败'
# for test, list all attributes. try:
ret_a = conn.search(
search_base=self._base_dn,
size_limit=size_limit,
# search_filter='(&(sAMAccountName={}*)(&(objectClass=person)))'.format(username),
# search_filter=filter, # (&(objectClass=person))
search_filter='(cn=*)',
search_scope=ldap3.SUBTREE,
# attributes=['cn', 'mail', 'sAMAccountName', 'objectGUID']
attributes=['*']
)
if len(conn.response) == 0:
return TPE_FAILED, [], ''
u = conn.response[0]
log.v(u['attributes'])
# ...
ret = conn.search( ret = conn.search(
search_base=self._base_dn, search_base=self._base_dn,
size_limit=size_limit, size_limit=size_limit,
# search_filter='(&(sAMAccountName={}*)(&(objectClass=person)))'.format(username),
search_filter=filter, # (&(objectClass=person)) search_filter=filter, # (&(objectClass=person))
search_scope=ldap3.SUBTREE, search_scope=ldap3.SUBTREE,
# attributes=['cn', 'mail', 'sAMAccountName', 'objectGUID']
# attributes=['*']
attributes=attrs_ldap attributes=attrs_ldap
) )
result = [] if not ret:
return TPE_FAILED, None, '未能搜索到LDAP用户请检查用户基准DN和过滤器设置'
# print(self.conn.entries[0].entry_to_json) except ldap3.core.exceptions.LDAPAttributeError as e:
log.e('')
return TPE_FAILED, None, '请检查属性映射设置:{}'.format(e.__str__())
if ret: result = {}
for u in conn.response:
# if u['attributes']['cn'].lower() in ['guest', 'krbtgt']: for i in range(0, len(conn.entries)):
# continue u = json.loads(conn.entries[0].entry_to_json())
# print(u) # result.append(u)
# print(u['attributes']['cn'])
# result.append(u['attributes'])
a = {} a = {}
for i in range(0, len(attrs_ldap)): for m in range(0, len(attrs_ldap)):
a[attrs_tp[i]] = u['attributes'][attrs_ldap[i]] a[attrs_tp[m]] = u['attributes'][attrs_ldap[m]]
result.append(a) # result.append(a)
result[u['dn']] = a
# print(conn.entries[0].entry_to_json())
#
# if ret:
# for u in conn.response:
# # if u['attributes']['cn'].lower() in ['guest', 'krbtgt']:
# # continue
# # print(u)
# # print(u['attributes']['cn'])
# # result.append(u['attributes'])
# a = {}
# for i in range(0, len(attrs_ldap)):
# a[attrs_tp[i]] = u['attributes'][attrs_ldap[i]]
# result.append(a)
return TPE_OK, result, '' return TPE_OK, result, ''
def valid_user(self, user_dn, password): def valid_user(self, user_dn, password):
return False return False