Squashed 'paste-framework/' content from commit 34e8684
git-subtree-dir: paste-framework git-subtree-split: 34e8684c4bc3cebbe177509f42ab4ef5b5425a7a
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
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
|
||||
Reference in New Issue
Block a user