pull/628/head
Mr Chen 6 months ago
parent 1c72e5b664
commit aad35bb73b
  1. 2
      plugins/data_query/static/html/index.html
  2. 51
      web/admin/__init__.py
  3. 39
      web/admin/dashboard/__init__.py
  4. 61
      web/admin/setting/__init__.py
  5. 9
      web/admin/setup/option.py
  6. 10
      web/admin/user_login_check.py
  7. 2
      web/config.py
  8. 16
      web/core/mw.py
  9. 4
      web/static/app/config.js
  10. 7
      web/templates/default/setting.html
  11. 30
      web/utils/config.py

@ -13,7 +13,7 @@
<div class="bgw mtb15 pd15 tab-view-box firewall-tab-view safe" style="overflow: hidden;">
<div class="mask_layer" style="display:none;">
<div class="prompt_description">当前未安装本地服务器,<a class="btlink install_server" href="/soft">点击安装</a></div>
<div class="prompt_description">当前未安装本地服务器,<a class="btlink install_server" href="/soft/index">点击安装</a></div>
</div>
<!-- mysql start -->

@ -10,17 +10,23 @@
import os
import sys
import json
import time
import uuid
import logging
from datetime import timedelta
from flask import Flask
from flask_socketio import SocketIO, emit, send
from flask import Flask, abort, request, current_app, session, url_for
from flask import Flask, abort, current_app, session, url_for
from flask import Blueprint, render_template
from flask import render_template_string
from flask import request
from flask import Response
from flask_migrate import Migrate
from flask_caching import Cache
from werkzeug.local import LocalProxy
@ -44,7 +50,9 @@ socketio = SocketIO(manage_session=False, async_mode='threading',
app = Flask(__name__, template_folder='templates/default')
# app.debug = True
# 缓存配置
cache = Cache(config={'CACHE_TYPE': 'simple'})
cache.init_app(app, config={'CACHE_TYPE': 'simple'})
# 静态文件配置
from whitenoise import WhiteNoise
@ -63,14 +71,11 @@ app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=31)
app.config['SQLALCHEMY_DATABASE_URI'] = mw.getSqitePrefix()+config.SQLITE_PATH+"?timeout=20" # 使用 SQLite 数据库
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# 缓存配置
cache = Cache(config={'CACHE_TYPE': 'simple'})
cache.init_app(app, config={'CACHE_TYPE': 'simple'})
# 初始化db
sys_db.init_app(app)
Migrate(app, sys_db)
# 检查数据库是否存在。如果没有就创建它。
setup_db_required = False
if not os.path.isfile(config.SQLITE_PATH):
@ -89,6 +94,11 @@ with app.app_context():
setup.init_admin_user()
setup.init_option()
app.config['BASIC_AUTH_OPEN'] = False
with app.app_context():
basic_auth = model.getOptionByJson('basic_auth', default={'open':False})
if basic_auth['open']:
app.config['BASIC_AUTH_OPEN'] = True
# 加载模块
@ -98,11 +108,36 @@ for module in get_submodules():
if app.blueprints.get(module.name) is None:
app.register_blueprint(module)
def sendAuthenticated():
# 发送http认证信息
request_host = mw.getHostAddr()
result = Response('', 401, {'WWW-Authenticate': 'Basic realm="%s"' % request_host.strip()})
if not 'login' in session and not 'admin_auth' in session:
session.clear()
return result
@app.before_request
def requestCheck():
# print("hh")
pass
# 自定义basic auth认证
if app.config['BASIC_AUTH_OPEN']:
basic_auth = model.getOptionByJson('basic_auth', default={'open':False})
if not basic_auth['open']:
return
auth = request.authorization
if request.path in ['/download', '/hook', '/down']:
return
if not auth:
return sendAuthenticated()
# print(auth.username.strip(),auth.password.strip())
salt = basic_auth['salt']
basic_user = mw.md5(auth.username.strip() + salt)
basic_pwd = mw.md5(auth.password.strip() + salt)
if basic_user != basic_auth['basic_user'] or basic_pwd != basic_auth['basic_pwd']:
return sendAuthenticated()
@app.after_request
def requestAfter(response):

