Files
hsa/Html/apps/auth/decorators.py
2025-08-27 19:39:38 +08:00

99 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# myapp/auth/decorators.py
from functools import wraps
from typing import Callable, Optional, Union, Iterable
from flask import session, redirect, url_for, current_app
RoleType = Union[str, Iterable[str]]
RoleChecker = Callable[[str, RoleType], bool] # (username, required_roles) -> bool
def _get_deps():
"""从 app 注入点拿依赖,避免循环导入。"""
app = current_app
uuid2username = (
app.config.get("UUID2USERNAME")
or app.extensions.get("uuid2username")
)
users_list = (
app.config.get("USERS_LIST")
or app.extensions.get("users_list")
)
role_checker: Optional[RoleChecker] = (
app.config.get("ROLE_CHECKER")
or app.extensions.get("role_checker")
)
return uuid2username, users_list, role_checker
def require_role(
view=None,
*,
# 未登录时跳转到哪个 endpoint
login_endpoint: str = "auth.login",
# 角色要求None/空 -> 仅需登录;"teacher" 或 ["teacher", "admin"] -> 需具备其中之一
roles: Optional[RoleType] = None,
# 可选:自定义角色校验函数(优先级高于内置 users_list 适配)
checker: Optional[RoleChecker] = None,
):
"""
用法:
1) 仅需登录(等价于 require_user
@require_role
2) 需要 teacher 角色(等价于 require_teacher
@require_role(roles="teacher")
3) 需要多个角色之一:
@require_role(roles=["teacher", "admin"])
4) 自定义校验器(例如 RBAC/权限码):
@require_role(roles="teacher", checker=my_checker)
也支持指定登录端点:
@require_role(login_endpoint="login")
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
uuid2username, users_list, injected_checker = _get_deps()
user_id = session.get("user_id")
if not user_id or uuid2username is None or user_id not in uuid2username:
return redirect(url_for(login_endpoint))
# 仅需登录
if not roles:
return func(*args, **kwargs)
username = uuid2username[user_id]
# 选择校验器优先级:参数 checker > app 注入的 role_checker > 内置 users_list 适配
effective_checker = checker or injected_checker
# 内置对 users_list 的向后兼容(你的现有接口)
if effective_checker is None:
def builtin_checker(u: str, required: RoleType) -> bool:
# 支持 teacher 场景(原先的 get_user_is_teacher
def has_teacher(u_: str) -> bool:
if users_list is None:
return False
getter = getattr(users_list, "get_user_is_teacher", None)
return bool(getter and getter(u_) is True)
def match_one(required_role: str) -> bool:
if required_role == "teacher":
return has_teacher(u)
# 你也可以在此扩展更多内置角色判断
# 比如 get_user_is_admin / get_user_roles 等
# 未知角色默认 False
return False
if isinstance(required, str):
return match_one(required)
return any(match_one(r) for r in required)
effective_checker = builtin_checker
ok = effective_checker(username, roles)
if not ok:
return redirect(url_for(login_endpoint))
return func(*args, **kwargs)
return wrapper
return decorator(view) if callable(view) else decorator