Files
d3i-szct/paste/web/decorators.py
T
zwf 4729698049 Squashed 'paste-framework/' content from commit 34e8684
git-subtree-dir: paste-framework
git-subtree-split: 34e8684c4bc3cebbe177509f42ab4ef5b5425a7a
2026-06-02 19:09:22 +08:00

206 lines
8.3 KiB
Python

import functools
import logging
from typing import Awaitable
from jwt import ExpiredSignatureError, InvalidSignatureError, InvalidTokenError
from paste.security import token
from paste.web.handler import RequestHandler
def route(route_pattern: str):
"""
路由装饰器。为类增加 route_pattern 属性,并赋值。
:param route_pattern: URL 路径模式
"""
def wrapper(cls: type[RequestHandler]):
cls.route_pattern = route_pattern
return cls
# 标记已经被 route 装饰
setattr(wrapper, '__route__', True)
return wrapper
def auth_token(func):
"""
令牌验证装饰器,用于 :class:`tornado.web.RequestHandler` 子类中的 get()/post() 等需要执行权限验证的方法,以便在正
式执行方法前利用客户端提交的令牌进行鉴权。
当执行该装饰器解码令牌后,将更新 RequestHandler 对象的 token_payload 属性数据。其次,若能通过令牌中配载的 user_id 取
得用户数据,则还将设置 current_user 属性。
该装饰器仅用于校验令牌的有效性,并取得用户信息,不负责校验用户的具体权限,若要验证权限需使用 @auth_permission 装饰器。
使用方式如下::
@auth_token
async def post(self):
pass
要求在请求的 Headers 中必须包含 Access-Token,且内容由 encode_token 方法签发。
:param func: 被装饰的函数对象,不需要手动传入该参数
:return: 装饰后的函数对象
"""
@functools.wraps(func)
async def wrapper(*args, **kwargs):
req_handler: RequestHandler = args[0]
try:
# 请求头
req_headers = dict(req_handler.request.headers)
# 取出 Token
access_token = req_headers.get('Access-Token', None)
if access_token in (None, b'', ''):
raise InvalidTokenError(f'请求地址:{req_handler.request.uri}')
# 如果采用 OAuth2 规范,这里应当调用远程 API 执行解码,解码后返回用户信息
# 用解码后的 Token 字典更新 Handler 中的的 token_dict
token_payload = token.decode_token(access_token)
req_handler.token_payload.update(token_payload)
# 根据 Token 读取用户对象,并设置到请求处理对象(控制器)
_user_id = req_handler.token_param('user_id')
if _user_id and req_handler.user_class:
_user = await req_handler.user_class.async_find_by_id(_user_id)
if _user is None:
raise InvalidTokenError()
req_handler.current_user = _user
await req_handler.after_auth_token(token_payload)
# 兼容同步或异步方法
_result = func(*args, **kwargs)
if isinstance(_result, Awaitable):
_result = await _result
return _result
except ExpiredSignatureError as e:
e.args = ('令牌已过期,请求被拒绝.',)
req_handler.response_error(e, status_code=403, api_status_code=403)
req_handler.log(msg=e, level=logging.ERROR, is_log_exc=True)
return None
except InvalidSignatureError as e:
e.args = ('令牌签名错误,请求被拒绝.',)
req_handler.response_error(e, status_code=403, api_status_code=403)
req_handler.log(msg=e, level=logging.ERROR, is_log_exc=True)
return None
except InvalidTokenError as e:
e.args = ('令牌错误,请求被拒绝.',)
req_handler.response_error(e, status_code=401, api_status_code=401)
req_handler.log(msg=e, level=logging.ERROR, is_log_exc=True)
return None
except Exception as e:
req_handler.response_error(e, status_code=501, api_status_code=501)
req_handler.log(msg=e, level=logging.ERROR, is_log_exc=True)
return None
# 标记已经被 auth_token 装饰
setattr(wrapper, '__auth_token__', True)
return wrapper
def auth_permission(func):
"""
权限检查装饰器。若不启用 RBAC 则不应用该装饰器。
用于检查用户是否有执行某个操作的具体权限。该装饰器须跟随在 @auth_token 装饰器的后面使用。
:param func: 被装饰的函数对象,不需要手动传入该参数
:return: 装饰后的函数对象
"""
@functools.wraps(func)
async def wrapper(*args, **kwargs):
#
# 为了能在不使用 RBAC 的系统中正常运行,这里的引用必须放在函数中
# 否则初始化过程中 RBAC 数据模型会尝试读取数据表配置,发生错误
#
from paste.rbac.rbac_user import RbacUser, Supervisors
req_handler: RequestHandler = args[0]
try:
# 验证当前用户状态
_user: RbacUser = req_handler.current_user
assert _user is not None, f"无效令牌或未登录,无权执行:{req_handler.route_pattern} 操作."
# 类型检测
_right_type = isinstance(_user, RbacUser)
assert _right_type, f"当前用户类型错误,必须为 RbacUser 的子类."
if _user.username not in Supervisors:
# 验证用户权限状态
_has_permission = await _user.has_permission(req_handler.route_pattern)
assert _has_permission, f"当前用户 {_user.username} 无权执行:{req_handler.route_pattern} 操作."
# 兼容同步或异步方法
_result = func(*args, **kwargs)
if isinstance(_result, Awaitable):
_result = await _result
return _result
except AssertionError as e:
req_handler.response_error(e, status_code=401, api_status_code=401)
req_handler.log(msg=e, level=logging.ERROR, is_log_exc=True)
return None
except Exception as e:
req_handler.response_error(e, status_code=501, api_status_code=501)
req_handler.log(msg=e, level=logging.ERROR, is_log_exc=True)
return None
# 标记已经被 auth_permission 装饰
setattr(wrapper, '__auth_permission__', True)
return wrapper
def auth_rule(func):
"""
规则检查装饰器。若不启用规则验证,则不应用该装饰器。
用于对用户按规则验证。该装饰器须跟随在 @auth_token 装饰器的后面使用。
:param func: 被装饰的函数对象,不需要手动传入该参数
:return: 装饰后的函数对象
"""
@functools.wraps(func)
async def wrapper(*args, **kwargs):
#
# 为了能在不使用 RBAC 的系统中正常运行,这里的引用必须放在函数中
# 否则初始化过程中 RBAC 数据模型会尝试读取数据表配置,发生错误
#
from paste.rbac.rbac_user import RbacUser, Supervisors
req_handler: RequestHandler = args[0]
try:
# 验证当前用户状态
_user: RbacUser = req_handler.current_user
assert _user is not None, f"无效令牌或未登录,无权执行:{req_handler.route_pattern} 操作."
# 类型检测
_right_type = isinstance(_user, RbacUser)
assert _right_type, f"当前用户类型错误,必须为 RbacUser 的子类."
if _user.username not in Supervisors:
# 验证用户权限状态
_user_can = await _user.can(req_handler.route_pattern, **kwargs)
assert _user_can, f"当前用户 {_user.username} 无权执行:{req_handler.route_pattern} 操作(规则验证不通过)."
# 兼容同步或异步方法
_result = func(*args, **kwargs)
if isinstance(_result, Awaitable):
_result = await _result
return _result
except AssertionError as e:
req_handler.response_error(e, status_code=401, api_status_code=401)
req_handler.log(msg=e, level=logging.ERROR, is_log_exc=True)
return None
except Exception as e:
req_handler.response_error(e, status_code=501, api_status_code=501)
req_handler.log(msg=e, level=logging.ERROR, is_log_exc=True)
return None
# 标记已经被 auth_rule 装饰
setattr(wrapper, '__auth_rule__', True)
return wrapper