添加临时访问授权功能

pull/250/head
midoks 3 years ago
parent 4fad0b9270
commit cd5e6e1ec7
  1. 82
      class/core/config_api.py
  2. 23
      class/core/db.py
  3. 5
      class/core/mw.py
  4. 1
      data/sql/default.sql
  5. 77
      route/__init__.py
  6. 175
      route/static/app/config.js
  7. 21
      route/static/app/public.js
  8. 10
      route/static/css/site.css
  9. 31
      route/templates/default/config.html
  10. 2
      scripts/update/debian.sh
  11. 2
      scripts/update/ubuntu.sh

@ -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, int(time.time()))).setField('state', -1)
start = (int(p) - 1) * (int(limit))
vlist = tempLoginM.limit(str(start) + ',' + str(limit)).order('id desc').field(
'id,addtime,expire,login_time,login_addr,state').select()
data = {}
data['data'] = vlist
count = tempLoginM.count()
page_args = {}
page_args['count'] = count
page_args['tojs'] = 'get_temp_login'
page_args['p'] = p
page_args['row'] = limit
data['page'] = mw.getPage(page_args)
return mw.getJson(data)
# 删除临时登录
def removeTempLoginApi(self):
if 'tmp_login_expire' in session:
return mw.returnJson(False, '没有权限')
sid = request.form.get('id', '10').strip()
if mw.M('temp_login').where('id=?', (sid,)).delete():
mw.writeLog('面板设置', '删除临时登录连接')
return mw.returnJson(True, '删除成功')
return mw.returnJson(False, '删除失败')
def setTempLoginApi(self):
if 'tmp_login_expire' in session:
return mw.returnJson(False, '没有权限')
s_time = int(time.time())
mw.M('temp_login').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):

@ -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

@ -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

@ -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,

@ -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('/<reqClass>/<reqAction>', methods=['POST', 'GET'])
@app.route('/<reqClass>/', methods=['POST', 'GET'])
@app.route('/<reqClass>', 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()

@ -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 += '<tr>';
tbody += '<td>' + (rdata.data[i]['type']) +'</td>';
tbody += '<td>' + rdata.data[i]['addtime'] +'</td>';
tbody += '<td>'+ rdata.data[i]['log'] +'</td>';
tbody += '</tr>';
}
$('#logs_list').html(tbody);
},'json');
}
function getTempAccessLogs(id){
layer.open({
area: ['700px', '250px'],
title: '临时授权管理',
closeBtn:1,
shift: 0,
type: 1,
content: "<div class=\"pd20\">\
<button class=\"btn btn-success btn-sm refresh_log\">刷新日志</button>\
<div class=\"divtable mt10\">\
<table class=\"table table-hover\">\
<thead>\
<tr><th>操作类型</th><th></th><th></th></tr>\
</thead>\
<tbody id=\"logs_list\"></tbody>\
</table>\
</div>\
</div>",
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 += '<tr>';
tbody += '<td>' + (rdata.data[i]['login_addr']||'未登陆') +'</td>';
tbody += '<td>';
switch (parseInt(rdata.data[i]['state'])) {
case 0:
tbody += '<a style="color:green;">待使用</a>';
break;
case 1:
tbody += '<a style="color:brown;">已使用</a>';
break;
case -1:
tbody += '<a>已过期</a>';
break;
}
tbody += '</td>';
tbody += '<td>' + (getLocalTime(rdata.data[i]['login_time'])||'未登陆') +'</td>';
tbody += '<td>' + getLocalTime(rdata.data[i]['expire']) +'</td>';
tbody += '<td>';
if (rdata.data[i]['state'] == '1' ){
tbody += '<a class="btlink" onclick="getTempAccessLogs(\''+rdata.data[i]['id']+'\')">操作日志</a>';
} else{
tbody += '<a class="btlink" onclick="removeTempAccess(\''+rdata.data[i]['id']+'\')">删除</a>';
}
tbody += '</td>';
tbody += '</tr>';
}
$('#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: "<div class=\"login_view_table pd20\">\
<button class=\"btn btn-success btn-sm create_temp_login\" >临时访问授权</button>\
<div class=\"divtable mt10\">\
<table class=\"table table-hover\">\
<thead>\
<tr><th>登录IP</th><th></th><th></th><th></th><th style=\"text-align:right;\"></th></tr>\
</thead>\
<tbody id=\"temp_login_view_tbody\"></tbody>\
</table>\
<div class=\"temp_login_view_page page\"></div>\
</div>\
</div>",
success:function(){
setTempAccessReq();
$('.create_temp_login').click(function(){
layer.confirm('<span style="color:red">注意1:滥用临时授权可能导致安全风险。</br>注意2:请勿在公共场合发布临时授权连接</span></br>即将创建临时授权连接,继续吗?',
{
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: "<div class=\"bt-form create_temp_view pd15\">\
<div class=\"line\">\
<span class=\"tname\">临时授权地址</span>\
<div>\
<textarea id=\"temp_link\" class=\"bt-input-text mr20\" style=\"margin: 0px;width: 500px;height: 50px;line-height: 19px;\"></textarea>\
</div>\
</div>\
<div class=\"line\"><button type=\"submit\" class=\"btn btn-success btn-sm btn-copy-temp-link\" data-clipboard-text=\"\">复制地址</button></div>\
<ul class=\"help-info-text c7 ptb15\">\
<li>临时授权生成后1小时内使用有效为一次性授权使用后立即失效</li>\
<li>使用临时授权登录面板后1小时内拥有面板所有权限请勿在公共场合发布临时授权连接</li>\
<li>授权连接信息仅在此处显示一次若在使用前忘记请重新生成</li>\
</ul>\
</div>",
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');
});
});
}
});
}

