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()