From 6fb943012923f638bc22da94fd80025c4e6a43b6 Mon Sep 17 00:00:00 2001 From: zypo Date: Sun, 8 Mar 2020 21:06:38 +0800 Subject: [PATCH] =?UTF-8?q?A=20=E5=A2=9E=E5=8A=A0=E6=94=AF=E6=8C=81LDAP?= =?UTF-8?q?=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spug_api/apps/account/models.py | 3 +- spug_api/apps/account/views.py | 102 ++++++++++++----- spug_api/apps/setting/urls.py | 7 +- spug_api/apps/setting/utils.py | 2 +- spug_api/apps/setting/views.py | 23 ++++ spug_api/libs/ldap.py | 54 +++++++++ spug_api/requirements.txt | 3 +- spug_web/src/pages/login/index.js | 3 +- spug_web/src/pages/system/account/Table.js | 3 + .../src/pages/system/setting/BasicSetting.js | 24 +++- .../src/pages/system/setting/LDAPSetting.js | 104 ++++++++++++++++++ spug_web/src/pages/system/setting/index.js | 3 + spug_web/src/pages/system/setting/store.js | 2 +- 13 files changed, 290 insertions(+), 43 deletions(-) create mode 100644 spug_api/libs/ldap.py create mode 100644 spug_web/src/pages/system/setting/LDAPSetting.js diff --git a/spug_api/apps/account/models.py b/spug_api/apps/account/models.py index 914d3e7..543b74e 100644 --- a/spug_api/apps/account/models.py +++ b/spug_api/apps/account/models.py @@ -8,9 +8,10 @@ import json class User(models.Model, ModelMixin): - username = models.CharField(max_length=100, unique=True) + username = models.CharField(max_length=100) nickname = models.CharField(max_length=100) password_hash = models.CharField(max_length=100) # hashed password + type = models.CharField(max_length=20, default='系统用户') is_supper = models.BooleanField(default=False) is_active = models.BooleanField(default=True) access_token = models.CharField(max_length=32) diff --git a/spug_api/apps/account/views.py b/spug_api/apps/account/views.py index 007a71b..88e6b60 100644 --- a/spug_api/apps/account/views.py +++ b/spug_api/apps/account/views.py @@ -9,6 +9,7 @@ from apps.account.models import User, Role import time import uuid import json +from libs.ldap import LDAP class UserView(View): @@ -30,6 +31,7 @@ class UserView(View): if error is None: form.password_hash = User.make_password(form.pop('password')) form.created_by = request.user + form.type = '系统用户' User.objects.create(**form) return json_response(error=error) @@ -134,42 +136,84 @@ class SelfView(View): def login(request): form, error = JsonParser( Argument('username', help='请输入用户名'), - Argument('password', help='请输入密码') + Argument('password', help='请输入密码'), + Argument('type') ).parse(request.body) if error is None: - user = User.objects.filter(username=form.username).first() - if user: - if not user.is_active: - return json_response(error="账户已被禁用") - if user.verify_password(form.password): - cache.delete(form.username) - x_real_ip = request.headers.get('x-real-ip', '') - token_isvalid = user.access_token and len(user.access_token) == 32 and user.token_expired >= time.time() - user.access_token = user.access_token if token_isvalid else uuid.uuid4().hex - user.token_expired = time.time() + 8 * 60 * 60 - user.last_login = human_datetime() - user.last_ip = x_real_ip - user.save() - return json_response({ - 'access_token': user.access_token, - 'nickname': user.nickname, - 'is_supper': user.is_supper, - 'has_real_ip': True if x_real_ip else False, - 'permissions': [] if user.is_supper else user.page_perms - }) + user = User.objects.filter(username=form.username) + if form.type == 'ldap': + u = LDAP() + valid = u.valid_user(form.username, form.password) + if valid['status']: + user = user.filter(type='LDAP').first() + if user: + if not user.is_active: + return json_response(error="账户已被系统禁用") + if not user.role_id: + return json_response(error="LDAP用户角色未分配") - value = cache.get_or_set(form.username, 0, 86400) - if value >= 3: - if user and user.is_active: - user.is_active = False - user.save() - return json_response(error='账户已被禁用') - cache.set(form.username, value + 1, 86400) - return json_response(error="用户名或密码错误,连续多次错误账户将会被禁用") + x_real_ip = request.headers.get('x-real-ip', '') + ret = handle_user_info(user, form.username, x_real_ip) + return json_response(ret) + + x_real_ip = request.headers.get('x-real-ip', '') + form.access_token = uuid.uuid4().hex + form.nickname = form.username + form.token_expired = time.time() + 8 * 60 * 60 + form.last_login = human_datetime() + form.last_ip = x_real_ip + form.type = 'LDAP' + form.pop('password') + User.objects.create(**form) + return json_response({ + 'access_token': form.access_token, + 'nickname': form.username, + 'is_supper': False, + 'has_real_ip': True if x_real_ip else False, + 'permissions': [] + }) + return json_response(error=valid['info']) + else: + user = user.filter(type='系统用户').first() + if user and user.deleted_by is None: + if not user.is_active: + return json_response(error="账户已被系统禁用") + if user.verify_password(form.password): + cache.delete(form.username) + x_real_ip = request.headers.get('x-real-ip', '') + ret = handle_user_info(user, form.username, x_real_ip) + return json_response(ret) + + value = cache.get_or_set(form.username, 0, 86400) + if value >= 3: + if user and user.is_active: + user.is_active = False + user.save() + return json_response(error='账户已被系统禁用') + cache.set(form.username, value + 1, 86400) + return json_response(error="用户名或密码错误,连续多次错误账户将会被禁用") return json_response(error=error) +def handle_user_info(user, username, x_real_ip): + cache.delete(username) + token_isvalid = user.access_token and len(user.access_token) == 32 and user.token_expired >= time.time() + user.access_token = user.access_token if token_isvalid else uuid.uuid4().hex + user.token_expired = time.time() + 8 * 60 * 60 + user.last_login = human_datetime() + user.last_ip = x_real_ip + user.save() + return { + 'access_token': user.access_token, + 'nickname': user.nickname, + 'is_supper': user.is_supper, + 'has_real_ip': True if x_real_ip else False, + 'permissions': [] if user.is_supper else user.page_perms + } + + def logout(request): request.user.token_expired = 0 request.user.save() return json_response() + diff --git a/spug_api/apps/setting/urls.py b/spug_api/apps/setting/urls.py index 4cb4500..22bb779 100644 --- a/spug_api/apps/setting/urls.py +++ b/spug_api/apps/setting/urls.py @@ -1,10 +1,11 @@ # Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug # Copyright: (c) # Released under the MIT License. -from django.urls import path - +# from django.urls import path +from django.conf.urls import url from .views import * urlpatterns = [ - path('', SettingView.as_view()), + url(r'^ldap_test/$', ldap_test), + url('', SettingView.as_view()), ] diff --git a/spug_api/apps/setting/utils.py b/spug_api/apps/setting/utils.py index 8bcd5bf..821cbf7 100644 --- a/spug_api/apps/setting/utils.py +++ b/spug_api/apps/setting/utils.py @@ -6,7 +6,7 @@ from apps.setting.models import Setting class AppSetting: - keys = ('public_key', 'private_key', 'mail_service', 'api_key', 'spug_key') + keys = ('public_key', 'private_key', 'mail_service', 'api_key', 'spug_key', 'ldap_service') @classmethod @lru_cache(maxsize=64) diff --git a/spug_api/apps/setting/views.py b/spug_api/apps/setting/views.py index bd3bb72..0730424 100644 --- a/spug_api/apps/setting/views.py +++ b/spug_api/apps/setting/views.py @@ -5,6 +5,7 @@ from django.views.generic import View from libs import JsonParser, Argument, json_response from apps.setting.utils import AppSetting from apps.setting.models import Setting +import ldap class SettingView(View): @@ -20,3 +21,25 @@ class SettingView(View): for item in form.data: AppSetting.set(**item) return json_response(error=error) + + +def ldap_test(request): + form, error = JsonParser( + Argument('server'), + Argument('port', type=int), + Argument('admin_dn'), + Argument('password'), + ).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']) + return json_response(error=error) + + + + diff --git a/spug_api/libs/ldap.py b/spug_api/libs/ldap.py new file mode 100644 index 0000000..a641727 --- /dev/null +++ b/spug_api/libs/ldap.py @@ -0,0 +1,54 @@ +# Copyright: (c) OpenSpug Organization. https://github.com/openspug/spug +# Copyright: (c) +# Released under the MIT License. + +import ldap +from apps.setting.models import Setting +import json + + +class LDAP: + def __init__(self): + server_info = Setting.objects.exclude(key__in=('public_key', 'private_key')) \ + .filter(key='ldap_service').first() + ldap_info_dict = json.loads(server_info.value) + self.server = ldap_info_dict['server'] + self.port = ldap_info_dict['port'] + self.rules = ldap_info_dict['rules'] + self.admin_dn = ldap_info_dict['admin_dn'] + self.password = ldap_info_dict['password'] + self.base_dn = ldap_info_dict['base_dn'] + self.con = ldap.initialize("ldap://{0}:{1}".format(self.server, self.port), bytes_mode=False) + try: + self.con.simple_bind_s(self.admin_dn, self.password) + except Exception as error: + self.error = error + + def get_user_info(self, username): + try: + search_scope = ldap.SCOPE_SUBTREE + search_filter_name = self.rules + retrieve_attributes = None + search_filter = '(' + search_filter_name + "=" + username + ')' + ldap_result_id = self.con.search(self.base_dn, search_scope, search_filter, retrieve_attributes) + result_type, result_data = self.con.result(ldap_result_id, 0) + if result_type == ldap.RES_SEARCH_ENTRY: + return {'status': True, 'info': result_data[0][0]} + else: + return {'status': False, 'info': 'LDAP用户未找到'} + except Exception as error: + error = eval(str(error)) + return {'status': False, 'info': error['desc']} + + def valid_user(self, username, password): + user = self.get_user_info(username) + if user['status']: + try: + self.con.simple_bind_s(user['info'], password) + return {'status': True, 'info': ''} + except Exception as error: + error = eval(str(error)) + return {'status': False, 'info': error['desc']} + else: + return {'status': False, 'info': user['info']} + diff --git a/spug_api/requirements.txt b/spug_api/requirements.txt index 079e65f..1532c93 100644 --- a/spug_api/requirements.txt +++ b/spug_api/requirements.txt @@ -5,4 +5,5 @@ channels_redis==2.4.1 paramiko==2.6.0 django-redis==4.10.0 requests==2.22.0 -GitPython==3.0.8 \ No newline at end of file +GitPython==3.0.8 +python-ldap==3.2.0 diff --git a/spug_web/src/pages/login/index.js b/spug_web/src/pages/login/index.js index f4aee6c..0ed13ee 100644 --- a/spug_web/src/pages/login/index.js +++ b/spug_web/src/pages/login/index.js @@ -31,6 +31,7 @@ class LoginIndex extends React.Component { this.props.form.validateFields((err, formData) => { if (!err) { this.setState({loading: true}); + formData['type'] = this.state.loginType; http.post('/api/account/login/', formData) .then(data => { if (!data['has_real_ip']) { @@ -75,7 +76,7 @@ class LoginIndex extends React.Component {
this.setState({loginType: e})}> - +
diff --git a/spug_web/src/pages/system/account/Table.js b/spug_web/src/pages/system/account/Table.js index 11cf88a..2b5b24f 100644 --- a/spug_web/src/pages/system/account/Table.js +++ b/spug_web/src/pages/system/account/Table.js @@ -34,6 +34,9 @@ class ComTable extends React.Component { }, { title: '姓名', dataIndex: 'nickname', + }, { + title: '类型', + dataIndex: 'type', }, { title: '角色', dataIndex: 'role_name' diff --git a/spug_web/src/pages/system/setting/BasicSetting.js b/spug_web/src/pages/system/setting/BasicSetting.js index 7411d7f..bd4f178 100644 --- a/spug_web/src/pages/system/setting/BasicSetting.js +++ b/spug_web/src/pages/system/setting/BasicSetting.js @@ -5,12 +5,24 @@ */ import React from 'react'; import styles from './index.module.css'; +import { Form} from "antd"; +import { observer } from 'mobx-react' -export default function BasicSetting(props) { - return ( - -
基本设置
-
- ) +@observer +class BasicSetting extends React.Component { + constructor(props) { + super(props); + this.state = {} + } + + + render() { + return ( + +
基本设置
+
+ ) + } } +export default Form.create()(BasicSetting) diff --git a/spug_web/src/pages/system/setting/LDAPSetting.js b/spug_web/src/pages/system/setting/LDAPSetting.js new file mode 100644 index 0000000..42956bf --- /dev/null +++ b/spug_web/src/pages/system/setting/LDAPSetting.js @@ -0,0 +1,104 @@ +/** + * Copyright (c) OpenSpug Organization. https://github.com/openspug/spug + * Copyright (c) + * Released under the MIT License. + */ +import React from 'react'; +import styles from './index.module.css'; +import {Button, Form, Input, message} from "antd"; +import { http } from 'libs'; +import { observer } from 'mobx-react' +import store from './store'; +import lds from "lodash"; + + +@observer +class LDAPSetting extends React.Component { + constructor(props) { + super(props); + this.setting = JSON.parse(lds.get(store.settings, 'ldap_service.value', "{}")); + this.state = { + loading: false, + ldap_test_loading: false, + } + } + + handleSubmit = () => { + const formData = []; + this.props.form.validateFields((err, data) => { + if (!err) { + this.setState({loading: true}); + formData.push({key: 'ldap_service', value: JSON.stringify(data)}); + http.post('/api/setting/', {data: formData}) + .then(() => { + message.success('保存成功'); + store.fetchSettings() + }) + .finally(() => this.setState({loading: false})) + } + }) + }; + ldapTest = () => { + this.props.form.validateFields((error, data) => { + if (!error) { + this.setState({ldap_test_loading: true}); + http.post('/api/setting/ldap_test/', data).then(()=> { + message.success('LDAP服务连接成功') + }).finally(()=> this.setState({ldap_test_loading: false})) + } + }) + }; + + render() { + const {getFieldDecorator} = this.props.form; + return ( + +
LDAP设置
+ + + {getFieldDecorator('server', {initialValue: this.setting['server'], + rules: [{required: true, message: '请输入LDAP服务地址'}]})( + + )} + + + {getFieldDecorator('port', {initialValue: this.setting['port'], + rules: [{required: true, message: '请输入LDAP服务端口'}]})( + + )} + + + {getFieldDecorator('admin_dn', {initialValue: this.setting['admin_dn'], + rules: [{required: true, message: '请输入LDAP管理员DN'}]})( + + )} + + + {getFieldDecorator('password', {initialValue: this.setting['password'], + rules: [{required: true, message: '请输入LDAP管理员密码'}]})( + + )} + + + {getFieldDecorator('rules', {initialValue: this.setting['rules'], + rules: [{required: true, message: '请输入LDAP搜索规则'}]})( + + )} + + + {getFieldDecorator('base_dn', {initialValue: this.setting['base_dn'], + rules: [{required: true, message: '请输入LDAP基本DN'}]})( + + )} + + + + + + +
+ ) + } +} +export default Form.create()(LDAPSetting) diff --git a/spug_web/src/pages/system/setting/index.js b/spug_web/src/pages/system/setting/index.js index 2674d59..fda1cd2 100644 --- a/spug_web/src/pages/system/setting/index.js +++ b/spug_web/src/pages/system/setting/index.js @@ -8,6 +8,7 @@ import { Menu } from 'antd'; import {AuthDiv} from 'components'; import BasicSetting from './BasicSetting'; import AlarmSetting from './AlarmSetting'; +import LDAPSetting from './LDAPSetting'; import OpenService from './OpenService'; import styles from './index.module.css'; import store from './store'; @@ -36,12 +37,14 @@ class Index extends React.Component { style={{border: 'none'}} onSelect={({selectedKeys}) => this.setState({selectedKeys})}> 基本设置 + LDAP设置 报警服务设置 开放服务设置
{selectedKeys[0] === 'basic' && } + {selectedKeys[0] === 'ldap' && } {selectedKeys[0] === 'alarm' && } {selectedKeys[0] === 'service' && }
diff --git a/spug_web/src/pages/system/setting/store.js b/spug_web/src/pages/system/setting/store.js index 890f76f..c9e8f74 100644 --- a/spug_web/src/pages/system/setting/store.js +++ b/spug_web/src/pages/system/setting/store.js @@ -16,7 +16,7 @@ class Store { http.get('/api/setting/') .then(res => { for (let item of res) { - this.settings[item.key] = item + this.settings[item.key] = item; } }) .finally(() => this.isFetching = false)