diff --git a/plugins/data_query/static/html/index.html b/plugins/data_query/static/html/index.html
index bd3bf3ce2..c6bcd3ab0 100644
--- a/plugins/data_query/static/html/index.html
+++ b/plugins/data_query/static/html/index.html
@@ -13,7 +13,7 @@
diff --git a/web/admin/__init__.py b/web/admin/__init__.py
index f4b8c9257..d8f3ff159 100644
--- a/web/admin/__init__.py
+++ b/web/admin/__init__.py
@@ -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):
diff --git a/web/admin/dashboard/__init__.py b/web/admin/dashboard/__init__.py
index f93a618a7..dc66c3df6 100644
--- a/web/admin/dashboard/__init__.py
+++ b/web/admin/dashboard/__init__.py
@@ -29,11 +29,41 @@ blueprint = Blueprint('dashboard', __name__, url_prefix='/', template_folder='..
def index():
return render_template('default/index.html')
+# 安全路径
+@blueprint.route('/
',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():
diff --git a/web/admin/setting/__init__.py b/web/admin/setting/__init__.py
index a7fece270..4b53d8604 100644
--- a/web/admin/setting/__init__.py
+++ b/web/admin/setting/__init__.py
@@ -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, '设置成功!')
+
+
+
+
\ No newline at end of file
diff --git a/web/admin/setup/option.py b/web/admin/setup/option.py
index 4e26bd42f..10d739875 100644
--- a/web/admin/setup/option.py
+++ b/web/admin/setup/option.py
@@ -8,6 +8,8 @@
# Author: midoks
# ---------------------------------------------------------------------------------
+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}))
+
diff --git a/web/admin/user_login_check.py b/web/admin/user_login_check.py
index 5cc84df46..63fbb096d 100644
--- a/web/admin/user_login_check.py
+++ b/web/admin/user_login_check.py
@@ -8,18 +8,26 @@
# Author: midoks
# ---------------------------------------------------------------------------------
+
+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
\ No newline at end of file
diff --git a/web/config.py b/web/config.py
index 1043b7102..1b84db8df 100644
--- a/web/config.py
+++ b/web/config.py
@@ -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
diff --git a/web/core/mw.py b/web/core/mw.py
index c657071b1..7bc3e0db4 100644
--- a/web/core/mw.py
+++ b/web/core/mw.py
@@ -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:
"""
字节单位转换
diff --git a/web/static/app/config.js b/web/static/app/config.js
index 7c1808558..989101bea 100755
--- a/web/static/app/config.js
+++ b/web/static/app/config.js
@@ -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);
diff --git a/web/templates/default/setting.html b/web/templates/default/setting.html
index 655630e3c..f99eca262 100755
--- a/web/templates/default/setting.html
+++ b/web/templates/default/setting.html
@@ -121,7 +121,8 @@
BasicAuth认证
-
+
为面板增加一道基于BasicAuth的认证服务,有效防止面板被扫描
@@ -136,8 +137,8 @@
未认证响应状态
-
-
+
+
用于在未登录且未正确输入安全入口时的响应,可用于隐藏面板特征
diff --git a/web/utils/config.py b/web/utils/config.py
index 766da0e21..91bed8319 100644
--- a/web/utils/config.py
+++ b/web/utils/config.py
@@ -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()