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="bgw mtb15 pd15 tab-view-box firewall-tab-view safe" style="overflow: hidden;">
<div class="mask_layer" style="display:none;"> <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> </div>
<!-- mysql start --> <!-- mysql start -->

@ -10,17 +10,23 @@
import os import os
import sys import sys
import json
import time import time
import uuid import uuid
import logging import logging
from datetime import timedelta from datetime import timedelta
from flask import Flask from flask import Flask
from flask_socketio import SocketIO, emit, send 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 Blueprint, render_template
from flask import render_template_string from flask import render_template_string
from flask import request
from flask import Response
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_caching import Cache from flask_caching import Cache
from werkzeug.local import LocalProxy 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 = 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 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_DATABASE_URI'] = mw.getSqitePrefix()+config.SQLITE_PATH+"?timeout=20" # 使用 SQLite 数据库
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# 缓存配置
cache = Cache(config={'CACHE_TYPE': 'simple'})
cache.init_app(app, config={'CACHE_TYPE': 'simple'})
# 初始化db # 初始化db
sys_db.init_app(app) sys_db.init_app(app)
Migrate(app, sys_db) Migrate(app, sys_db)
# 检查数据库是否存在。如果没有就创建它。 # 检查数据库是否存在。如果没有就创建它。
setup_db_required = False setup_db_required = False
if not os.path.isfile(config.SQLITE_PATH): if not os.path.isfile(config.SQLITE_PATH):
@ -89,6 +94,11 @@ with app.app_context():
setup.init_admin_user() setup.init_admin_user()
setup.init_option() 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: if app.blueprints.get(module.name) is None:
app.register_blueprint(module) 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 @app.before_request
def requestCheck(): def requestCheck():
# print("hh") # 自定义basic auth认证
pass 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 @app.after_request
def requestAfter(response): def requestAfter(response):

@ -29,11 +29,41 @@ blueprint = Blueprint('dashboard', __name__, url_prefix='/', template_folder='..
def index(): def index():
return render_template('default/index.html') 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') @blueprint.route('/code')
def code(): def code():
@ -47,15 +77,6 @@ def code():
img = Response(out.getvalue(), headers={'Content-Type': 'image/png'}) img = Response(out.getvalue(), headers={'Content-Type': 'image/png'})
return make_response(img) 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']) @blueprint.route('/check_login',methods=['GET','POST'])
def check_login(): def check_login():

@ -9,6 +9,8 @@
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
import re import re
import json
import os
from flask import Blueprint, render_template from flask import Blueprint, render_template
from flask import request from flask import request
@ -17,6 +19,7 @@ from admin import model
from admin.user_login_check import panel_login_required from admin.user_login_check import panel_login_required
import core.mw as mw import core.mw as mw
import utils.config as utils_config
# 默认页面 # 默认页面
blueprint = Blueprint('setting', __name__, url_prefix='/setting', template_folder='../../templates') 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(): def index():
return render_template('default/setting.html') return render_template('default/setting.html')
@blueprint.route('/get_panel_list', endpoint='get_panel_list', methods=['POST']) @blueprint.route('/get_panel_list', endpoint='get_panel_list', methods=['POST'])
@panel_login_required @panel_login_required
def get_panel_list(): 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> # Author: midoks <midoks@163.com>
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
import json
from flask import request from flask import request
from admin import model from admin import model
@ -22,9 +24,16 @@ def init_option():
# 后台面板是否关闭 # 后台面板是否关闭
model.setOption('admin_close', 'no') model.setOption('admin_close', 'no')
# 未认证状态码
model.setOption('unauthorized_status', '0')
# 调式模式,默认关闭 # 调式模式,默认关闭
model.setOption('debug', 'close') model.setOption('debug', 'close')
# basic auth 配置
model.setOption('basic_auth', json.dumps({'open':False}))

@ -8,18 +8,26 @@
# Author: midoks <midoks@163.com> # Author: midoks <midoks@163.com>
# --------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------
from flask import render_template
from flask import Response
from functools import wraps from functools import wraps
from admin import model from admin import model
from admin import session from admin import session
from admin.common import isLogined from admin.common import isLogined
def panel_login_required(func): def panel_login_required(func):
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
if not isLogined(): 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 func(*args, **kwargs)
return wrapper return wrapper

@ -26,7 +26,7 @@ from version import APP_VERSION, APP_RELEASE, APP_REVISION, APP_SUFFIX
DEBUG = False DEBUG = False
# 配置数据库连接池大小。将其设置为0将删除任何限制 # 配置数据库连接池大小。将其设置为0将删除任何限制
CONFIG_DATABASE_CONNECTION_POOL_SIZE = 1 CONFIG_DATABASE_CONNECTION_POOL_SIZE = 20
# 允许溢出超过连接池大小的连接数 # 允许溢出超过连接池大小的连接数
CONFIG_DATABASE_CONNECTION_MAX_OVERFLOW = 100 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() time.tzset()
return time.strftime(tf_format, time.localtime()) return time.strftime(tf_format, time.localtime())
def getDataFromInt(val): def getDataFromInt(val):
time_format = '%Y-%m-%d %H:%M:%S' time_format = '%Y-%m-%d %H:%M:%S'
time_str = time.localtime(val) time_str = time.localtime(val)
return time.strftime(time_format, time_str) 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: def toSize(size, middle='') -> str:
""" """
字节单位转换 字节单位转换

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

@ -121,7 +121,8 @@
<p class="mtb15"> <p class="mtb15">
<span class="set-tit text-right" title="BasicAuth认证" style="float: left;">BasicAuth认证</span> <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> <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> <span class="set-info c7">为面板增加一道基于BasicAuth的认证服务,有效防止面板被扫描</span>
</p> </p>
@ -136,8 +137,8 @@
<p class="mtb15"> <p class="mtb15">
<span class="set-tit text-right" title="未认证响应状态">未认证响应状态</span> <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> <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['status_code']}}" onclick="setStatusCode(this)">设置</button> <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> <span class="set-info c7">用于在未登录且未正确输入安全入口时的响应,可用于隐藏面板特征</span>
</p> </p>

@ -12,6 +12,31 @@ from admin import model
import core.mw as mw 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(): def getGlobalVar():
''' '''
获取全局变量 获取全局变量
@ -26,6 +51,11 @@ def getGlobalVar():
data['site_count'] = model.getSitesCount() data['site_count'] = model.getSitesCount()
data['port'] = mw.getHostPort() 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"' sformat = 'date +"%Y-%m-%d %H:%M:%S %Z %z"'
data['systemdate'] = mw.execShell(sformat)[0].strip() data['systemdate'] = mw.execShell(sformat)[0].strip()

Loading…
Cancel
Save