From cd5e6e1ec76a89fb504f20ad7657a42cbc6a99db Mon Sep 17 00:00:00 2001 From: midoks Date: Sat, 5 Nov 2022 22:41:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=B4=E6=97=B6=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E6=8E=88=E6=9D=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- class/core/config_api.py | 82 ++++++++++++- class/core/db.py | 23 ++++ class/core/mw.py | 5 +- data/sql/default.sql | 1 + route/__init__.py | 77 +++++++++++- route/static/app/config.js | 175 ++++++++++++++++++++++++++++ route/static/app/public.js | 23 ++-- route/static/css/site.css | 10 +- route/templates/default/config.html | 31 +++-- scripts/update/debian.sh | 2 + scripts/update/ubuntu.sh | 2 + 11 files changed, 403 insertions(+), 28 deletions(-) diff --git a/class/core/config_api.py b/class/core/config_api.py index decd635cf..6e7b3f019 100755 --- a/class/core/config_api.py +++ b/class/core/config_api.py @@ -20,6 +20,9 @@ class config_api: def __init__(self): pass + def getVersion(self): + return self.__version + ##### ----- start ----- ### # 取面板列表 @@ -306,8 +309,83 @@ class config_api: return True return False - def getVersion(self): - return self.__version + # 获取临时登录列表 + def getTempLoginApi(self): + if 'tmp_login_expire' in session: + return mw.returnJson(False, '没有权限') + limit = request.form.get('limit', '10').strip() + p = request.form.get('p', '1').strip() + tojs = request.form.get('tojs', '').strip() + + tempLoginM = mw.M('temp_login') + tempLoginM.where('state=? and expire?', (0, s_time)).delete() + token = mw.getRandomString(48) + salt = mw.getRandomString(12) + + pdata = { + 'token': mw.md5(token + salt), + 'salt': salt, + 'state': 0, + 'login_time': 0, + 'login_addr': '', + 'expire': s_time + 3600, + 'addtime': s_time + } + + if not mw.M('temp_login').count(): + pdata['id'] = 101 + + if mw.M('temp_login').insert(pdata): + mw.writeLog('面板设置', '生成临时连接,过期时间:{}'.format( + mw.formatDate(times=pdata['expire']))) + return mw.getJson({'status': True, 'msg': "临时连接已生成", 'token': token, 'expire': pdata['expire']}) + return mw.returnJson(False, '连接生成失败') + + def getTempLoginLogsApi(self): + if 'tmp_login_expire' in session: + return mw.returnJson(False, '没有权限') + + logs_id = request.form.get('id', '').strip() + logs_id = int(logs_id) + data = mw.M('logs').where( + 'uid=?', (logs_id,)).order('id desc').field( + 'id,type,uid,log,addtime').select() + return mw.returnJson(False, 'ok', data) def get(self): diff --git a/class/core/db.py b/class/core/db.py index 901d0c8cd..32c5be64a 100755 --- a/class/core/db.py +++ b/class/core/db.py @@ -213,6 +213,29 @@ class Sql(): except Exception as ex: return "error: " + str(ex) + # 插入数据 + def insert(self, pdata): + if not pdata: + return False + keys, param = self.__format_pdata(pdata) + return self.add(keys, param) + + # 更新数据 + def update(self, pdata): + if not pdata: + return False + keys, param = self.__format_pdata(pdata) + return self.save(keys, param) + + # 构造数据 + def __format_pdata(self, pdata): + keys = pdata.keys() + keys_str = ','.join(keys) + param = [] + for k in keys: + param.append(pdata[k]) + return keys_str, tuple(param) + def checkInput(self, data): if not data: return data diff --git a/class/core/mw.py b/class/core/mw.py index ffdd60ffb..378b9dc15 100755 --- a/class/core/mw.py +++ b/class/core/mw.py @@ -392,10 +392,13 @@ def writeLog(stype, msg, args=()): import time import db import json + from flask import session + uid = 1 + if 'uid' in session: + uid = session['uid'] sql = db.Sql() mdate = time.strftime('%Y-%m-%d %X', time.localtime()) wmsg = getInfo(msg, args) - uid = 1 data = (stype, wmsg, uid, mdate) result = sql.table('logs').add('type,log,uid,addtime', data) return True diff --git a/data/sql/default.sql b/data/sql/default.sql index c4af586df..8cdd9bf63 100755 --- a/data/sql/default.sql +++ b/data/sql/default.sql @@ -59,6 +59,7 @@ CREATE TABLE IF NOT EXISTS `logs` ( `uid` TEXT, `addtime` TEXT ); +ALTER TABLE `logs` ADD COLUMN `uid` INTEGER DEFAULT '1'; CREATE TABLE IF NOT EXISTS `sites` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/route/__init__.py b/route/__init__.py index ced9bbcd4..b6d27fcb7 100755 --- a/route/__init__.py +++ b/route/__init__.py @@ -76,7 +76,7 @@ socketio.init_app(app) # http_server.serve_forever() # debug macosx dev -if mw.isAppleSystem(): +if mw.isDebugMode(): app.debug = True app.config.version = app.config.version + str(time.time()) @@ -84,6 +84,33 @@ import common common.init() +# ---------- error function start ----------------- +def getErrorNum(key, limit=None): + key = mw.md5(key) + num = cache.get(key) + if not num: + num = 0 + if not limit: + return num + if limit > num: + return True + return False + + +def setErrorNum(key, empty=False, expire=3600): + key = mw.md5(key) + num = cache.get(key) + if not num: + num = 0 + else: + if empty: + cache.delete(key) + return True + cache.set(key, num + 1, expire) + return True +# ---------- error function end ----------------- + + def funConvert(fun): block = fun.split('_') func = block[0] @@ -98,6 +125,7 @@ def isLogined(): if 'login' in session and 'username' in session and session['login'] == True: userInfo = mw.M('users').where( "id=?", (1,)).field('id,username,password').find() + # print(userInfo) if userInfo['username'] != session['username']: return False @@ -291,6 +319,46 @@ def admin_safe_path(path, req, data, pageFile): return render_template(req + '.html', data=data) +def login_temp_user(token): + print(token) + if len(token) != 48: + return '错误的参数!' + + skey = mw.getClientIp() + '_temp_login' + if not getErrorNum(skey, 10): + return '连续10次验证失败,禁止1小时' + + stime = int(time.time()) + data = mw.M('temp_login').where('state=? and expire>?', + (0, stime)).field('id,token,salt,expire').find() + if not data: + setErrorNum(skey) + return '验证失败!' + + r_token = mw.md5(token + data['salt']) + if r_token != data['token']: + setErrorNum(skey) + return '验证失败!' + + userInfo = mw.M('users').where( + "id=?", (1,)).field('id,username').find() + session['login'] = True + session['username'] = userInfo['username'] + session['tmp_login'] = True + session['tmp_login_id'] = str(data['id']) + session['tmp_login_expire'] = time.time() + 3600 + session['uid'] = data['id'] + + login_addr = mw.getClientIp() + ":" + str(request.environ.get('REMOTE_PORT')) + mw.writeLog('用户登录', "登录成功,帐号:{1},登录IP:{2}", + (userInfo['username'], login_addr)) + mw.M('temp_login').where('id=?', (data['id'],)).update( + {"login_time": stime, 'state': 1, 'login_addr': login_addr}) + + print(session) + return redirect('/') + + @app.route('//', methods=['POST', 'GET']) @app.route('//', methods=['POST', 'GET']) @app.route('/', methods=['POST', 'GET']) @@ -312,11 +380,18 @@ def index(reqClass=None, reqAction=None, reqData=None): pageFile = ('config', 'control', 'crontab', 'files', 'firewall', 'index', 'plugins', 'login', 'system', 'site', 'ssl', 'task', 'soft') + if reqClass == 'login': + token = request.args.get('tmp_token', '').strip() + print(token) + if token != '': + return login_temp_user(token) + # 设置了安全路径 ainfo = get_admin_safe() # 登录页 if reqClass == 'login': + dologin = request.args.get('dologin', '') if dologin == 'True': session.clear() diff --git a/route/static/app/config.js b/route/static/app/config.js index b9345779e..121e470fa 100755 --- a/route/static/app/config.js +++ b/route/static/app/config.js @@ -338,3 +338,178 @@ function savePanelSSL(){ },'json'); } + +function removeTempAccess(id){ + $.post('/config/remove_temp_login', {id:id}, function(rdata){ + showMsg(rdata.msg, function(){ + setTempAccessReq(); + },{ icon: rdata.status ? 1 : 2 }, 2000); + },'json'); +} + +function getTempAccessLogsReq(id){ + $.post('/config/get_temp_login_logs', {id:id}, function(rdata){ + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + + tbody += ''; + tbody += '' + (rdata.data[i]['type']) +''; + tbody += '' + rdata.data[i]['addtime'] +''; + tbody += ''+ rdata.data[i]['log'] +''; + tbody += ''; + } + $('#logs_list').html(tbody); + + },'json'); +} + +function getTempAccessLogs(id){ + + layer.open({ + area: ['700px', '250px'], + title: '临时授权管理', + closeBtn:1, + shift: 0, + type: 1, + content: "
\ + \ +
\ + \ + \ + \ + \ + \ +
操作类型操作时间日志
\ +
\ +
", + success:function(){ + getTempAccessLogsReq(id); + $('.refresh_log').click(function(){ + getTempAccessLogsReq(id); + }); + } + }); +} + +function setTempAccessReq(page){ + if (typeof(page) == 'undefined'){ + page = 1; + } + + $.post('/config/get_temp_login', {page:page}, function(rdata){ + if ( typeof(rdata.status) !='undefined' && !rdata.status){ + showMsg(rdata.msg,function(){ + layer.closeAll(); + },{icon:2}, 2000); + return; + } + + var tbody = ''; + for (var i = 0; i < rdata.data.length; i++) { + + tbody += ''; + tbody += '' + (rdata.data[i]['login_addr']||'未登陆') +''; + + tbody += ''; + switch (parseInt(rdata.data[i]['state'])) { + case 0: + tbody += '待使用'; + break; + case 1: + tbody += '已使用'; + break; + case -1: + tbody += '已过期'; + break; + } + tbody += ''; + + tbody += '' + (getLocalTime(rdata.data[i]['login_time'])||'未登陆') +''; + tbody += '' + getLocalTime(rdata.data[i]['expire']) +''; + + tbody += ''; + + if (rdata.data[i]['state'] == '1' ){ + tbody += '操作日志'; + } else{ + tbody += '删除'; + } + + tbody += ''; + + tbody += ''; + } + + $('#temp_login_view_tbody').html(tbody); + $('.temp_login_view_page').html(rdata.page); + },'json'); +} + +function setTempAccess(){ + layer.open({ + area: ['700px', '250px'], + title: '临时授权管理', + closeBtn:1, + shift: 0, + type: 1, + content: "
\ + \ +
\ + \ + \ + \ + \ + \ +
登录IP状态登录时间过期时间操作
\ + \ +
\ +
", + success:function(){ + setTempAccessReq(); + + $('.create_temp_login').click(function(){ + layer.confirm('注意1:滥用临时授权可能导致安全风险。
注意2:请勿在公共场合发布临时授权连接

