From 2eb55e08793d0435403daf14384233c2769fc1be Mon Sep 17 00:00:00 2001 From: zypo Date: Wed, 23 Nov 2022 10:30:12 +0800 Subject: [PATCH] add ladp config --- spug_api/apps/account/views.py | 2 +- spug_api/apps/setting/urls.py | 2 + spug_api/apps/setting/views.py | 80 ++++++++++-- spug_api/libs/ldap.py | 78 ++++++++--- .../src/pages/system/setting/LDAPSetting.js | 80 +++++++++--- .../src/pages/system/setting/LdapImport.js | 123 ++++++++++++++++++ spug_web/src/pages/system/setting/store.js | 28 +++- 7 files changed, 343 insertions(+), 50 deletions(-) create mode 100644 spug_web/src/pages/system/setting/LdapImport.js diff --git a/spug_api/apps/account/views.py b/spug_api/apps/account/views.py index 182b9bd..d9608e4 100644 --- a/spug_api/apps/account/views.py +++ b/spug_api/apps/account/views.py @@ -201,7 +201,7 @@ def login(request): if not config: return handle_response(error='请在系统设置中配置LDAP后再尝试通过该方式登录') ldap = LDAP(**config) - is_success, message = ldap.valid_user(form.username, form.password) + is_success, message = ldap.verify_user(form.username, form.password) if is_success: if not user: user = User.objects.create(username=form.username, nickname=form.username, type=form.type) diff --git a/spug_api/apps/setting/urls.py b/spug_api/apps/setting/urls.py index 89c6de5..33ca444 100644 --- a/spug_api/apps/setting/urls.py +++ b/spug_api/apps/setting/urls.py @@ -9,7 +9,9 @@ from apps.setting.user import UserSettingView urlpatterns = [ url(r'^$', SettingView.as_view()), url(r'^user/$', UserSettingView.as_view()), + url(r'^ldap/$', LDAPUserView.as_view()), url(r'^ldap_test/$', ldap_test), + url(r'^ldap_import/$', ldap_import), url(r'^email_test/$', email_test), url(r'^mfa/$', MFAView.as_view()), url(r'^about/$', get_about) diff --git a/spug_api/apps/setting/views.py b/spug_api/apps/setting/views.py index 417cc15..ffa5cef 100644 --- a/spug_api/apps/setting/views.py +++ b/spug_api/apps/setting/views.py @@ -6,14 +6,16 @@ from django.core.cache import cache from django.conf import settings from libs import JsonParser, Argument, json_response, auth from libs.utils import generate_random_str +from libs.ldap import LDAP from libs.mail import Mail from libs.spug import send_login_wx_code from libs.mixins import AdminView from apps.setting.utils import AppSetting from apps.setting.models import Setting, KEYS_DEFAULT +from apps.account.models import User from copy import deepcopy import platform -import ldap +import json class SettingView(AdminView): @@ -68,21 +70,79 @@ class MFAView(AdminView): def ldap_test(request): form, error = JsonParser( Argument('server'), - Argument('port', type=int), Argument('admin_dn'), - Argument('password'), + Argument('admin_password'), + Argument('user_ou'), + Argument('user_filter'), + Argument('map_username'), + Argument('map_nickname'), + ).parse(request.body) + print('form', form) + if error is None: + ldap = LDAP(form.server, form.admin_dn, form.admin_password, form.user_ou, form.user_filter, form.map_username, form.map_nickname) + status, ret = ldap.all_user() + if status: + return json_response(ret) + return json_response(error=ret) + return json_response(error=error) + + +@auth('admin') +def ldap_import(request): + form, error = JsonParser( + Argument('ldap_data', type=list), + Argument('username'), + Argument('nickname'), ).parse(request.body) if error is None: - try: - con = ldap.initialize("ldap://{0}:{1}".format(form.server, form.port), bytes_mode=False) - con.simple_bind_s(form.admin_dn, form.password) - return json_response() - except Exception as e: - error = eval(str(e)) - return json_response(error=error['desc']) + for x in form.ldap_data: + User.objects.update_or_create( + username=x[form.username], + defaults={'nickname': x[form.nickname], 'type': 'ldap'} + ) + return json_response() return json_response(error=error) +class LDAPUserView(AdminView): + def get(self, request): + ldap_config = AppSetting.get('ldap_service') + if not ldap_config: + return json_response(error='LDAP服务未配置') + ldap = LDAP(**ldap_config) + status, ret = ldap.all_user() + if status: + cn_key, sn_key = ldap_config.get('map_username'), ldap_config.get('map_nickname') + system_users = [x.username for x in User.objects.filter(type='ldap', deleted_by_id__isnull=True)] + for index, u in enumerate(ret): + u['cn'] = u[cn_key] + u['sn'] = u[sn_key] + u['is_exist'] = u.get(cn_key) in system_users + u['id'] = index + return json_response(ret) + return json_response(error=ret) + + def post(self, request): + form, error = JsonParser( + Argument('server'), + Argument('admin_dn'), + Argument('admin_password'), + Argument('user_ou'), + Argument('user_filter'), + Argument('map_username'), + Argument('map_nickname'), + Argument('ldap_user', help='LDAP用户不能为空'), + Argument('ldap_password', help='LDAP密码不能为空'), + ).parse(request.body) + if error is None: + ldap = LDAP(form.server, form.admin_dn, form.admin_password, form.user_ou, form.user_filter, form.map_username, form.map_nickname) + status, msg = ldap.verify_user(form.ldap_user, form.ldap_password) + if status: + return json_response() + return json_response(error=msg) + return json_response(error=error) + + @auth('admin') def email_test(request): form, error = JsonParser( diff --git a/spug_api/libs/ldap.py b/spug_api/libs/ldap.py index fc9b6c0..d7a391c 100644 --- a/spug_api/libs/ldap.py +++ b/spug_api/libs/ldap.py @@ -5,26 +5,68 @@ import ldap class LDAP: - def __init__(self, server, port, rules, admin_dn, password, base_dn): + def __init__(self, server, admin_dn, admin_password, user_ou, user_filter, map_username, map_nickname): self.server = server - self.port = port - self.rules = rules self.admin_dn = admin_dn - self.password = password - self.base_dn = base_dn + self.admin_dn = admin_dn + self.admin_password = admin_password + self.user_ou = user_ou + self.user_filter = user_filter + self.map_username = map_username + self.map_nickname = map_nickname - def valid_user(self, username, password): + + def connect(self): try: - conn = ldap.initialize("ldap://{0}:{1}".format(self.server, self.port), bytes_mode=False) - conn.simple_bind_s(self.admin_dn, self.password) - search_filter = f'({self.rules}={username})' - ldap_result_id = conn.search(self.base_dn, ldap.SCOPE_SUBTREE, search_filter, None) - result_type, result_data = conn.result(ldap_result_id, 0) - if result_type == ldap.RES_SEARCH_ENTRY: - conn.simple_bind_s(result_data[0][0], password) - return True, None - else: - return False, None + conn = ldap.initialize(f'{self.server}', bytes_mode=False) + conn.set_option(ldap.OPT_TIMEOUT, 3) + conn.set_option(ldap.OPT_NETWORK_TIMEOUT, 3) + conn.simple_bind_s(self.admin_dn, self.admin_password) + return True, conn except Exception as error: - args = error.args - return False, args[0].get('desc', '未知错误') if args else '%s' % error + return False, error.args[0].get('desc') + + + def all_user(self): + status, conn = self.connect() + if status: + try: + # user_filter = '(cn=*)' + # map = ['cn', 'sn'] + # user_map = list(self.user_map.values()) + user_filter = "({}=*)".format(self.user_filter.split('=')[0][1:]) + user_map = [self.map_username, self.map_nickname] + ldap_result = conn.search_s(self.user_ou, ldap.SCOPE_SUBTREE, user_filter, user_map) + ldap_users = [] + for dn,entry in ldap_result: + if dn == self.user_ou: + continue + tmp_user = {} + for k,v in entry.items(): + tmp_user.update({k: v[0].decode()}) + + ldap_users.append(tmp_user) + return True, ldap_users + + except Exception as error: + return False, error.args[0].get('desc') + else: + return False, conn + + def verify_user(self, username, password): + status, conn = self.connect() + if status: + try: + user_filter = f'({self.map_username}={username})' + ldap_result_id = conn.search(self.user_ou, ldap.SCOPE_SUBTREE, user_filter, [self.map_username]) + _, result_data = conn.result(ldap_result_id, 0) + if result_data: + conn.simple_bind_s(result_data[0][0], password) + return True, True + else: + return False, '账户未找到' + except Exception as error: + return False, error.args[0].get('desc') + else: + return False, conn + diff --git a/spug_web/src/pages/system/setting/LDAPSetting.js b/spug_web/src/pages/system/setting/LDAPSetting.js index a25c46f..a17e6f3 100644 --- a/spug_web/src/pages/system/setting/LDAPSetting.js +++ b/spug_web/src/pages/system/setting/LDAPSetting.js @@ -5,8 +5,9 @@ */ import React, { useState } from 'react'; import styles from './index.module.css'; -import { Form, Button, Input, Space, message } from 'antd'; +import { Form, Button, Input, Space, message, Modal } from 'antd'; import { http } from 'libs'; +import LdapImport from './LdapImport'; import { observer } from 'mobx-react' import store from './store'; @@ -17,7 +18,7 @@ export default observer(function () { function handleSubmit() { store.loading = true; const formData = form.getFieldsValue(); - http.post('/api/setting/', {data: [{key: 'ldap_service', value: formData}]}) + http.post('/api/setting/', { data: [{ key: 'ldap_service', value: formData }] }) .then(() => { message.success('保存成功'); store.fetchSettings() @@ -28,39 +29,78 @@ export default observer(function () { function ldapTest() { setLoading(true); const formData = form.getFieldsValue(); - http.post('/api/setting/ldap_test/', formData).then(() => { - message.success('LDAP服务连接成功') + http.post('/api/setting/ldap_test/', formData).then((res) => { + message.success("成功匹配" + res.length + "个用户") }).finally(() => setLoading(false)) } + + function ldapLogin(info) { + let ldadUser; + let ldadPwd; + Modal.confirm({ + title: 'LDAP用户测试登录', + content:
+ + ldadUser = val.target.value }/> + + + ldadPwd = val.target.value}/> + +
, + onOk: () => { + setLoading(true); + const formData = form.getFieldsValue(); + formData.ldap_user = ldadUser; + formData.ldap_password = ldadPwd; + return http.post('/api/setting/ldap/', formData) + .then(() => message.success('登录成功', 1)).finally(() => setLoading(false)) + }, + }) + }; + + return (
LDAP设置
-
- - + + + - - + + + - - + + - - + + + - - + + + - - + {/* + + */} + + + + + + - + + + -
+ {store.importVisible && } + ) }) \ No newline at end of file diff --git a/spug_web/src/pages/system/setting/LdapImport.js b/spug_web/src/pages/system/setting/LdapImport.js new file mode 100644 index 0000000..d866d0a --- /dev/null +++ b/spug_web/src/pages/system/setting/LdapImport.js @@ -0,0 +1,123 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the AGPL-3.0 License. + */ +import React, { useState } from 'react'; +import { observer } from 'mobx-react'; +import { Modal, Input, message, Badge, Button } from 'antd'; +import http from 'libs/http'; +import { TableCard } from 'components'; +import store from './store'; + + +export default observer(function () { + const [loading, setLoading] = useState(false); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + function handleImportSelect() { + setLoading(true); + let ldap_data = []; + for (let item of store.dataSource) { + if (selectedRowKeys.includes(item.id)) { + ldap_data.push(item); + } + } + handleSubmit(ldap_data); + } + + function handleImportAll() { + setLoading(true); + handleSubmit(store.dataSource); + } + + function handleSubmit(data) { + if (data) { + http.post('/api/setting/ldap_import/', + {'ldap_data': data, 'username': store.username, 'nickname': store.nickname}) + .then(() => { + message.success('操作成功'); + store.importVisible = false; + }, () => setLoading(false)) + } + } + + function handleClickRow(record) { + let tmp = new Set(selectedRowKeys) + if (!tmp.delete(record.id)) { + tmp.add(record.id) + } + setSelectedRowKeys([...tmp]) + } + + function handleSelectAll(selected) { + let tmp = new Set(selectedRowKeys) + for (let item of store.dataSource) { + if (selected) { + tmp.add(item.id) + } else { + tmp.delete(item.id) + } + } + setSelectedRowKeys([...tmp]) + } + + let columns = [{ + title: '登录名', + dataIndex: "cn", + }, { + title: '姓名', + dataIndex: "sn", + }, { + title: '是否存在', + render: text => text.is_exist ? : , + } + ]; + + + return ( + store.importVisible = false} + confirmLoading={loading} + onOk={handleImportAll} + footer={[ + , + , + , + ]}> + + { + return { + onClick: () => handleClickRow(record) + } + }} + actions={[ + store.f_name = e.target.value } placeholder="搜索LDAP用户" />, + ]} + pagination={{ + showSizeChanger: true, + showLessItems: true, + showTotal: total => `共 ${total} 条`, + pageSizeOptions: ['10', '20', '50', '100'] + }} + rowSelection={{ + selectedRowKeys, + onSelect: handleClickRow, + onSelectAll: handleSelectAll + }} + columns={columns} /> + + + ) +}) diff --git a/spug_web/src/pages/system/setting/store.js b/spug_web/src/pages/system/setting/store.js index 2b6fa7f..5b31359 100644 --- a/spug_web/src/pages/system/setting/store.js +++ b/spug_web/src/pages/system/setting/store.js @@ -3,13 +3,23 @@ * Copyright (c) * Released under the AGPL-3.0 License. */ -import { observable } from "mobx"; +import { observable, computed } from "mobx"; import http from 'libs/http'; class Store { @observable settings = {}; @observable isFetching = false; @observable loading = false; + @observable importVisible = false; + @observable records = []; + @observable f_name; + + @computed get dataSource() { + let records = this.records; + if (this.f_name) records = records.filter(x => x.cn.toLowerCase().includes(this.f_name.toLowerCase())); + return records + } + fetchSettings = () => { this.isFetching = true; @@ -21,6 +31,22 @@ class Store { update = (key, value) => { this.settings[key] = value } + + + fetchLdapRecords = () => { + this.isFetching = true; + http.get('/api/setting/ldap/') + .then((res) => { + this.records = res; + }) + .finally(() => this.isFetching = false) + }; + + + handleLdapImport = () => { + this.importVisible = true; + this.fetchLdapRecords(); + } } export default new Store()