初始提交
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
from fastapi import Depends, Request, params
|
||||
from sqlalchemy import ColumnElement, func, or_, select
|
||||
|
||||
from common.context import RequestContext
|
||||
from config.database import Base
|
||||
from module_admin.entity.do.dept_do import SysDept
|
||||
from module_admin.entity.do.role_do import SysRoleDept
|
||||
from utils.dependency_util import DependencyUtil
|
||||
|
||||
|
||||
class GetDataScope:
|
||||
"""
|
||||
获取当前用户数据权限对应的查询sql语句
|
||||
"""
|
||||
|
||||
DATA_SCOPE_ALL = '1'
|
||||
DATA_SCOPE_CUSTOM = '2'
|
||||
DATA_SCOPE_DEPT = '3'
|
||||
DATA_SCOPE_DEPT_AND_CHILD = '4'
|
||||
DATA_SCOPE_SELF = '5'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
query_alias: Base,
|
||||
user_alias: str | None = 'user_id',
|
||||
dept_alias: str | None = 'dept_id',
|
||||
) -> None:
|
||||
"""
|
||||
获取当前用户数据权限对应的查询sql语句
|
||||
|
||||
:param query_alias: 所要查询表对应的sqlalchemy模型类,不可为空
|
||||
:param user_alias: 用户id字段别名,默认为'user_id'
|
||||
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
||||
"""
|
||||
self.query_alias = query_alias
|
||||
self.user_alias = user_alias
|
||||
self.dept_alias = dept_alias
|
||||
|
||||
def __call__(self, request: Request) -> ColumnElement:
|
||||
DependencyUtil.check_exclude_routes(request, err_msg='当前路由不在认证规则内,不可使用GetDataScope依赖项')
|
||||
current_user = RequestContext.get_current_user()
|
||||
user_id = current_user.user.user_id
|
||||
dept_id = current_user.user.dept_id
|
||||
custom_data_scope_role_id_list = [
|
||||
item.role_id for item in current_user.user.role if item.data_scope == self.DATA_SCOPE_CUSTOM
|
||||
]
|
||||
param_sql_list = []
|
||||
for role in current_user.user.role:
|
||||
if current_user.user.admin or role.data_scope == self.DATA_SCOPE_ALL:
|
||||
param_sql_list = [True]
|
||||
break
|
||||
if role.data_scope == self.DATA_SCOPE_CUSTOM:
|
||||
if len(custom_data_scope_role_id_list) > 1:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.dept_alias).in_(
|
||||
select(SysRoleDept.dept_id).where(SysRoleDept.role_id.in_(custom_data_scope_role_id_list))
|
||||
)
|
||||
if hasattr(self.query_alias, self.dept_alias)
|
||||
else False
|
||||
)
|
||||
else:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.dept_alias).in_(
|
||||
select(SysRoleDept.dept_id).where(SysRoleDept.role_id == role.role_id)
|
||||
)
|
||||
if hasattr(self.query_alias, self.dept_alias)
|
||||
else False
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_DEPT:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.dept_alias) == dept_id
|
||||
if hasattr(self.query_alias, self.dept_alias)
|
||||
else False
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_DEPT_AND_CHILD:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.dept_alias).in_(
|
||||
select(SysDept.dept_id).where(
|
||||
or_(SysDept.dept_id == dept_id, func.find_in_set(dept_id, SysDept.ancestors))
|
||||
)
|
||||
)
|
||||
if hasattr(self.query_alias, self.dept_alias)
|
||||
else False
|
||||
)
|
||||
elif role.data_scope == self.DATA_SCOPE_SELF:
|
||||
param_sql_list.append(
|
||||
getattr(self.query_alias, self.user_alias) == user_id
|
||||
if hasattr(self.query_alias, self.user_alias)
|
||||
else False
|
||||
)
|
||||
else:
|
||||
param_sql_list.append(False)
|
||||
param_sql_list = list(dict.fromkeys(param_sql_list))
|
||||
param_sql = or_(*param_sql_list)
|
||||
|
||||
return param_sql
|
||||
|
||||
|
||||
def DataScopeDependency( # noqa: N802
|
||||
query_alias: Base,
|
||||
user_alias: str | None = 'user_id',
|
||||
dept_alias: str | None = 'dept_id',
|
||||
) -> params.Depends:
|
||||
"""
|
||||
当前用户数据权限依赖
|
||||
|
||||
:param query_alias: 所要查询表对应的sqlalchemy模型类,不可为空
|
||||
:param user_alias: 用户id字段别名,默认为'user_id'
|
||||
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
||||
:return: 当前用户数据权限依赖
|
||||
"""
|
||||
return Depends(GetDataScope(query_alias, user_alias, dept_alias))
|
||||
@@ -0,0 +1,12 @@
|
||||
from fastapi import Depends, params
|
||||
|
||||
from config.get_db import get_db
|
||||
|
||||
|
||||
def DBSessionDependency() -> params.Depends: # noqa: N802
|
||||
"""
|
||||
数据库会话依赖
|
||||
|
||||
:return: 数据库会话依赖
|
||||
"""
|
||||
return Depends(get_db)
|
||||
@@ -0,0 +1,94 @@
|
||||
from fastapi import Depends, Request, params
|
||||
|
||||
from common.context import RequestContext
|
||||
from exceptions.exception import PermissionException
|
||||
from utils.dependency_util import DependencyUtil
|
||||
|
||||
|
||||
class CheckUserInterfaceAuth:
|
||||
"""
|
||||
校验当前用户是否具有相应的接口权限
|
||||
"""
|
||||
|
||||
def __init__(self, perm: str | list, is_strict: bool = False) -> None:
|
||||
"""
|
||||
校验当前用户是否具有相应的接口权限
|
||||
|
||||
:param perm: 权限标识
|
||||
:param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过
|
||||
"""
|
||||
self.perm = perm
|
||||
self.is_strict = is_strict
|
||||
|
||||
def __call__(self, request: Request) -> bool:
|
||||
DependencyUtil.check_exclude_routes(
|
||||
request, err_msg='当前路由不在认证规则内,不可使用CheckUserInterfaceAuth依赖项'
|
||||
)
|
||||
current_user = RequestContext.get_current_user()
|
||||
user_auth_list = current_user.permissions
|
||||
if '*:*:*' in user_auth_list:
|
||||
return True
|
||||
if isinstance(self.perm, str) and self.perm in user_auth_list:
|
||||
return True
|
||||
if isinstance(self.perm, list):
|
||||
if self.is_strict:
|
||||
if all(perm_str in user_auth_list for perm_str in self.perm):
|
||||
return True
|
||||
elif any(perm_str in user_auth_list for perm_str in self.perm):
|
||||
return True
|
||||
raise PermissionException(data='', message='该用户无此接口权限')
|
||||
|
||||
|
||||
class CheckRoleInterfaceAuth:
|
||||
"""
|
||||
根据角色校验当前用户是否具有相应的接口权限
|
||||
"""
|
||||
|
||||
def __init__(self, role_key: str | list, is_strict: bool = False) -> None:
|
||||
"""
|
||||
根据角色校验当前用户是否具有相应的接口权限
|
||||
|
||||
:param role_key: 角色标识
|
||||
:param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过
|
||||
"""
|
||||
self.role_key = role_key
|
||||
self.is_strict = is_strict
|
||||
|
||||
def __call__(self, request: Request) -> bool:
|
||||
DependencyUtil.check_exclude_routes(
|
||||
request, err_msg='当前路由不在认证规则内,不可使用CheckRoleInterfaceAuth依赖项'
|
||||
)
|
||||
current_user = RequestContext.get_current_user()
|
||||
user_role_list = current_user.user.role
|
||||
user_role_key_list = [role.role_key for role in user_role_list]
|
||||
if isinstance(self.role_key, str) and self.role_key in user_role_key_list:
|
||||
return True
|
||||
if isinstance(self.role_key, list):
|
||||
if self.is_strict:
|
||||
if all(role_key_str in user_role_key_list for role_key_str in self.role_key):
|
||||
return True
|
||||
elif any(role_key_str in user_role_key_list for role_key_str in self.role_key):
|
||||
return True
|
||||
raise PermissionException(data='', message='该用户无此接口权限')
|
||||
|
||||
|
||||
def UserInterfaceAuthDependency(perm: str | list, is_strict: bool = False) -> params.Depends: # noqa: N802
|
||||
"""
|
||||
根据权限标识校验当前用户接口权限依赖
|
||||
|
||||
:param perm: 权限标识
|
||||
:param is_strict: 当传入的权限标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个权限标识,所有的校验结果都需要为True才会通过
|
||||
:return: 根据权限标识校验当前用户接口权限依赖
|
||||
"""
|
||||
return Depends(CheckUserInterfaceAuth(perm, is_strict))
|
||||
|
||||
|
||||
def RoleInterfaceAuthDependency(role_key: str | list, is_strict: bool = False) -> params.Depends: # noqa: N802
|
||||
"""
|
||||
根据角色校验当前用户接口权限依赖
|
||||
|
||||
:param role_key: 角色标识
|
||||
:param is_strict: 当传入的角色标识是list类型时,是否开启严格模式,开启表示会校验列表中的每一个角色标识,所有的校验结果都需要为True才会通过
|
||||
:return: 根据角色校验当前用户接口权限依赖
|
||||
"""
|
||||
return Depends(CheckRoleInterfaceAuth(role_key, is_strict))
|
||||
@@ -0,0 +1,146 @@
|
||||
import re
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
from fastapi import Depends, Request, params
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.context import RequestContext
|
||||
from config.env import AppConfig
|
||||
from config.get_db import get_db
|
||||
from exceptions.exception import AuthException
|
||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||
from module_admin.service.login_service import LoginService
|
||||
|
||||
|
||||
# 定义排除路由的字典结构
|
||||
class ExcludeRoute(TypedDict, total=False):
|
||||
"""
|
||||
排除路由的字典结构
|
||||
|
||||
:param path: 路由路径(必填)
|
||||
:param methods: HTTP方法列表,空列表表示所有方法(可选,默认为[])
|
||||
:param ignore_paths: 需要忽略的特定路径列表,即使匹配通配符也不排除(可选,默认为[])
|
||||
"""
|
||||
|
||||
path: str
|
||||
methods: list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']]
|
||||
ignore_paths: list[str]
|
||||
|
||||
|
||||
# 创建OAuth2PasswordBearer对象
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/login')
|
||||
|
||||
|
||||
class PreAuth:
|
||||
"""
|
||||
登录认证前置校验依赖类
|
||||
"""
|
||||
|
||||
def __init__(self, exclude_routes: list[ExcludeRoute] | None = None) -> None:
|
||||
"""
|
||||
初始化登录认证前置校验依赖
|
||||
|
||||
:param exclude_routes: 需要排除的路由列表,格式为:
|
||||
[{'path': '/path1', 'methods': ['GET', 'POST']}, {'path': '/path2/{param}', 'methods': ['GET']}]
|
||||
methods 可以是字符串或列表,空列表表示所有方法
|
||||
"""
|
||||
self.exclude_routes = exclude_routes or []
|
||||
# 编译排除路径为正则表达式模式,并存储方法信息
|
||||
self.exclude_patterns = []
|
||||
|
||||
for route in self.exclude_routes:
|
||||
# 使用TypedDict,确保路由字典包含path字段
|
||||
path = route.get('path', '')
|
||||
methods = route.get('methods', [])
|
||||
ignore_paths = route.get('ignore_paths', [])
|
||||
|
||||
# 编译路径为正则表达式
|
||||
pattern = self._compile_path_pattern(path)
|
||||
# 存储编译后的模式和方法信息
|
||||
self.exclude_patterns.append(
|
||||
{
|
||||
'pattern': pattern,
|
||||
'methods': [method.upper() for method in methods],
|
||||
'original_path': path,
|
||||
'ignore_paths': ignore_paths,
|
||||
}
|
||||
)
|
||||
|
||||
def _compile_path_pattern(self, path: str) -> re.Pattern:
|
||||
"""
|
||||
将FastAPI路径转换为正则表达式模式
|
||||
|
||||
:param path: FastAPI路径(如 /configKey/{config_key})
|
||||
:return: 编译后的正则表达式模式
|
||||
"""
|
||||
# 将FastAPI路径参数转换为正则表达式
|
||||
# 例如:/configKey/{config_key} -> /configKey/[^/]+
|
||||
pattern_str = re.sub(r'\{[^}]+\}', r'[^/]+', path)
|
||||
# 添加开始和结束锚点,确保精确匹配
|
||||
return re.compile(f'^{pattern_str}$')
|
||||
|
||||
async def __call__(self, request: Request, db: AsyncSession = Depends(get_db)) -> CurrentUserModel | None:
|
||||
"""
|
||||
执行登录认证校验
|
||||
|
||||
:param request: 当前请求对象
|
||||
:param db: 数据库会话
|
||||
:return: 当前用户信息
|
||||
"""
|
||||
# 获取当前请求路径和方法
|
||||
path = request.url.path
|
||||
method = request.method.upper()
|
||||
|
||||
# 从配置中获取APP_ROOT_PATH
|
||||
app_root_path = AppConfig.app_root_path
|
||||
|
||||
# 去掉APP_ROOT_PATH前缀
|
||||
if app_root_path and path.startswith(app_root_path):
|
||||
path = path[len(app_root_path) :]
|
||||
|
||||
# 设置上下文变量
|
||||
RequestContext.set_current_exclude_patterns(self.exclude_patterns)
|
||||
|
||||
# 检查路径和方法是否匹配排除模式
|
||||
for item in self.exclude_patterns:
|
||||
pattern = item['pattern']
|
||||
exclude_methods = item['methods']
|
||||
ignore_paths = item['ignore_paths']
|
||||
|
||||
# 检查当前路径是否在忽略列表中
|
||||
if path in ignore_paths:
|
||||
continue
|
||||
|
||||
# 检查路径是否匹配,并且methods为空列表(匹配所有方法)或者当前方法在允许列表中
|
||||
if pattern.match(path) and (not exclude_methods or method in exclude_methods):
|
||||
# 跳过认证
|
||||
return None
|
||||
|
||||
# 否则执行正常认证
|
||||
token = request.headers.get('Authorization')
|
||||
if not token:
|
||||
raise AuthException(data='', message='用户未登录,请先完成登录')
|
||||
current_user = await LoginService.get_current_user(request, token, db)
|
||||
return current_user
|
||||
|
||||
|
||||
def PreAuthDependency(exclude_routes: list[ExcludeRoute] | None = None) -> params.Depends: # noqa: N802
|
||||
"""
|
||||
登录认证前置校验依赖
|
||||
|
||||
:param exclude_routes: 需要排除的路由列表,格式为:
|
||||
[{'path': '/path1', 'methods': ['GET', 'POST']}, {'path': '/path2/{param}', 'methods': ['GET']}]
|
||||
methods 可以是字符串或列表,空列表表示所有方法
|
||||
:return: 登录认证前置校验依赖
|
||||
"""
|
||||
return Depends(PreAuth(exclude_routes))
|
||||
|
||||
|
||||
def CurrentUserDependency() -> params.Depends: # noqa: N802
|
||||
"""
|
||||
当前登录用户信息依赖
|
||||
|
||||
:return: 当前登录用户信息依赖
|
||||
"""
|
||||
return Depends(LoginService.get_current_user)
|
||||
Reference in New Issue
Block a user