即将创建临时授权连接,继续吗?', + { + title:'风险提示', + closeBtn:1, + icon:13, + }, function(create_temp_login_layer) { + $.post('/config/set_temp_login', {}, function(rdata){ + layer.close(create_temp_login_layer); + setTempAccessReq(); + layer.open({ + area: '570px', + title: '创建临时授权', + shift: 0, + type: 1, + content: "
\ +
\ + 临时授权地址\ +
\ + \ +
\ +
\ +
\ +
    \ +
  • 临时授权生成后1小时内使用有效,为一次性授权,使用后立即失效
  • \ +
  • 使用临时授权登录面板后1小时内拥有面板所有权限,请勿在公共场合发布临时授权连接
  • \ +
  • 授权连接信息仅在此处显示一次,若在使用前忘记,请重新生成
  • \ +
\ +
", + success:function(){ + var temp_link = "".concat(location.origin, "/login?tmp_token=").concat(rdata.token); + $('#temp_link').val(temp_link); + + copyText(temp_link); + $('.btn-copy-temp-link').click(function(){ + copyText(temp_link); + }); + } + }); + },'json'); + }); + }); + } + }); +} + diff --git a/route/static/app/public.js b/route/static/app/public.js index 02cc06a45..23ca5ae39 100755 --- a/route/static/app/public.js +++ b/route/static/app/public.js @@ -616,19 +616,18 @@ function divcenter() { $(".layui-layer").css("top", e + "px") } -function btcopy(password) { - $("#bt_copys").attr('data-clipboard-text',password); - $("#bt_copys").click(); -} - -var clipboard = new ClipboardJS('#bt_copys'); -clipboard.on('success', function (e) { - layer.msg('复制成功!',{icon:1}); -}); +function copyText(value) { + var clipboard = new ClipboardJS('#bt_copys'); + clipboard.on('success', function (e) { + layer.msg('复制成功',{icon:1,time:2000}); + }); -clipboard.on('error', function (e) { - layer.msg('复制失败,浏览器不兼容!',{icon:2}); -}); + clipboard.on('error', function (e) { + layer.msg('复制失败,浏览器不兼容!',{icon:2,time:2000}); + }); + $("#bt_copys").attr('data-clipboard-text',value); + $("#bt_copys").click(); +} function isChineseChar(b) { var a = /[\u4E00-\u9FA5\uF900-\uFA2D]/; diff --git a/route/static/css/site.css b/route/static/css/site.css index 1d4c6c4f3..a785048a6 100755 --- a/route/static/css/site.css +++ b/route/static/css/site.css @@ -2220,26 +2220,26 @@ html .menu .menu_exit:hover { margin-left: -40px; vertical-align: 0; position: relative; - z-index: 10 + z-index: 10; } .setting-con p .set-info { - margin-left: 20px + margin-left: 10px; } .set-submit { - margin: 20px 0 10px 100px + margin: 20px 0 10px 100px; } .changepath { - height: 500px + height: 500px; } .changepath .path-top { height: 50px; line-height: 50px; padding-left: 10px; - border-bottom: #aaa 1px solid + border-bottom: #aaa 1px solid; } .changepath .path-top .btn { diff --git a/route/templates/default/config.html b/route/templates/default/config.html index e6b068a45..987442437 100755 --- a/route/templates/default/config.html +++ b/route/templates/default/config.html @@ -50,9 +50,7 @@