@ -616,20 +616,19 @@ function divcenter() {
$(".layui-layer").css("top", e + "px")
}
function btcopy(password) {
$("#bt_copys").attr('data-clipboard-text',password);
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,time:2000});
});
$("#bt_copys").attr('data-clipboard-text',value);
$("#bt_copys").click();
}
var clipboard = new ClipboardJS('#bt_copys');
clipboard.on('success', function (e) {
layer.msg('复制成功!',{icon:1});
});
clipboard.on('error', function (e) {
layer.msg('复制失败,浏览器不兼容!',{icon:2});
});
function isChineseChar(b) {
var a = /[\u4E00-\u9FA5\uF900-\uFA2D]/;
return a.test(b)

@ -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 {

@ -50,9 +50,7 @@
<h3 class="f16">设置</h3>
</div>
<div class="info-title-tips" style="margin: 20px 30px 0px;">
<p>
<span class="glyphicon glyphicon-alert" style="color: #f39c12; margin-right: 10px;">
</span>为了提高安全,请修改别名、默认端口、面板用户和密码!</p>
<p><span class="glyphicon glyphicon-alert" style="color: #f39c12; margin-right: 10px;"></span>为了提高安全,修改面板密码!</p>
</div>
<div class="setting-con pd15">
<form id="set_config">
@ -67,7 +65,8 @@
<p class="mtb15">
<span class="set-tit text-right">安全入口</span>
<input id="admin_path" name="admin_path" class="inputtxt bt-input-text disable" type="text" value="{{data['admin_path']}}"><span class="modify btn btn-xs btn-success" onclick="modifyAuthPath();">修改</span>
<input id="admin_path" name="admin_path" class="inputtxt bt-input-text disable" type="text" value="{{data['admin_path']}}">
<button type="button" class="btn btn-success btn-sm ml5" onclick="modifyAuthPath()">设置</button>
<span class="set-info c7">面板管理入口,设置后只能通过指定安全入口登录面板,如: /abc</span>
</p>
@ -99,19 +98,37 @@
<p class="mtb15">
<span class="set-tit text-right" title="服务器时间">服务器时间</span>
<input id="systemdate" name="systemdate" class="inputtxt bt-input-text disable" type="text" value="{{data['systemdate']}}"><span class="modify btn btn-xs btn-success" onclick="syncDate()">同步</span>
<input id="systemdate" name="systemdate" class="inputtxt bt-input-text disable" type="text" value="{{data['systemdate']}}">
<button type="button" class="btn btn-success btn-sm ml5" onclick="syncDate()">同步</button>
<span class="set-info c7">同步当前服务器时间</span>
</p>
<p class="mtb15">
<span class="set-tit text-right" title="面板用户">面板用户</span>
<input name="username_" class="inputtxt bt-input-text disable" type="text" value="{{data['username']}}" disabled><span class="modify btn btn-xs btn-success" onclick="setUserName()">修改</span>
<input name="username_" class="inputtxt bt-input-text disable" type="text" value="{{data['username']}}" disabled>
<button type="button" class="btn btn-success btn-sm ml5" onclick="setUserName()">设置</button>
<span class="set-info c7">设置面板账号</span>
</p>
<p class="mtb15">
<span class="set-tit text-right" title="面板密码">面板密码</span>
<input name="password_" class="inputtxt bt-input-text disable" type="text" value="******" disabled><span class="modify btn btn-xs btn-success" onclick="setPassword()">修改</span>
<input name="password_" class="inputtxt bt-input-text disable" type="text" value="******" disabled>
<button type="button" class="btn btn-success btn-sm ml5" onclick="setPassword()">设置</button>
<span class="set-info c7">设置面板密码</span>
</p>
</form>
<div class="bt-submit set-submit">保存</div>
</div>
<div class="title c6 plr15">
<h3 class="f16">安全</h3>
</div>
<div class="setting-con pd15">
<p class="mtb15">
<span class="set-tit text-right" title="临时访问授权">临时访问授权</span>
<button type="button" class="btn btn-success btn-sm ml5" onclick="setTempAccess()">临时访问授权管理</button>
<span class="set-info c7">为非管理员临时提供面板访问权限</span>
</p>
</div>
</div>
</div>
</div>

@ -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

@ -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

Loading…
Cancel
Save