@ -29,11 +29,41 @@ blueprint = Blueprint('dashboard', __name__, url_prefix='/', template_folder='..
def index():
return render_template('default/index.html')
# 安全路径
@blueprint.route('/<path>',endpoint='admin_safe_path',methods=['GET'])
def admin_safe_path(path):
db_path = model.getOption('admin_path')
if db_path == path:
return render_template('default/login.html')
unauthorized_status = model.getOption('unauthorized_status')
if unauthorized_status == '0':
return render_template('default/path.html')
return Response(status=int(unauthorized_status))
# ---------------------------------------------------------------------------------
# 定义登录入口相关方法
# ---------------------------------------------------------------------------------
# 登录页: 当设置了安全路径,本页失效。
@blueprint.route('/login')
def login():
signout = request.args.get('signout', '')
if signout == 'True':
session.clear()
session['login'] = False
session['overdue'] = 0
db_path = model.getOption('admin_path')
if db_path == '':
return render_template('default/login.html')
else:
unauthorized_status = model.getOption('unauthorized_status')
if unauthorized_status == '0':
return render_template('default/path.html')
return Response(status=int(unauthorized_status))
# 验证码
@blueprint.route('/code')
def code():
@ -47,15 +77,6 @@ def code():
img = Response(out.getvalue(), headers={'Content-Type': 'image/png'})
return make_response(img)
@blueprint.route('/login')
def login():
signout = request.args.get('signout', '')
if signout == 'True':
session.clear()
session['login'] = False
session['overdue'] = 0
return render_template('default/login.html')
# 检查是否登录
@blueprint.route('/check_login',methods=['GET','POST'])
def check_login():

@ -9,6 +9,8 @@
# ---------------------------------------------------------------------------------
import re
import json
import os
from flask import Blueprint, render_template
from flask import request
@ -17,6 +19,7 @@ from admin import model
from admin.user_login_check import panel_login_required
import core.mw as mw
import utils.config as utils_config
# 默认页面
blueprint = Blueprint('setting', __name__, url_prefix='/setting', template_folder='../../templates')
@ -25,8 +28,6 @@ blueprint = Blueprint('setting', __name__, url_prefix='/setting', template_folde
def index():
return render_template('default/setting.html')
@blueprint.route('/get_panel_list', endpoint='get_panel_list', methods=['POST'])
@panel_login_required
def get_panel_list():
@ -106,5 +107,61 @@ def set_admin_path():
# 设置BasicAuth认证
@blueprint.route('/set_basic_auth', endpoint='set_basic_auth', methods=['POST'])
@panel_login_required
def set_basic_auth():
basic_user = request.form.get('basic_user', '').strip()
basic_pwd = request.form.get('basic_pwd', '').strip()
basic_open = request.form.get('is_open', '').strip()
__file = mw.getCommonFile()
path = __file['basic_auth']
is_open = True
if basic_open == 'false':
is_open = False
if basic_open == 'false':
model.setOption('basic_auth', json.dumps({'open':False}))
mw.writeLog('面板设置', '设置BasicAuth状态为: %s' % is_open)
return mw.returnData(True, '删除BasicAuth成功!')
if basic_user == '' or basic_pwd == '':
return mw.returnData(False, '用户和密码不能为空!')
salt = mw.getRandomString(6)
data = {}
data['salt'] = salt
data['basic_user'] = mw.md5(basic_user + salt)
data['basic_pwd'] = mw.md5(basic_pwd + salt)
data['open'] = is_open
model.setOption('basic_auth', json.dumps(data))
mw.writeLog('面板设置', '设置BasicAuth状态为: %s' % is_open)
return mw.returnData(True, '设置成功!')
# 默认站点目录
@blueprint.route('/set_status_code', endpoint='set_status_code', methods=['POST'])
@panel_login_required
def set_status_code():
status_code = request.form.get('status_code', '').strip()
if re.match(r"^\d+$", status_code):
status_code = int(status_code)
if status_code != 0:
if status_code < 100 or status_code > 999:
return mw.returnData(False, '状态码范围错误!!')
else:
return mw.returnData(False, '状态码范围错误!')
info = utils_config.getUnauthStatus(code=str(status_code))
model.setOption('unauthorized_status', str(status_code))
mw.writeLog('面板设置', '将未授权响应状态码设置为:{0}:{1}'.format(status_code,info['text']))
return mw.returnData(True, '设置成功!')

@ -8,6 +8,8 @@
# Author: midoks <midoks@163.com>
# ---------------------------------------------------------------------------------
import json
from flask import request
from admin import model
@ -22,9 +24,16 @@ def init_option():
# 后台面板是否关闭
model.setOption('admin_close', 'no')
# 未认证状态码
model.setOption('unauthorized_status', '0')
# 调式模式,默认关闭
model.setOption('debug', 'close')
# basic auth 配置
model.setOption('basic_auth', json.dumps({'open':False}))

@ -8,18 +8,26 @@
# Author: midoks <midoks@163.com>
# ---------------------------------------------------------------------------------
from flask import render_template
from flask import Response
from functools import wraps
from admin import model
from admin import session
from admin.common import isLogined
def panel_login_required(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not isLogined():
return {'status':False, 'msg':'未登录/登录过期'}
unauthorized_status = model.getOption('unauthorized_status')
if unauthorized_status == '0':
return render_template('default/path.html')
return Response(status=int(unauthorized_status))
return func(*args, **kwargs)
return wrapper

@ -26,7 +26,7 @@ from version import APP_VERSION, APP_RELEASE, APP_REVISION, APP_SUFFIX
DEBUG = False
# 配置数据库连接池大小。将其设置为0将删除任何限制
CONFIG_DATABASE_CONNECTION_POOL_SIZE = 1
CONFIG_DATABASE_CONNECTION_POOL_SIZE = 20
# 允许溢出超过连接池大小的连接数
CONFIG_DATABASE_CONNECTION_MAX_OVERFLOW = 100

@ -137,12 +137,26 @@ def getDateFromNow(tf_format="%Y-%m-%d %H:%M:%S", time_zone="Asia/Shanghai"):
time.tzset()
return time.strftime(tf_format, time.localtime())
def getDataFromInt(val):
time_format = '%Y-%m-%d %H:%M:%S'
time_str = time.localtime(val)
return time.strftime(time_format, time_str)
def getCommonFile():
# 统一默认配置文件
base_dir = getPanelDir()+'/'
data = {
'debug' : base_dir+'data/debug.pl', # DEBUG文件
'close' : base_dir+'data/close.pl', # 识别关闭面板文件
'basic_auth' : base_dir+'data/basic_auth.json', # 面板Basic验证
'ipv6' : base_dir+'data/ipv6.pl', # ipv6识别文件
'bind_domain' : base_dir+'data/bind_domain.pl', # 面板域名绑定
'auth_secret': base_dir+'data/auth_secret.pl', # 二次验证密钥
'ssl': base_dir+'ssl/choose.pl', # ssl设置
}
return data
def toSize(size, middle='') -> str:
"""
字节单位转换

@ -1152,7 +1152,7 @@ function setStatusCode(o){
yes:function(index){
var loadT = layer.msg("正在设置未认证时的响应状态", { icon: 16, time: 0, shade: [0.3, '#000'] });
var status_code = $('select[name="status_code"]').val();
$.post('/config/set_status_code', { status_code: status_code }, function (rdata) {
$.post('/setting/set_status_code', { status_code: status_code }, function (rdata) {
showMsg(rdata.msg, function(){
layer.close(index);
layer.close(loadT);
@ -1362,7 +1362,7 @@ function setBasicAuth(){
$('.save_auth_cfg').click(function(){
var basic_user = $('input[name="basic_user"]').val();
var basic_pwd = $('input[name="basic_pwd"]').val();
$.post('/config/set_basic_auth', {'basic_user':basic_user,'basic_pwd':basic_pwd},function(rdata){
$.post('/setting/set_basic_auth', {'basic_user':basic_user,'basic_pwd':basic_pwd},function(rdata){
showMsg(rdata.msg, function(){
window.location.reload();
} ,{icon:rdata.status?1:2}, 2000);

@ -121,7 +121,8 @@
<p class="mtb15">
<span class="set-tit text-right" title="BasicAuth认证" style="float: left;">BasicAuth认证</span>
<input class="btswitch btswitch-ios" id="cfg_basic_auth" type="checkbox" {{data['basic_auth']}}/>
<input class="btswitch btswitch-ios" id="cfg_basic_auth" type="checkbox"
{% if data['basic_auth']['open'] %}checked{% endif %}/>
<label class="btswitch-btn ml5" for="cfg_basic_auth" style="float: left;margin-top:4px;" onclick="setBasicAuth()"></label>
<span class="set-info c7">为面板增加一道基于BasicAuth的认证服务,有效防止面板被扫描</span>
</p>
@ -136,8 +137,8 @@
<p class="mtb15">
<span class="set-tit text-right" title="未认证响应状态">未认证响应状态</span>
<input name="status_code" class="inputtxt bt-input-text disable" type="text" value="{{data['status_code_msg']}}" disabled>
<button type="button" class="btn btn-success btn-sm ml5" data-code="{{data['status_code']}}" onclick="setStatusCode(this)">设置</button>
<input name="status_code" class="inputtxt bt-input-text disable" type="text" value="{{data['unauthorized_status']['text']}}" disabled>
<button type="button" class="btn btn-success btn-sm ml5" data-code="{{data['unauthorized_status']['code']}}" onclick="setStatusCode(this)">设置</button>
<span class="set-info c7">用于在未登录且未正确输入安全入口时的响应,可用于隐藏面板特征</span>
</p>

@ -12,6 +12,31 @@ from admin import model
import core.mw as mw
def getUnauthStatus(
code: str | None = '0'
):
code = str(code)
data = {}
data['code'] = code
if code == '0':
data['text'] = "默认-安全入口错误提示"
elif code == '400':
data['text'] = "400-客户端请求错误"
elif code == '401':
data['text'] = "401-未授权访问"
elif code == '403':
data['text'] = "403-拒绝访问"
elif code == '404':
data['text'] = "404-页面不存在"
elif code == '408':
data['text'] = "408-客户端超时"
elif code == '416':
data['text'] = "416-无效的请求"
else:
data['code'] = '0'
data['text'] = "默认-安全入口错误提示"
return data
def getGlobalVar():
'''
获取全局变量
@ -26,6 +51,11 @@ def getGlobalVar():
data['site_count'] = model.getSitesCount()
data['port'] = mw.getHostPort()
# 获取未认证状态信息
unauthorized_status = model.getOption('unauthorized_status', default='0')
data['unauthorized_status'] = getUnauthStatus(code=unauthorized_status)
data['basic_auth'] = model.getOptionByJson('basic_auth', default={'open':False})
# 服务器时间
sformat = 'date +"%Y-%m-%d %H:%M:%S %Z %z"'
data['systemdate'] = mw.execShell(sformat)[0].strip()

Loading…
Cancel
Save