设置

-

- - 为了提高安全,请修改别名、默认端口、面板用户和密码!

+

为了提高安全,修改面板密码!

@@ -67,7 +65,8 @@

安全入口 - 修改 + + 面板管理入口,设置后只能通过指定安全入口登录面板,如: /abc

@@ -99,19 +98,37 @@

服务器时间 - 同步 + + + 同步当前服务器时间

面板用户 - 修改 + + + 设置面板账号

面板密码 - 修改 + + + 设置面板密码

保存
+ +
+

安全

+
+ +
+

+ 临时访问授权 + + 为非管理员临时提供面板访问权限 +

+
diff --git a/scripts/update/debian.sh b/scripts/update/debian.sh index d6a731d3d..aca647fba 100644 --- a/scripts/update/debian.sh +++ b/scripts/update/debian.sh @@ -4,6 +4,8 @@ export PATH export LANG=en_US.UTF-8 export DEBIAN_FRONTEND=noninteractive +localedef -v -c -i en_US -f UTF-8 en_US.UTF-8 + if grep -Eq "Ubuntu" /etc/*-release; then sudo ln -sf /bin/bash /bin/sh #sudo dpkg-reconfigure dash diff --git a/scripts/update/ubuntu.sh b/scripts/update/ubuntu.sh index e794ff810..27f4c899d 100644 --- a/scripts/update/ubuntu.sh +++ b/scripts/update/ubuntu.sh @@ -4,6 +4,8 @@ export PATH export LANG=en_US.UTF-8 export DEBIAN_FRONTEND=noninteractive +localedef -v -c -i en_US -f UTF-8 en_US.UTF-8 + if grep -Eq "Ubuntu" /etc/*-release; then sudo ln -sf /bin/bash /bin/sh #sudo dpkg-reconfigure dash