async def delete_catalog(request: sanic.Request): if isinstance(request.json, dict): catalog_id = request.json.get('catalog_id', None) if isinstance(catalog_id, int): query = select(HttpRuleCatalog).where( HttpRuleCatalog.catalog_id == catalog_id) catalog = (await request.ctx.db_session.execute(query)).scalar() if catalog is not None: # 删除分类和其下的所有规则 query = select(HttpRule).where( HttpRule.catalog_id == catalog_id) rules = (await request.ctx.db_session.execute(query)).scalars() for rule in rules: await request.ctx.db_session.delete(rule) await request.ctx.db_session.delete(catalog) await request.ctx.db_session.commit() return json(Response.success('删除成功')) else: return json(Response.failed('分类不存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def system_log_list(request: sanic.Request): if isinstance(request.json, dict): page = request.json.get('page', 0) page_size = request.json.get('page_size', 35) filter = request.json.get('filter', {}) if isinstance(page, int) and isinstance(page_size, int) and isinstance( filter, dict): system_log_scalars = (await request.ctx.db_session.execute( select(SystemLog).order_by(SystemLog.log_id.desc()).offset( page * page_size).limit(page_size))).scalars() count = (await request.ctx.db_session.execute( select(func.count('1')).select_from(SystemLog))).scalar() system_logs = [] for log in system_log_scalars: log.log_time = log.log_time.strftime('%Y-%m-%d %H:%M:%S') system_logs.append(log) paged = PagedResponse(payload=system_logs, total_page=ceil(count / page_size), curr_page=page) return json(Response.success('', paged)) else: return json(Response.invalid('无效参数')) else: return json(Response.invalid('无效参数'))
async def get_last_id(request: sanic.Request): query = select(DNSLog).order_by(DNSLog.log_id.desc()) log = (await request.ctx.db_session.execute(query)).scalar() if log: return json(Response.success('', log.log_id)) else: return json(Response.success('', 0))
async def login(request: sanic.Request): if isinstance(request.json, dict): username = request.json.get('username', None) password = request.json.get('password', None) user: typing.Union[None, User] = (await request.ctx.db_session.execute( select(User).where(User.username == username))).scalar() if isinstance(username, str) and isinstance(password, str) and \ (user is not None) and compare_digest(passwd_hash(password, system_config.PASSWORD_SALT), user.password): token = sign_token(user.user_id) if system_config.BEHIND_PROXY: client_ip = request.headers.get(constants.REAL_IP_HEADER) else: client_ip = request.ip log_content = f'Login with username [{username}] in [{client_ip} | {get_region_from_ip(client_ip)}]' await add_system_log(request.ctx.db_session, log_content, constants.LOG_TYPE_LOGIN) return json( Response.success('登录成功', { 'token': token, 'user_type': user.user_type })) else: return json(Response.failed('用户名或密码错误')) else: return json(Response.invalid('无效请求'))
async def change_password(request: sanic.Request): target_user = request.json.get('target_user', None) original_password = request.json.get('original_password', None) new_password = request.json.get('new_password', None) if target_user is None and isinstance(original_password, str) and isinstance(new_password, str) \ and compare_digest(request.ctx.user.password, passwd_hash(original_password, system_config.PASSWORD_SALT)): request.ctx.user.password = passwd_hash(new_password, system_config.PASSWORD_SALT) request.ctx.db_session.add(request.ctx.user) await request.ctx.db_session.commit() return json(Response.success('修改成功')) elif target_user is not None and request.ctx.user.user_type == constants.USER_TYPE_SUPER_ADMIN \ and isinstance(new_password, str) and isinstance(target_user, str): # 超级用户修改普通用户不需要原密码 user = (await request.ctx.db_session.execute( select(User).where(User.username == target_user))).scalar() if user is not None and user.user_type == constants.USER_TYPE_NORMAL: user.password = passwd_hash(new_password, system_config.PASSWORD_SALT) request.ctx.db_session.add(request.ctx.user) await request.ctx.db_session.commit() return json(Response.success('修改成功')) else: return json(Response.failed('用户不存在')) else: return json(Response.invalid('原密码错误'))
async def delete_all(request: sanic.Request): if isinstance(request.json, dict): delete_check = request.json.get('delete', False) if isinstance(delete_check, bool) and delete_check: await request.ctx.db_session.execute(delete(DNSLog)) await request.ctx.db_session.commit() return json(Response.success('清空成功')) else: return json(Response.invalid('无效请求')) else: return json(Response.invalid('无效请求'))
async def add(request: sanic.Request): file = request.files.get('file', None) if file: filename = secure_filename_with_directory(file.name) path = join(system_config.UPLOAD_PATH, filename) if not exists(path): asyncio.create_task(write_file(path, file.body)) return json(Response.success('上传成功')) else: return json(Response.failed('已存在同名文件')) else: return json(Response.invalid('无效参数'))
async def download(request: sanic.Request): if isinstance(request.json, dict): filename = request.json.get('filename', None) if isinstance(filename, str): filename = secure_filename(filename) path = join(system_config.TEMP_FILE_PATH, filename) if exists(path) and not isdir(path): return await sanic.response.file(path, filename=filename) else: return json(Response.failed('文件不存在'), 500) else: return json(Response.invalid('参数无效'), 400) else: return json(Response.invalid('参数无效'), 400)
async def delete_all(request: sanic.Request): if isinstance(request.json, dict): delete = request.json.get('delete', None) if isinstance(delete, bool) and delete: temp_files = listdir(system_config.TEMP_FILE_PATH) for filename in temp_files: path = join(system_config.TEMP_FILE_PATH, filename) unlink(path) return json(Response.success("清空成功")) else: return json(Response.invalid('无效请求')) else: return json(Response.invalid('无效请求'))
async def delete(request: sanic.Request): username = request.json.get('username', None) if isinstance(username, str): user = (await request.ctx.db_session.execute( select(User).where(User.username == username))).scalar() # 只能删除普通用户 if user is not None and user.user_type == constants.USER_TYPE_NORMAL: await request.ctx.db_session.delete(user) await request.ctx.db_session.commit() return json(Response.success('删除成功')) else: return json(Response.failed('用户不存在')) else: return json(Response.invalid('无效请求'))
async def delete(request: sanic.Request): if isinstance(request.json, dict): filename = request.json.get('filename', None) if isinstance(filename, str): filename = secure_filename_with_directory(filename) path = join(system_config.UPLOAD_PATH, filename) if exists(path) and not isdir(path): unlink(path) return json(Response.success('删除成功')) else: return json(Response.failed('删除的文件不存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def add_directory(request: sanic.Request): if isinstance(request.json, dict): directory_name = request.json.get('directory_name', None) if isinstance(directory_name, str) and len(secure_filename(directory_name)) > 0: directory_name = secure_filename(directory_name) full_path = join(system_config.UPLOAD_PATH, directory_name) if not exists(full_path): mkdir(full_path) return json(Response.success('创建成功')) else: return json(Response.failed('文件夹已存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def http_rule_list(request: sanic.Request): rules = (await request.ctx.db_session.execute(select(HttpRule))).scalars() rules = list(rules) # for rule in rules: # rule.create_time = rule.create_time.strftime('%Y-%m-%d %H:%M:%S') return json(Response.success('', rules))
async def delete_directory(request: sanic.Request): if isinstance(request.json, dict): directory_name = request.json.get('directory_name', None) if isinstance(directory_name, str) and len(secure_filename(directory_name)) > 0: directory_name = secure_filename(directory_name) full_path = join(system_config.UPLOAD_PATH, directory_name) if exists(full_path) and not isfile(full_path): shutil.rmtree(full_path) return json(Response.success('删除成功')) else: return json(Response.failed('文件夹不存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def dns_log_list(request: sanic.Request): if isinstance(request.json, dict): page = request.json.get('page', 0) page_size = request.json.get('page_size', 35) filter = request.json.get('filter', {}) if isinstance(page, int) and isinstance(page_size, int) and isinstance( filter, dict): query = select(DNSLog) count_query = select(func.count('1')).select_from(DNSLog) available_filter = { 'client_ip': DNSLog.client_ip.__eq__, 'domain': DNSLog.domain.__eq__, 'dns_type': DNSLog.dns_type.__eq__, 'time_before': DNSLog.log_time.__le__, 'time_after': DNSLog.log_time.__ge__ } for key in filter: if isinstance(filter[key], (str, int)) and key in available_filter: query = query.where(available_filter[key](filter[key])) count_query = count_query.where(available_filter[key]( filter[key])) dns_log_scalars = (await request.ctx.db_session.execute( query.order_by(DNSLog.log_id.desc()).offset( page * page_size).limit(page_size))).scalars() count = (await request.ctx.db_session.execute(count_query)).scalar() dns_logs = [] for i in dns_log_scalars: # 也可以在入库的时候计算地区, 在这里输出时计算是因为感觉存储大量地址挺浪费空间的 233, 而计算不用太耗时间 i.region = get_region_from_ip(i.client_ip) i.log_time = i.log_time.strftime('%Y-%m-%d %H:%M:%S') dns_logs.append(i) paged = PagedResponse(payload=dns_logs, total_page=ceil(count / page_size), curr_page=page) return json(Response.success('', paged)) else: return json(Response.invalid('无效请求')) else: return json(Response.invalid('无效请求'))
async def modify(request: sanic.Request): if isinstance(request.json, dict): key = request.json.get('key', None) value = request.json.get('value', None) if isinstance(key, str) and value is not None: _, mutable = system_config.get_config_privileges(key) if mutable and isinstance(value, system_config.get_config_type(key)): setattr(system_config, key, value) return json(Response.success('修改成功')) else: return json(Response.failed('目标配置无法修改或者类型不正确')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def list_user(request: sanic.Request): users = (await request.ctx.db_session.execute( select(User.username, User.user_type))).fetchall() users = [{ 'username': user, 'user_type': user_type } for user, user_type in users] return json(Response.success('', users))
async def delete(request: sanic.Request): if isinstance(request.json, dict): rule_id = request.json.get('rule_id', None) if isinstance(rule_id, int): query = select(HttpRule).where(HttpRule.rule_id == rule_id) rule = (await request.ctx.db_session.execute(query)).scalar() if rule is not None: await request.ctx.db_session.delete(rule) await request.ctx.db_session.commit() return json(Response.success('删除成功')) else: return json(Response.failed('规则不存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def add_catalog(request: sanic.Request): if isinstance(request.json, dict): catalog_name = request.json.get('catalog_name', None) if isinstance(catalog_name, str): query = select(HttpRuleCatalog).where( HttpRuleCatalog.catalog_name == catalog_name) catalog = (await request.ctx.db_session.execute(query)).scalar() if catalog is None: catalog = HttpRuleCatalog(catalog_name=catalog_name) request.ctx.db_session.add(catalog) await request.ctx.db_session.commit() return json(Response.success('创建成功')) else: return json(Response.failed('已有此分类')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def config_list(request: sanic.Request): configs = system_config.get_public_config() config_table = [] for key, value in configs.items(): config_table.append({ 'key': key, 'value': value, 'comment': system_config.get_config_comment(key) }) return json(Response.success("", config_table))
async def register(request: sanic.Request): username = request.json.get('username', None) password = request.json.get('password', None) user = (await request.ctx.db_session.execute( select(User).where(User.username == username))).scalar() if user is None: if isinstance(username, str) and isinstance( password, str) and len(username) < 255: user = User(username=username, password=passwd_hash(password, system_config.PASSWORD_SALT), user_type=constants.USER_TYPE_NORMAL) request.ctx.db_session.add(user) await request.ctx.db_session.commit() return json(Response.success('注册成功')) else: return json(Response.invalid('无效请求')) else: return json(Response.failed('用户名已经被注册'))
async def modify(request: sanic.Request): if isinstance(request.json, dict): filename = request.json.get('filename', None) new_filename = request.json.get('new_filename', None) content = request.json.get('content', None) if isinstance(filename, str): filename = secure_filename_with_directory(filename) path = join(system_config.UPLOAD_PATH, filename) if exists(path) and not isdir(path): response = Response.success('修改成功') if isinstance(content, str): await write_file(path, content.encode()) if isinstance(new_filename, str): new_filename = secure_filename_with_directory(new_filename) new_path = join(system_config.UPLOAD_PATH, new_filename) if not exists(new_path): try: rename(path, new_path) except FileNotFoundError: response = Response.failed('重命名失败, 请确认新文件所在文件夹存在') else: response = Response.failed('重命名失败, 目标文件名已存在') return json(response) else: return json(Response.failed('文件不存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def modify_directory(request: sanic.Request): if isinstance(request.json, dict): directory_name = request.json.get('directory_name', None) new_directory_name = request.json.get('new_directory_name', None) if isinstance( directory_name, str) and len(secure_filename(directory_name)) > 0 and len( secure_filename(new_directory_name)) > 0: full_path = join(system_config.UPLOAD_PATH, secure_filename(directory_name)) new_full_path = join(system_config.UPLOAD_PATH, secure_filename(new_directory_name)) if exists(full_path) and not exists(new_full_path): rename(full_path, new_full_path) return json(Response.success('重命名成功')) else: return json(Response.failed('目标文件夹名已存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def add(request: sanic.Request): if isinstance(request.json, dict): path = request.json.get('path', None) rule_type = request.json.get('rule_type', None) filename = request.json.get('filename', None) write_log = request.json.get('write_log', None) send_mail = request.json.get('send_mail', None) comment = request.json.get('comment', None) catalog_id = request.json.get('catalog_id', None) if isinstance(path, str) and isinstance(filename, str) and isinstance(write_log, bool) \ and isinstance(send_mail, bool) and isinstance(comment, str) and isinstance(catalog_id, int) and rule_type in constants.RULE_TYPES: query = select(HttpRuleCatalog).where( HttpRuleCatalog.catalog_id == catalog_id) catalog = (await request.ctx.db_session.execute(query)).scalar() if catalog is not None: query = select(HttpRule).where(HttpRule.path == path) rule = (await request.ctx.db_session.execute(query)).scalar() if rule is None: rule = HttpRule(path=path, rule_type=rule_type, filename=filename, write_log=write_log, send_mail=send_mail, comment=comment, catalog_id=catalog_id) request.ctx.db_session.add(rule) await request.ctx.db_session.commit() return json(Response.success('添加成功')) else: return json(Response.failed('已经存在此规则')) else: return json(Response.failed('分类不存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def modify(request: sanic.Request): if isinstance(request.json, dict): rule_id = request.json.get('rule_id', None) rule_type = request.json.get('rule_type', None) path = request.json.get('path', None) filename = request.json.get('filename', None) write_log = request.json.get('write_log', None) send_mail = request.json.get('send_mail', None) comment = request.json.get('comment', None) catalog_id = request.json.get('catalog_id', None) if isinstance(rule_id, int): query = select(HttpRule).where(HttpRule.rule_id == rule_id) rule = (await request.ctx.db_session.execute(query)).scalar() if rule is not None: if isinstance(path, str): query = select(HttpRule).where(HttpRule.path == path) result = (await request.ctx.db_session.execute(query)).scalar() if result is None or result.rule_id == rule_id: rule.path = path else: return json(Response.failed('路径已经存在')) if isinstance(catalog_id, int): query = select(HttpRuleCatalog).where( HttpRuleCatalog.catalog_id == catalog_id) result = (await request.ctx.db_session.execute(query)).scalar() if result is not None: rule.catalog_id = catalog_id else: return json(Response.failed('分类不存在')) if isinstance(filename, str): rule.filename = filename if isinstance(write_log, bool): rule.write_log = write_log if isinstance(send_mail, bool): rule.send_mail = send_mail if isinstance(comment, str): rule.comment = comment if rule_type in constants.RULE_TYPES: rule.rule_type = rule_type request.ctx.db_session.add(rule) await request.ctx.db_session.commit() return json(Response.success('修改成功')) else: return json(Response.failed('规则不存在')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def file_list(request: sanic.Request): def get_dir_files(path: str, base=''): files = [] for entry in scandir(path): files.append({ 'filename': entry.name, 'path': join(base, entry.name), 'size': entry.stat().st_size, 'mttime': entry.stat().st_mtime, 'dir': entry.is_dir() }) files = sorted(files, key=lambda x: x['filename']) return files files = get_dir_files(system_config.UPLOAD_PATH) for file in files: # 只支持一层深度 if file['dir']: file['children'] = get_dir_files( join(system_config.UPLOAD_PATH, file['filename']), file['filename']) return json(Response.success('', files))
async def preview(request: sanic.Request): if isinstance(request.json, dict): filename = request.json.get('filename', None) if isinstance(filename, str): filename = secure_filename(filename) path = join(system_config.TEMP_FILE_PATH, filename) if exists(path) and not isdir(path): if getsize(path) < system_config.MAX_PREVIEW_SIZE: content = await read_file(path) try: content = content.decode('utf-8') return json(Response.success('', content)) except Exception: return json(Response.failed('文件不是纯文本, 无法预览')) else: json(Response.failed('文件过大, 无法预览')) else: return json(Response.failed('文件不存在或者为文件夹')) else: return json(Response.invalid('参数无效')) else: return json(Response.invalid('参数无效'))
async def status(request: sanic.Request): if request.ctx.auth: return json(Response.success('', request.ctx.user.user_type)) else: return json(Response.failed())
async def decorator(request: sanic.Request, *args, **kwargs): if request.ctx.auth and request.ctx.user.user_type == constants.USER_TYPE_SUPER_ADMIN: return await func(request, *args, **kwargs) else: return sanic.response.json( Response().failed('Super admin required'), 403)
async def decorator(request: sanic.Request, *args, **kwargs): if request.ctx.auth: return await func(request, *args, **kwargs) else: return sanic.response.json(Response().failed('Login required'), 403)