From 2be07fad64ea99a2470a813c1ed9bce3dbfa6e7e Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 5 Jan 2024 02:52:37 +0800 Subject: [PATCH 01/22] Reconstruct RBAC authentication logic --- backend/app/common/rbac.py | 37 ++++++++++++++++++++----------------- backend/app/core/conf.py | 10 ++++++---- backend/app/schemas/user.py | 3 +++ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index fe2a5a7f..06091244 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -27,12 +27,12 @@ async def enforcer() -> casbin.AsyncEnforcer: await enforcer.load_policy() return enforcer - async def rbac_verify(self, request: Request, _: dict = DependsJwtAuth) -> None: + async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> None: """ RBAC 权限校验 :param request: - :param _: + :param _token: :return: """ path = request.url.path @@ -57,34 +57,37 @@ async def rbac_verify(self, request: Request, _: dict = DependsJwtAuth) -> None: if data_scope: return method = request.method - if settings.MENU_PERMISSION: + # TODO: 手动编写每一个菜单及菜单按钮权限,使用 fastapi Depends 实现 + path_auth = 'todo' + if settings.PERMISSION_MODE == 'role-menu': # 菜单权限校验 - # TODO: 改用流行方案,自定义接口权限字段标识 - path_auth = path.split(f'{settings.API_V1_STR}/')[-1].replace('/', ':') + f':{method}' + if path_auth in set(settings.MENU_EXCLUDE): + return menu_perms = [] forbid_menu_perms = [] for role in user_roles: - if role.menus: - for menu in role.menus: + user_menus = role.menus + if user_menus: + for menu in user_menus: + perms = menu.perms if menu.status == StatusType.enable: - menu_perms.append(menu.perms) + menu_perms.extend(perms.split(',')) else: - forbid_menu_perms.append(menu.perms) - if path_auth in set(settings.MENU_EXCLUDE): - return - if path_auth in set([perm for perms_str in forbid_menu_perms for perm in perms_str.split(',')]): + forbid_menu_perms.extend(perms.split(',')) + if path_auth in set(forbid_menu_perms): raise AuthorizationError(msg='菜单已禁用,授权失败') - if path_auth not in set([perm for perms_str in menu_perms for perm in perms_str.split(',')]): + if path_auth not in set(menu_perms): raise AuthorizationError else: # casbin 权限校验 forbid_menu_path = [] for role in user_roles: - if role.menus: - for menu in role.menus: + user_menus = role.menus + if user_menus: + for menu in user_menus: if menu.status == StatusType.disable: - forbid_menu_path.append(menu.path) - if path.split('/')[-1] in forbid_menu_path: + forbid_menu_path.append(menu.perms) + if path_auth in forbid_menu_path: raise AuthorizationError(msg='菜单已禁用,授权失败') if (method, path) in settings.CASBIN_EXCLUDE: return diff --git a/backend/app/core/conf.py b/backend/app/core/conf.py index b3ccd26b..e03f7d37 100644 --- a/backend/app/core/conf.py +++ b/backend/app/core/conf.py @@ -103,7 +103,7 @@ def validate_openapi_url(cls, values): TOKEN_URL_SWAGGER: str = f'{API_V1_STR}/auth/swagger_login' TOKEN_REDIS_PREFIX: str = 'fba_token' TOKEN_REFRESH_REDIS_PREFIX: str = 'fba_refresh_token' - TOKEN_EXCLUDE: list[str] = [ # 白名单 + TOKEN_EXCLUDE: list[str] = [ # 路由白名单 f'{API_V1_STR}/auth/login', ] @@ -120,7 +120,10 @@ def validate_openapi_url(cls, values): MIDDLEWARE_GZIP: bool = True MIDDLEWARE_ACCESS: bool = False - # Casbin + # RBAC Permission Mode + PERMISSION_MODE: Literal['casbin', 'role-menu'] = 'casbin' + + # Casbin Auth CASBIN_RBAC_MODEL_NAME: str = 'rbac_model.conf' CASBIN_EXCLUDE: set[tuple[str, str]] = { ('POST', f'{API_V1_STR}/auth/swagger_login'), @@ -130,8 +133,7 @@ def validate_openapi_url(cls, values): ('GET', f'{API_V1_STR}/auth/captcha'), } - # Menu - MENU_PERMISSION: bool = False # 危险行为,开启此功能, Casbin 鉴权将失效,并将使用角色菜单鉴权 (默认关闭) + # Role Menu Auth MENU_EXCLUDE: list[str] = [ 'auth:swagger_login:post', 'auth:login:post', diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index f4d63d81..f7ae64f3 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -76,6 +76,9 @@ class GetAllUserInfo(GetUserInfoNoRelation): class GetCurrentUserInfo(GetAllUserInfo): model_config = ConfigDict(from_attributes=True) + dept: GetAllDept | str | None = None + roles: list[GetAllRole] | list[str] | None = None + @model_validator(mode='after') def handel(self, values): """处理部门和角色""" From 8a2a913ef9d3ab656762bddc99ec05f15ce1abdd Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 5 Jan 2024 02:55:12 +0800 Subject: [PATCH 02/22] fix typo --- backend/app/common/rbac.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index 06091244..3467f0e1 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -57,7 +57,7 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N if data_scope: return method = request.method - # TODO: 手动编写每一个菜单及菜单按钮权限,使用 fastapi Depends 实现 + # TODO: 手动编写每一个路由权限标识,使用 fastapi Depends 实现 path_auth = 'todo' if settings.PERMISSION_MODE == 'role-menu': # 菜单权限校验 From 51668955076ffc13071b07649f57e210ddef9774 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 5 Jan 2024 18:59:18 +0800 Subject: [PATCH 03/22] fix README typo --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 019582d7..3af56df9 100644 --- a/README.md +++ b/README.md @@ -218,18 +218,13 @@ Execute unittests via pytest - [Ruff](https://beta.ruff.rs/docs/) - ... -## 互动 +## Interactivity We only have one current channel. - - - - - - - -
Jump
Telegram
+| [Jump](https://t.me/+ZlPhIFkPp7E4NGI1) | +|----------------------------------------| +| Telegram | ## Sponsor us From 0f9282636a188c9694fbc9b5a108fb38d823cccc Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 5 Jan 2024 18:59:33 +0800 Subject: [PATCH 04/22] Migrate casbin sqla Adapter to redis --- backend/app/.env.example | 2 ++ backend/app/common/rbac.py | 32 ++++++++++++++++++++++++++------ backend/app/core/conf.py | 4 +++- backend/app/core/path_conf.py | 5 ----- requirements.txt | 8 ++++---- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/backend/app/.env.example b/backend/app/.env.example index 1863b8c2..2ecb8301 100644 --- a/backend/app/.env.example +++ b/backend/app/.env.example @@ -10,6 +10,8 @@ REDIS_HOST='127.0.0.1' REDIS_PORT=6379 REDIS_PASSWORD='' REDIS_DATABASE=0 +# Casbin +CASBIN_REDIS_DATABASE=9 # Celery CELERY_REDIS_HOST='127.0.0.1' CELERY_REDIS_PORT=6379 diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index 3467f0e1..1f959dad 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -1,17 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import casbin -import casbin_async_sqlalchemy_adapter +from casbin_async_redis_adapter.adapter import Adapter from fastapi import Depends, Request from backend.app.common.enums import StatusType from backend.app.common.exception.errors import AuthorizationError, TokenError from backend.app.common.jwt import DependsJwtAuth from backend.app.core.conf import settings -from backend.app.core.path_conf import RBAC_MODEL_CONF -from backend.app.database.db_mysql import async_engine -from backend.app.models.sys_casbin_rule import CasbinRule class RBAC: @@ -22,8 +19,31 @@ async def enforcer() -> casbin.AsyncEnforcer: :return: """ - adapter = casbin_async_sqlalchemy_adapter.Adapter(async_engine, db_class=CasbinRule) - enforcer = casbin.AsyncEnforcer(RBAC_MODEL_CONF, adapter) + # 规则数据作为死数据直接在方法内定义 + _CASBIN_RBAC_MODEL_CONF_TEXT = """ + [request_definition] + r = sub, obj, act + + [policy_definition] + p = sub, obj, act + + [role_definition] + g = _, _ + + [policy_effect] + e = some(where (p.eft == allow)) + + [matchers] + m = g(r.sub, p.sub) && (keyMatch(r.obj, p.obj) || keyMatch3(r.obj, p.obj)) && (r.act == p.act || p.act == "*") + """ + adapter = Adapter( + host=settings.REDIS_HOST, + port=settings.REDIS_PORT, + db=settings.CASBIN_REDIS_DATABASE, + password=settings.REDIS_PASSWORD, + ) + model = casbin.AsyncEnforcer.new_model(text=_CASBIN_RBAC_MODEL_CONF_TEXT) + enforcer = casbin.AsyncEnforcer(model, adapter) await enforcer.load_policy() return enforcer diff --git a/backend/app/core/conf.py b/backend/app/core/conf.py index e03f7d37..4ce3f94a 100644 --- a/backend/app/core/conf.py +++ b/backend/app/core/conf.py @@ -25,6 +25,9 @@ class Settings(BaseSettings): REDIS_PASSWORD: str REDIS_DATABASE: int + # Env Casbin + CASBIN_REDIS_DATABASE: int + # Env Celery CELERY_REDIS_HOST: str CELERY_REDIS_PORT: int @@ -124,7 +127,6 @@ def validate_openapi_url(cls, values): PERMISSION_MODE: Literal['casbin', 'role-menu'] = 'casbin' # Casbin Auth - CASBIN_RBAC_MODEL_NAME: str = 'rbac_model.conf' CASBIN_EXCLUDE: set[tuple[str, str]] = { ('POST', f'{API_V1_STR}/auth/swagger_login'), ('POST', f'{API_V1_STR}/auth/login'), diff --git a/backend/app/core/path_conf.py b/backend/app/core/path_conf.py index 0326e322..97cede49 100644 --- a/backend/app/core/path_conf.py +++ b/backend/app/core/path_conf.py @@ -4,8 +4,6 @@ from pathlib import Path -from backend.app.core.conf import settings - # 获取项目根目录 # 或使用绝对路径,指到backend目录为止,例如windows:BasePath = D:\git_project\fastapi_mysql\backend BasePath = Path(__file__).resolve().parent.parent.parent @@ -16,8 +14,5 @@ # 日志文件路径 LogPath = os.path.join(BasePath, 'app', 'log') -# RBAC model.conf 文件路径 -RBAC_MODEL_CONF = os.path.join(BasePath, 'app', 'core', settings.CASBIN_RBAC_MODEL_NAME) - # 离线 IP 数据库路径 IP2REGION_XDB = os.path.join(BasePath, 'app', 'static', 'ip2region.xdb') diff --git a/requirements.txt b/requirements.txt index 8b36e646..5f8e0274 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,14 +4,14 @@ alembic==1.13.0 asgiref==3.7.2 asyncmy==0.2.9 bcrypt==4.0.1 -casbin==1.33.0 -casbin-async-sqlalchemy-adapter==1.3.0 +casbin==1.34.0 +casbin_async_redis_adapter==1.0.0 celery==5.3.6 cryptography==41.0.7 email-validator==2.0.0 fast-captcha==0.2.1 fastapi==0.108.0 -fastapi-limiter==0.1.5 +fastapi-limiter==0.1.6 fastapi-pagination==0.12.13 gunicorn==21.2.0 httpx==0.25.2 @@ -30,7 +30,7 @@ pytest-pretty==1.2.0 python-jose==3.3.0 python-multipart==0.0.6 pytz==2023.3 -redis[hiredis]==4.5.5 +redis[hiredis]==5.0.1 ruff==0.1.8 SQLAlchemy==2.0.23 supervisor==4.2.5 From 8d5f9e8e182934ff812b74878da59fd232557c4e Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 5 Jan 2024 19:00:44 +0800 Subject: [PATCH 05/22] Delete casbin model conf file --- backend/app/core/rbac_model.conf | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 backend/app/core/rbac_model.conf diff --git a/backend/app/core/rbac_model.conf b/backend/app/core/rbac_model.conf deleted file mode 100644 index 43dbcfb6..00000000 --- a/backend/app/core/rbac_model.conf +++ /dev/null @@ -1,14 +0,0 @@ -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act - -[role_definition] -g = _, _ - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = g(r.sub, p.sub) && (keyMatch(r.obj, p.obj) || keyMatch3(r.obj, p.obj)) && (r.act == p.act || p.act == "*") From 3acc2eafe6fc4a7e7a99f3d72de9c36133f10662 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Fri, 5 Jan 2024 21:11:17 +0800 Subject: [PATCH 06/22] Add permission dependencies --- backend/app/common/permission.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 backend/app/common/permission.py diff --git a/backend/app/common/permission.py b/backend/app/common/permission.py new file mode 100644 index 00000000..4afaa7aa --- /dev/null +++ b/backend/app/common/permission.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from fastapi import Request + +from backend.app.common.exception.errors import ServerError + + +class RequestPermission: + def __init__(self, value: str): + self.value = value + + async def __call__(self, request: Request): + if not isinstance(self.value, str): + raise ServerError + # 附加权限标识 + request.state.permission = self.value From c3a31ee72fcbbac3c09b4d8878e62de696010c86 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sat, 6 Jan 2024 00:07:57 +0800 Subject: [PATCH 07/22] Add request permission depends on execution condition --- backend/app/common/permission.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/app/common/permission.py b/backend/app/common/permission.py index 4afaa7aa..27d385f3 100644 --- a/backend/app/common/permission.py +++ b/backend/app/common/permission.py @@ -3,6 +3,7 @@ from fastapi import Request from backend.app.common.exception.errors import ServerError +from backend.app.core.conf import settings class RequestPermission: @@ -10,7 +11,8 @@ def __init__(self, value: str): self.value = value async def __call__(self, request: Request): - if not isinstance(self.value, str): - raise ServerError - # 附加权限标识 - request.state.permission = self.value + if settings.PERMISSION_MODE == 'role-menu': + if not isinstance(self.value, str): + raise ServerError + # 附加权限标识 + request.state.permission = self.value From 97d85888dd2fa4e496692c3d512f474f57dccc11 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sat, 6 Jan 2024 00:43:17 +0800 Subject: [PATCH 08/22] Update openapi authorization method --- backend/app/api/v1/api.py | 18 ++++++------- backend/app/api/v1/auth/auth.py | 15 +++++++---- backend/app/api/v1/casbin.py | 38 ++++++++++++++-------------- backend/app/api/v1/dept.py | 16 ++++++------ backend/app/api/v1/dict_data.py | 16 ++++++------ backend/app/api/v1/dict_type.py | 14 +++++----- backend/app/api/v1/log/login_log.py | 12 ++++----- backend/app/api/v1/log/opera_log.py | 12 ++++----- backend/app/api/v1/menu.py | 18 ++++++------- backend/app/api/v1/mixed/config.py | 6 ++--- backend/app/api/v1/monitor/redis.py | 6 ++--- backend/app/api/v1/monitor/server.py | 6 ++--- backend/app/api/v1/role.py | 24 +++++++++--------- backend/app/api/v1/task.py | 12 ++++----- backend/app/api/v1/user.py | 32 +++++++++++------------ backend/app/common/jwt.py | 12 ++++----- backend/app/common/rbac.py | 6 ++--- 17 files changed, 132 insertions(+), 131 deletions(-) diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py index bbe88193..ef11bc3f 100644 --- a/backend/app/api/v1/api.py +++ b/backend/app/api/v1/api.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query +from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import PageDepends, paging_data -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.api import CreateApi, GetAllApi, UpdateApi @@ -15,19 +15,19 @@ router = APIRouter() -@router.get('/all', summary='获取所有接口', dependencies=[DependsJwtAuth]) +@router.get('/all', summary='获取所有接口', dependencies=[Depends(jwt_auth)]) async def get_all_apis(): data = await ApiService.get_all() return await response_base.success(data=data) -@router.get('/{pk}', summary='获取接口详情', dependencies=[DependsJwtAuth]) +@router.get('/{pk}', summary='获取接口详情', dependencies=[Depends(jwt_auth)]) async def get_api(pk: int): api = await ApiService.get(pk=pk) return await response_base.success(data=api) -@router.get('', summary='(模糊条件)分页获取所有接口', dependencies=[DependsJwtAuth, PageDepends]) +@router.get('', summary='(模糊条件)分页获取所有接口', dependencies=[Depends(jwt_auth), PageDepends]) async def get_api_list( db: CurrentSession, name: Annotated[str | None, Query()] = None, @@ -39,13 +39,13 @@ async def get_api_list( return await response_base.success(data=page_data) -@router.post('', summary='创建接口', dependencies=[DependsRBAC]) +@router.post('', summary='创建接口', dependencies=[Depends(RBAC.rbac_verify)]) async def create_api(obj: CreateApi): await ApiService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新接口', dependencies=[DependsRBAC]) +@router.put('/{pk}', summary='更新接口', dependencies=[Depends(RBAC.rbac_verify)]) async def update_api(pk: int, obj: UpdateApi): count = await ApiService.update(pk=pk, obj=obj) if count > 0: @@ -53,7 +53,7 @@ async def update_api(pk: int, obj: UpdateApi): return await response_base.fail() -@router.delete('', summary='(批量)删除接口', dependencies=[DependsRBAC]) +@router.delete('', summary='(批量)删除接口', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_api(pk: Annotated[list[int], Query(...)]): count = await ApiService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/auth/auth.py b/backend/app/api/v1/auth/auth.py index 050b688a..79cbfc75 100644 --- a/backend/app/api/v1/auth/auth.py +++ b/backend/app/api/v1/auth/auth.py @@ -7,7 +7,7 @@ from fastapi_limiter.depends import RateLimiter from starlette.background import BackgroundTasks -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.response.response_schema import response_base from backend.app.schemas.token import GetLoginToken, GetNewToken, GetSwaggerToken from backend.app.schemas.user import AuthLogin @@ -16,7 +16,12 @@ router = APIRouter() -@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口') +@router.post( + '/swagger_login', + summary='swagger 表单登录', + description='form 格式登录,用于 swagger 文档调试以及获取 JWT Auth', + deprecated=True, +) async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> GetSwaggerToken: token, user = await AuthService().swagger_login(form_data=form_data) return GetSwaggerToken(access_token=token, user=user) # type: ignore @@ -25,7 +30,7 @@ async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) - @router.post( '/login', summary='用户登录', - description='json 格式登录, 仅支持在第三方api工具调试接口, 例如: postman', + description='json 格式登录, 仅支持在第三方api工具调试, 例如: postman', dependencies=[Depends(RateLimiter(times=5, minutes=1))], ) async def user_login(request: Request, obj: AuthLogin, background_tasks: BackgroundTasks): @@ -42,7 +47,7 @@ async def user_login(request: Request, obj: AuthLogin, background_tasks: Backgro return await response_base.success(data=data) -@router.post('/new_token', summary='创建新 token', dependencies=[DependsJwtAuth]) +@router.post('/new_token', summary='创建新 token', dependencies=[Depends(jwt_auth)]) async def create_new_token(request: Request, refresh_token: Annotated[str, Query(...)]): ( new_access_token, @@ -59,7 +64,7 @@ async def create_new_token(request: Request, refresh_token: Annotated[str, Query return await response_base.success(data=data) -@router.post('/logout', summary='用户登出', dependencies=[DependsJwtAuth]) +@router.post('/logout', summary='用户登出', dependencies=[Depends(jwt_auth)]) async def user_logout(request: Request): await AuthService.logout(request=request) return await response_base.success() diff --git a/backend/app/api/v1/casbin.py b/backend/app/api/v1/casbin.py index 34cbb801..6619afd8 100644 --- a/backend/app/api/v1/casbin.py +++ b/backend/app/api/v1/casbin.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Path, Query +from fastapi import APIRouter, Depends, Path, Query -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import PageDepends, paging_data -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.casbin_rule import ( @@ -24,7 +24,7 @@ router = APIRouter() -@router.get('', summary='(模糊条件)分页获取所有权限规则', dependencies=[DependsJwtAuth, PageDepends]) +@router.get('', summary='(模糊条件)分页获取所有权限规则', dependencies=[Depends(jwt_auth), PageDepends]) async def get_all_casbin( db: CurrentSession, ptype: Annotated[str | None, Query()] = None, @@ -35,19 +35,19 @@ async def get_all_casbin( return await response_base.success(data=page_data) -@router.get('/policy', summary='获取所有P权限规则', dependencies=[DependsJwtAuth]) +@router.get('/policy', summary='获取所有P权限规则', dependencies=[Depends(jwt_auth)]) async def get_all_policies(): policies = await CasbinService.get_policy_list() return await response_base.success(data=policies) -@router.get('/policy/{role}/all', summary='获取指定角色的所有P权限规则', dependencies=[DependsJwtAuth]) +@router.get('/policy/{role}/all', summary='获取指定角色的所有P权限规则', dependencies=[Depends(jwt_auth)]) async def get_role_policies(role: Annotated[str, Path(description='角色ID')]): policies = await CasbinService.get_policy_list_by_role(role=role) return await response_base.success(data=policies) -@router.post('/policy', summary='添加P权限规则', dependencies=[DependsRBAC]) +@router.post('/policy', summary='添加P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def create_policy(p: CreatePolicy): """ p 规则: @@ -62,37 +62,37 @@ async def create_policy(p: CreatePolicy): return await response_base.success(data=data) -@router.post('/policies', summary='添加多组P权限规则', dependencies=[DependsRBAC]) +@router.post('/policies', summary='添加多组P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def create_policies(ps: list[CreatePolicy]): data = await CasbinService.create_policies(ps=ps) return await response_base.success(data=data) -@router.put('/policy', summary='更新P权限规则', dependencies=[DependsRBAC]) +@router.put('/policy', summary='更新P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def update_policy(old: UpdatePolicy, new: UpdatePolicy): data = await CasbinService.update_policy(old=old, new=new) return await response_base.success(data=data) -@router.put('/policies', summary='更新多组P权限规则', dependencies=[DependsRBAC]) +@router.put('/policies', summary='更新多组P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def update_policies(old: list[UpdatePolicy], new: list[UpdatePolicy]): data = await CasbinService.update_policies(old=old, new=new) return await response_base.success(data=data) -@router.delete('/policy', summary='删除P权限规则', dependencies=[DependsRBAC]) +@router.delete('/policy', summary='删除P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_policy(p: DeletePolicy): data = await CasbinService.delete_policy(p=p) return await response_base.success(data=data) -@router.delete('/policies', summary='删除多组P权限规则', dependencies=[DependsRBAC]) +@router.delete('/policies', summary='删除多组P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_policies(ps: list[DeletePolicy]): data = await CasbinService.delete_policies(ps=ps) return await response_base.success(data=data) -@router.delete('/policies/all', summary='删除所有P权限规则', dependencies=[DependsRBAC]) +@router.delete('/policies/all', summary='删除所有P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_all_policies(sub: DeleteAllPolicies): count = await CasbinService.delete_all_policies(sub=sub) if count > 0: @@ -100,13 +100,13 @@ async def delete_all_policies(sub: DeleteAllPolicies): return await response_base.fail() -@router.get('/group', summary='获取所有G权限规则', dependencies=[DependsJwtAuth]) +@router.get('/group', summary='获取所有G权限规则', dependencies=[Depends(jwt_auth)]) async def get_all_groups(): data = await CasbinService.get_group_list() return await response_base.success(data=data) -@router.post('/group', summary='添加G权限规则', dependencies=[DependsRBAC]) +@router.post('/group', summary='添加G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def create_group(g: CreateUserRole): """ g 规则 (**依赖 p 规则**): @@ -121,25 +121,25 @@ async def create_group(g: CreateUserRole): return await response_base.success(data=data) -@router.post('/groups', summary='添加多组G权限规则', dependencies=[DependsRBAC]) +@router.post('/groups', summary='添加多组G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def create_groups(gs: list[CreateUserRole]): data = await CasbinService.create_groups(gs=gs) return await response_base.success(data=data) -@router.delete('/group', summary='删除G权限规则', dependencies=[DependsRBAC]) +@router.delete('/group', summary='删除G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_group(g: DeleteUserRole): data = await CasbinService.delete_group(g=g) return await response_base.success(data=data) -@router.delete('/groups', summary='删除多组G权限规则', dependencies=[DependsRBAC]) +@router.delete('/groups', summary='删除多组G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_groups(gs: list[DeleteUserRole]): data = await CasbinService.delete_groups(gs=gs) return await response_base.success(data=data) -@router.delete('/groups/all', summary='删除所有G权限规则', dependencies=[DependsRBAC]) +@router.delete('/groups/all', summary='删除所有G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_all_groups(uuid: DeleteAllUserRoles): count = await CasbinService.delete_all_groups(uuid=uuid) if count > 0: diff --git a/backend/app/api/v1/dept.py b/backend/app/api/v1/dept.py index 94cab232..6c3327ac 100644 --- a/backend/app/api/v1/dept.py +++ b/backend/app/api/v1/dept.py @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query +from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import DependsJwtAuth -from backend.app.common.rbac import DependsRBAC +from backend.app.common.jwt import jwt_auth +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.schemas.dept import CreateDept, GetAllDept, UpdateDept from backend.app.services.dept_service import DeptService @@ -14,14 +14,14 @@ router = APIRouter() -@router.get('/{pk}', summary='获取部门详情', dependencies=[DependsJwtAuth]) +@router.get('/{pk}', summary='获取部门详情', dependencies=[Depends(jwt_auth)]) async def get_dept(pk: int): dept = await DeptService.get(pk=pk) data = GetAllDept(**await select_as_dict(dept)) return await response_base.success(data=data) -@router.get('', summary='获取所有部门展示树', dependencies=[DependsJwtAuth]) +@router.get('', summary='获取所有部门展示树', dependencies=[Depends(jwt_auth)]) async def get_all_depts( name: Annotated[str | None, Query()] = None, leader: Annotated[str | None, Query()] = None, @@ -32,13 +32,13 @@ async def get_all_depts( return await response_base.success(data=dept) -@router.post('', summary='创建部门', dependencies=[DependsRBAC]) +@router.post('', summary='创建部门', dependencies=[Depends(RBAC.rbac_verify)]) async def create_dept(obj: CreateDept): await DeptService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新部门', dependencies=[DependsRBAC]) +@router.put('/{pk}', summary='更新部门', dependencies=[Depends(RBAC.rbac_verify)]) async def update_dept(pk: int, obj: UpdateDept): count = await DeptService.update(pk=pk, obj=obj) if count > 0: @@ -46,7 +46,7 @@ async def update_dept(pk: int, obj: UpdateDept): return await response_base.fail() -@router.delete('{pk}', summary='删除部门', dependencies=[DependsRBAC]) +@router.delete('{pk}', summary='删除部门', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_dept(pk: int): count = await DeptService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/dict_data.py b/backend/app/api/v1/dict_data.py index 6337c9d0..37ecf4fd 100644 --- a/backend/app/api/v1/dict_data.py +++ b/backend/app/api/v1/dict_data.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query +from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import PageDepends, paging_data -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.dict_data import CreateDictData, GetAllDictData, UpdateDictData @@ -16,14 +16,14 @@ router = APIRouter() -@router.get('/{pk}', summary='获取字典详情', dependencies=[DependsJwtAuth]) +@router.get('/{pk}', summary='获取字典详情', dependencies=[Depends(jwt_auth)]) async def get_dict_data(pk: int): dict_data = await DictDataService.get(pk=pk) data = GetAllDictData(**await select_as_dict(dict_data)) return await response_base.success(data=data) -@router.get('', summary='(模糊条件)分页获取所有字典', dependencies=[DependsJwtAuth, PageDepends]) +@router.get('', summary='(模糊条件)分页获取所有字典', dependencies=[Depends(jwt_auth), PageDepends]) async def get_all_dict_datas( db: CurrentSession, label: Annotated[str | None, Query()] = None, @@ -35,13 +35,13 @@ async def get_all_dict_datas( return await response_base.success(data=page_data) -@router.post('', summary='创建字典', dependencies=[DependsRBAC]) +@router.post('', summary='创建字典', dependencies=[Depends(RBAC.rbac_verify)]) async def create_dict_data(obj: CreateDictData): await DictDataService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新字典', dependencies=[DependsRBAC]) +@router.put('/{pk}', summary='更新字典', dependencies=[Depends(RBAC.rbac_verify)]) async def update_dict_data(pk: int, obj: UpdateDictData): count = await DictDataService.update(pk=pk, obj=obj) if count > 0: @@ -49,7 +49,7 @@ async def update_dict_data(pk: int, obj: UpdateDictData): return await response_base.fail() -@router.delete('', summary='(批量)删除字典', dependencies=[DependsRBAC]) +@router.delete('', summary='(批量)删除字典', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_dict_data(pk: Annotated[list[int], Query(...)]): count = await DictDataService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/dict_type.py b/backend/app/api/v1/dict_type.py index 4d141977..3e57fb52 100644 --- a/backend/app/api/v1/dict_type.py +++ b/backend/app/api/v1/dict_type.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query +from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import PageDepends, paging_data -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.dict_type import CreateDictType, GetAllDictType, UpdateDictType @@ -15,7 +15,7 @@ router = APIRouter() -@router.get('', summary='(模糊条件)分页获取所有字典类型', dependencies=[DependsJwtAuth, PageDepends]) +@router.get('', summary='(模糊条件)分页获取所有字典类型', dependencies=[Depends(jwt_auth), PageDepends]) async def get_all_dict_types( db: CurrentSession, name: Annotated[str | None, Query()] = None, @@ -27,13 +27,13 @@ async def get_all_dict_types( return await response_base.success(data=page_data) -@router.post('', summary='创建字典类型', dependencies=[DependsRBAC]) +@router.post('', summary='创建字典类型', dependencies=[Depends(RBAC.rbac_verify)]) async def create_dict_type(obj: CreateDictType): await DictTypeService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新字典类型', dependencies=[DependsRBAC]) +@router.put('/{pk}', summary='更新字典类型', dependencies=[Depends(RBAC.rbac_verify)]) async def update_dict_type(pk: int, obj: UpdateDictType): count = await DictTypeService.update(pk=pk, obj=obj) if count > 0: @@ -41,7 +41,7 @@ async def update_dict_type(pk: int, obj: UpdateDictType): return await response_base.fail() -@router.delete('', summary='(批量)删除字典类型', dependencies=[DependsRBAC]) +@router.delete('', summary='(批量)删除字典类型', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_dict_type(pk: Annotated[list[int], Query(...)]): count = await DictTypeService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/log/login_log.py b/backend/app/api/v1/log/login_log.py index dac8e77b..05d2247c 100644 --- a/backend/app/api/v1/log/login_log.py +++ b/backend/app/api/v1/log/login_log.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query +from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import PageDepends, paging_data -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.login_log import GetAllLoginLog @@ -15,7 +15,7 @@ router = APIRouter() -@router.get('', summary='(模糊条件)分页获取登录日志', dependencies=[DependsJwtAuth, PageDepends]) +@router.get('', summary='(模糊条件)分页获取登录日志', dependencies=[Depends(jwt_auth), PageDepends]) async def get_all_login_logs( db: CurrentSession, username: Annotated[str | None, Query()] = None, @@ -27,7 +27,7 @@ async def get_all_login_logs( return await response_base.success(data=page_data) -@router.delete('', summary='(批量)删除登录日志', dependencies=[DependsRBAC]) +@router.delete('', summary='(批量)删除登录日志', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_login_log(pk: Annotated[list[int], Query(...)]): count = await LoginLogService.delete(pk=pk) if count > 0: @@ -35,7 +35,7 @@ async def delete_login_log(pk: Annotated[list[int], Query(...)]): return await response_base.fail() -@router.delete('/all', summary='清空登录日志', dependencies=[DependsRBAC]) +@router.delete('/all', summary='清空登录日志', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_all_login_logs(): count = await LoginLogService.delete_all() if count > 0: diff --git a/backend/app/api/v1/log/opera_log.py b/backend/app/api/v1/log/opera_log.py index 1170769f..5b372e84 100644 --- a/backend/app/api/v1/log/opera_log.py +++ b/backend/app/api/v1/log/opera_log.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query +from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import PageDepends, paging_data -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.opera_log import GetAllOperaLog @@ -15,7 +15,7 @@ router = APIRouter() -@router.get('', summary='(模糊条件)分页获取操作日志', dependencies=[DependsJwtAuth, PageDepends]) +@router.get('', summary='(模糊条件)分页获取操作日志', dependencies=[Depends(jwt_auth), PageDepends]) async def get_all_opera_logs( db: CurrentSession, username: Annotated[str | None, Query()] = None, @@ -27,7 +27,7 @@ async def get_all_opera_logs( return await response_base.success(data=page_data) -@router.delete('', summary='(批量)删除操作日志', dependencies=[DependsRBAC]) +@router.delete('', summary='(批量)删除操作日志', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_opera_log(pk: Annotated[list[int], Query(...)]): count = await OperaLogService.delete(pk=pk) if count > 0: @@ -35,7 +35,7 @@ async def delete_opera_log(pk: Annotated[list[int], Query(...)]): return await response_base.fail() -@router.delete('/all', summary='清空操作日志', dependencies=[DependsRBAC]) +@router.delete('/all', summary='清空操作日志', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_all_opera_logs(): count = await OperaLogService.delete_all() if count > 0: diff --git a/backend/app/api/v1/menu.py b/backend/app/api/v1/menu.py index f06ffd28..41e257e5 100644 --- a/backend/app/api/v1/menu.py +++ b/backend/app/api/v1/menu.py @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query, Request +from fastapi import APIRouter, Depends, Query, Request -from backend.app.common.jwt import DependsJwtAuth -from backend.app.common.rbac import DependsRBAC +from backend.app.common.jwt import jwt_auth +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.schemas.menu import CreateMenu, GetAllMenu, UpdateMenu from backend.app.services.menu_service import MenuService @@ -14,20 +14,20 @@ router = APIRouter() -@router.get('/sidebar', summary='获取用户菜单展示树', dependencies=[DependsJwtAuth]) +@router.get('/sidebar', summary='获取用户菜单展示树', dependencies=[Depends(jwt_auth)]) async def get_user_menus(request: Request): menu = await MenuService.get_user_menu_tree(request=request) return await response_base.success(data=menu) -@router.get('/{pk}', summary='获取菜单详情', dependencies=[DependsJwtAuth]) +@router.get('/{pk}', summary='获取菜单详情', dependencies=[Depends(jwt_auth)]) async def get_menu(pk: int): menu = await MenuService.get(pk=pk) data = GetAllMenu(**await select_as_dict(menu)) return await response_base.success(data=data) -@router.get('', summary='获取所有菜单展示树', dependencies=[DependsJwtAuth]) +@router.get('', summary='获取所有菜单展示树', dependencies=[Depends(jwt_auth)]) async def get_all_menus( title: Annotated[str | None, Query()] = None, status: Annotated[int | None, Query()] = None, @@ -36,13 +36,13 @@ async def get_all_menus( return await response_base.success(data=menu) -@router.post('', summary='创建菜单', dependencies=[DependsRBAC]) +@router.post('', summary='创建菜单', dependencies=[Depends(RBAC.rbac_verify)]) async def create_menu(obj: CreateMenu): await MenuService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新菜单', dependencies=[DependsRBAC]) +@router.put('/{pk}', summary='更新菜单', dependencies=[Depends(RBAC.rbac_verify)]) async def update_menu(pk: int, obj: UpdateMenu): count = await MenuService.update(pk=pk, obj=obj) if count > 0: @@ -50,7 +50,7 @@ async def update_menu(pk: int, obj: UpdateMenu): return await response_base.fail() -@router.delete('/{pk}', summary='删除菜单', dependencies=[DependsRBAC]) +@router.delete('/{pk}', summary='删除菜单', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_menu(pk: int): count = await MenuService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/mixed/config.py b/backend/app/api/v1/mixed/config.py index bdc7dec1..0ea2aac4 100644 --- a/backend/app/api/v1/mixed/config.py +++ b/backend/app/api/v1/mixed/config.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from fastapi import APIRouter, Request +from fastapi import APIRouter, Depends, Request from fastapi.routing import APIRoute -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base router = APIRouter() -@router.get('/routers', summary='获取所有路由', dependencies=[DependsRBAC]) +@router.get('/routers', summary='获取所有路由', dependencies=[Depends(RBAC.rbac_verify)]) async def get_all_route(request: Request): data = [] for route in request.app.routes: diff --git a/backend/app/api/v1/monitor/redis.py b/backend/app/api/v1/monitor/redis.py index 3719fd91..b553caed 100644 --- a/backend/app/api/v1/monitor/redis.py +++ b/backend/app/api/v1/monitor/redis.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from fastapi import APIRouter +from fastapi import APIRouter, Depends -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.response.response_schema import response_base from backend.app.utils.redis_info import redis_info router = APIRouter() -@router.get('/redis', summary='redis 监控', dependencies=[DependsJwtAuth]) +@router.get('/redis', summary='redis 监控', dependencies=[Depends(jwt_auth)]) async def get_redis_info(): data = {'info': await redis_info.get_info(), 'stats': await redis_info.get_stats()} return await response_base.success(data=data) diff --git a/backend/app/api/v1/monitor/server.py b/backend/app/api/v1/monitor/server.py index 9e06917b..dd31aa59 100644 --- a/backend/app/api/v1/monitor/server.py +++ b/backend/app/api/v1/monitor/server.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from fastapi import APIRouter +from fastapi import APIRouter, Depends from starlette.concurrency import run_in_threadpool -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.response.response_schema import response_base from backend.app.utils.server_info import server_info router = APIRouter() -@router.get('/server', summary='server 监控', dependencies=[DependsJwtAuth]) +@router.get('/server', summary='server 监控', dependencies=[Depends(jwt_auth)]) async def get_server_info(): """IO密集型任务,使用线程池尽量减少性能损耗""" data = { diff --git a/backend/app/api/v1/role.py b/backend/app/api/v1/role.py index 1e9a44b9..a1228b81 100644 --- a/backend/app/api/v1/role.py +++ b/backend/app/api/v1/role.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query +from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import PageDepends, paging_data -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.role import CreateRole, GetAllRole, UpdateRole, UpdateRoleMenu @@ -17,34 +17,34 @@ router = APIRouter() -@router.get('/all', summary='获取所有角色', dependencies=[DependsJwtAuth]) +@router.get('/all', summary='获取所有角色', dependencies=[Depends(jwt_auth)]) async def get_all_roles(): roles = await RoleService.get_all() data = await select_list_serialize(roles) return await response_base.success(data=data) -@router.get('/{pk}/all', summary='获取用户所有角色', dependencies=[DependsJwtAuth]) +@router.get('/{pk}/all', summary='获取用户所有角色', dependencies=[Depends(jwt_auth)]) async def get_user_all_roles(pk: int): roles = await RoleService.get_user_all(pk=pk) data = await select_list_serialize(roles) return await response_base.success(data=data) -@router.get('/{pk}/menus', summary='获取角色所有菜单', dependencies=[DependsJwtAuth]) +@router.get('/{pk}/menus', summary='获取角色所有菜单', dependencies=[Depends(jwt_auth)]) async def get_role_all_menus(pk: int): menu = await MenuService.get_role_menu_tree(pk=pk) return await response_base.success(data=menu) -@router.get('/{pk}', summary='获取角色详情', dependencies=[DependsJwtAuth]) +@router.get('/{pk}', summary='获取角色详情', dependencies=[Depends(jwt_auth)]) async def get_role(pk: int): role = await RoleService.get(pk=pk) data = GetAllRole(**await select_as_dict(role)) return await response_base.success(data=data) -@router.get('', summary='(模糊条件)分页获取所有角色', dependencies=[DependsJwtAuth, PageDepends]) +@router.get('', summary='(模糊条件)分页获取所有角色', dependencies=[Depends(jwt_auth), PageDepends]) async def get_all_role_list( db: CurrentSession, name: Annotated[str | None, Query()] = None, @@ -56,13 +56,13 @@ async def get_all_role_list( return await response_base.success(data=page_data) -@router.post('', summary='创建角色', dependencies=[DependsRBAC]) +@router.post('', summary='创建角色', dependencies=[Depends(RBAC.rbac_verify)]) async def create_role(obj: CreateRole): await RoleService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新角色', dependencies=[DependsRBAC]) +@router.put('/{pk}', summary='更新角色', dependencies=[Depends(RBAC.rbac_verify)]) async def update_role(pk: int, obj: UpdateRole): count = await RoleService.update(pk=pk, obj=obj) if count > 0: @@ -70,7 +70,7 @@ async def update_role(pk: int, obj: UpdateRole): return await response_base.fail() -@router.put('/{pk}/menu', summary='更新角色菜单', dependencies=[DependsRBAC]) +@router.put('/{pk}/menu', summary='更新角色菜单', dependencies=[Depends(RBAC.rbac_verify)]) async def update_role_menu(pk: int, menu_ids: UpdateRoleMenu): count = await RoleService.update_menus(pk=pk, menu_ids=menu_ids) if count > 0: @@ -78,7 +78,7 @@ async def update_role_menu(pk: int, menu_ids: UpdateRoleMenu): return await response_base.fail() -@router.delete('', summary='(批量)删除角色', dependencies=[DependsRBAC]) +@router.delete('', summary='(批量)删除角色', dependencies=[Depends(RBAC.rbac_verify)]) async def delete_role(pk: Annotated[list[int], Query(...)]): count = await RoleService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/task.py b/backend/app/api/v1/task.py index 34cd8d36..68c4edad 100644 --- a/backend/app/api/v1/task.py +++ b/backend/app/api/v1/task.py @@ -2,10 +2,10 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Body, Path +from fastapi import APIRouter, Body, Depends, Path -from backend.app.common.jwt import DependsJwtAuth -from backend.app.common.rbac import DependsRBAC +from backend.app.common.jwt import jwt_auth +from backend.app.common.rbac import RBAC from backend.app.common.response.response_code import CustomResponseCode from backend.app.common.response.response_schema import response_base from backend.app.services.task_service import TaskService @@ -13,13 +13,13 @@ router = APIRouter() -@router.get('', summary='获取所有可执行任务模块', dependencies=[DependsJwtAuth]) +@router.get('', summary='获取所有可执行任务模块', dependencies=[Depends(jwt_auth)]) async def get_all_tasks(): tasks = TaskService.gets() return await response_base.success(data=tasks) -@router.get('/{pk}', summary='获取任务结果', dependencies=[DependsJwtAuth]) +@router.get('/{pk}', summary='获取任务结果', dependencies=[Depends(jwt_auth)]) async def get_task_result(pk: str = Path(description='任务ID')): task = TaskService.get(pk) if not task: @@ -27,7 +27,7 @@ async def get_task_result(pk: str = Path(description='任务ID')): return await response_base.success(data=task.result) -@router.post('/{module}', summary='执行任务', dependencies=[DependsRBAC]) +@router.post('/{module}', summary='执行任务', dependencies=[Depends(RBAC.rbac_verify)]) async def run_task( module: Annotated[str, Path(description='任务模块')], args: Annotated[list | None, Body()] = None, diff --git a/backend/app/api/v1/user.py b/backend/app/api/v1/user.py index 30c63309..6a477a75 100644 --- a/backend/app/api/v1/user.py +++ b/backend/app/api/v1/user.py @@ -2,11 +2,11 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Query, Request +from fastapi import APIRouter, Depends, Query, Request -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import PageDepends, paging_data -from backend.app.common.rbac import DependsRBAC +from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.user import ( @@ -31,7 +31,7 @@ async def user_register(obj: RegisterUser): return await response_base.success() -@router.post('/add', summary='添加用户', dependencies=[DependsRBAC]) +@router.post('/add', summary='添加用户', dependencies=[Depends(RBAC.rbac_verify)]) async def add_user(obj: AddUser): await UserService.add(obj=obj) current_user = await UserService.get_userinfo(username=obj.username) @@ -39,7 +39,7 @@ async def add_user(obj: AddUser): return await response_base.success(data=data) -@router.post('/password/reset', summary='密码重置', dependencies=[DependsJwtAuth]) +@router.post('/password/reset', summary='密码重置', dependencies=[Depends(jwt_auth)]) async def password_reset(request: Request, obj: ResetPassword): count = await UserService.pwd_reset(request=request, obj=obj) if count > 0: @@ -47,20 +47,20 @@ async def password_reset(request: Request, obj: ResetPassword): return await response_base.fail() -@router.get('/me', summary='获取当前用户信息', dependencies=[DependsJwtAuth]) +@router.get('/me', summary='获取当前用户信息', dependencies=[Depends(jwt_auth)]) async def get_current_userinfo(request: Request): data = GetCurrentUserInfo(**await select_as_dict(request.user)) return await response_base.success(data=data, exclude={'password'}) -@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtAuth]) +@router.get('/{username}', summary='查看用户信息', dependencies=[Depends(jwt_auth)]) async def get_user(username: str): current_user = await UserService.get_userinfo(username=username) data = GetAllUserInfo(**await select_as_dict(current_user)) return await response_base.success(data=data) -@router.put('/{username}', summary='更新用户信息', dependencies=[DependsJwtAuth]) +@router.put('/{username}', summary='更新用户信息', dependencies=[Depends(jwt_auth)]) async def update_userinfo(request: Request, username: str, obj: UpdateUser): count = await UserService.update(request=request, username=username, obj=obj) if count > 0: @@ -68,13 +68,13 @@ async def update_userinfo(request: Request, username: str, obj: UpdateUser): return await response_base.fail() -@router.put('/{username}/role', summary='更新用户角色', dependencies=[DependsRBAC]) +@router.put('/{username}/role', summary='更新用户角色', dependencies=[Depends(RBAC.rbac_verify)]) async def update_user_role(request: Request, username: str, obj: UpdateUserRole): await UserService.update_role(request=request, username=username, obj=obj) return await response_base.success() -@router.put('/{username}/avatar', summary='更新头像', dependencies=[DependsJwtAuth]) +@router.put('/{username}/avatar', summary='更新头像', dependencies=[Depends(jwt_auth)]) async def update_avatar(request: Request, username: str, avatar: Avatar): count = await UserService.update_avatar(request=request, username=username, avatar=avatar) if count > 0: @@ -82,7 +82,7 @@ async def update_avatar(request: Request, username: str, avatar: Avatar): return await response_base.fail() -@router.get('', summary='(模糊条件)分页获取所有用户', dependencies=[DependsJwtAuth, PageDepends]) +@router.get('', summary='(模糊条件)分页获取所有用户', dependencies=[Depends(jwt_auth), PageDepends]) async def get_all_users( db: CurrentSession, dept: Annotated[int | None, Query()] = None, @@ -95,7 +95,7 @@ async def get_all_users( return await response_base.success(data=page_data) -@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[DependsRBAC]) +@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[Depends(RBAC.rbac_verify)]) async def super_set(request: Request, pk: int): count = await UserService.update_permission(request=request, pk=pk) if count > 0: @@ -103,7 +103,7 @@ async def super_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/staff', summary='修改用户后台登录权限', dependencies=[DependsRBAC]) +@router.put('/{pk}/staff', summary='修改用户后台登录权限', dependencies=[Depends(RBAC.rbac_verify)]) async def staff_set(request: Request, pk: int): count = await UserService.update_staff(request=request, pk=pk) if count > 0: @@ -111,7 +111,7 @@ async def staff_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/status', summary='修改用户状态', dependencies=[DependsRBAC]) +@router.put('/{pk}/status', summary='修改用户状态', dependencies=[Depends(RBAC.rbac_verify)]) async def status_set(request: Request, pk: int): count = await UserService.update_status(request=request, pk=pk) if count > 0: @@ -119,7 +119,7 @@ async def status_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/multi', summary='修改用户多点登录状态', dependencies=[DependsRBAC]) +@router.put('/{pk}/multi', summary='修改用户多点登录状态', dependencies=[Depends(RBAC.rbac_verify)]) async def multi_set(request: Request, pk: int): count = await UserService.update_multi_login(request=request, pk=pk) if count > 0: @@ -131,7 +131,7 @@ async def multi_set(request: Request, pk: int): path='/{username}', summary='用户注销', description='用户注销 != 用户登出,注销之后用户将从数据库删除', - dependencies=[DependsRBAC], + dependencies=[Depends(RBAC.rbac_verify)], ) async def delete_user(request: Request, username: str): count = await UserService.delete(request=request, username=username) diff --git a/backend/app/common/jwt.py b/backend/app/common/jwt.py index fd13297d..f820e257 100644 --- a/backend/app/common/jwt.py +++ b/backend/app/common/jwt.py @@ -3,8 +3,8 @@ from datetime import datetime, timedelta from asgiref.sync import sync_to_async -from fastapi import Depends, Request -from fastapi.security import OAuth2PasswordBearer +from fastapi import Request +from fastapi.security import HTTPBearer, OAuth2PasswordBearer from fastapi.security.utils import get_authorization_scheme_param from jose import jwt from passlib.context import CryptContext @@ -19,8 +19,11 @@ pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto') +# Deprecated, may be enabled when oauth2 is actually integrated oauth2_schema = OAuth2PasswordBearer(tokenUrl=settings.TOKEN_URL_SWAGGER) +jwt_auth = HTTPBearer() + @sync_to_async def get_hash_password(password: str) -> str: @@ -209,8 +212,3 @@ def superuser_verify(request: Request) -> bool: if not request.user.is_staff: raise AuthorizationError(msg='此管理员已被禁止后台管理操作') return is_superuser - - -# JWT authorizes dependency injection, which can be used if the interface only -# needs to provide a token instead of RBAC permission control -DependsJwtAuth = Depends(oauth2_schema) diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index 1f959dad..9300ea1e 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -7,7 +7,7 @@ from backend.app.common.enums import StatusType from backend.app.common.exception.errors import AuthorizationError, TokenError -from backend.app.common.jwt import DependsJwtAuth +from backend.app.common.jwt import jwt_auth from backend.app.core.conf import settings @@ -47,7 +47,7 @@ async def enforcer() -> casbin.AsyncEnforcer: await enforcer.load_policy() return enforcer - async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> None: + async def rbac_verify(self, request: Request, _token: str = Depends(jwt_auth)) -> None: """ RBAC 权限校验 @@ -118,5 +118,3 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N RBAC = RBAC() -# RBAC 授权依赖注入 -DependsRBAC = Depends(RBAC.rbac_verify) From 880172799f50a828d5255ee13975bee07beef9ad Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sat, 6 Jan 2024 01:21:29 +0800 Subject: [PATCH 09/22] Add request permission identity --- backend/app/api/v1/api.py | 39 +++++++-- backend/app/api/v1/casbin.py | 120 ++++++++++++++++++++++++---- backend/app/api/v1/dept.py | 28 ++++++- backend/app/api/v1/dict_data.py | 39 +++++++-- backend/app/api/v1/dict_type.py | 39 +++++++-- backend/app/api/v1/log/login_log.py | 30 ++++++- backend/app/api/v1/log/opera_log.py | 30 ++++++- backend/app/api/v1/menu.py | 28 ++++++- backend/app/api/v1/mixed/config.py | 10 ++- backend/app/api/v1/role.py | 48 +++++++++-- backend/app/api/v1/task.py | 10 ++- backend/app/api/v1/user.py | 71 +++++++++++++--- backend/app/common/pagination.py | 2 +- 13 files changed, 433 insertions(+), 61 deletions(-) diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py index ef11bc3f..548d8b5f 100644 --- a/backend/app/api/v1/api.py +++ b/backend/app/api/v1/api.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, Query from backend.app.common.jwt import jwt_auth -from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.pagination import DependsPagination, paging_data +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession @@ -27,7 +28,14 @@ async def get_api(pk: int): return await response_base.success(data=api) -@router.get('', summary='(模糊条件)分页获取所有接口', dependencies=[Depends(jwt_auth), PageDepends]) +@router.get( + '', + summary='(模糊条件)分页获取所有接口', + dependencies=[ + Depends(jwt_auth), + DependsPagination, + ], +) async def get_api_list( db: CurrentSession, name: Annotated[str | None, Query()] = None, @@ -39,13 +47,27 @@ async def get_api_list( return await response_base.success(data=page_data) -@router.post('', summary='创建接口', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '', + summary='创建接口', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:api:add')), + ], +) async def create_api(obj: CreateApi): await ApiService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新接口', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}', + summary='更新接口', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:api:edit')), + ], +) async def update_api(pk: int, obj: UpdateApi): count = await ApiService.update(pk=pk, obj=obj) if count > 0: @@ -53,7 +75,14 @@ async def update_api(pk: int, obj: UpdateApi): return await response_base.fail() -@router.delete('', summary='(批量)删除接口', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '', + summary='(批量)删除接口', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:api:del')), + ], +) async def delete_api(pk: Annotated[list[int], Query(...)]): count = await ApiService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/casbin.py b/backend/app/api/v1/casbin.py index 6619afd8..8f8762ca 100644 --- a/backend/app/api/v1/casbin.py +++ b/backend/app/api/v1/casbin.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, Path, Query from backend.app.common.jwt import jwt_auth -from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.pagination import DependsPagination, paging_data +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession @@ -24,7 +25,14 @@ router = APIRouter() -@router.get('', summary='(模糊条件)分页获取所有权限规则', dependencies=[Depends(jwt_auth), PageDepends]) +@router.get( + '', + summary='(模糊条件)分页获取所有权限规则', + dependencies=[ + Depends(jwt_auth), + DependsPagination, + ], +) async def get_all_casbin( db: CurrentSession, ptype: Annotated[str | None, Query()] = None, @@ -47,7 +55,14 @@ async def get_role_policies(role: Annotated[str, Path(description='角色ID')]): return await response_base.success(data=policies) -@router.post('/policy', summary='添加P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '/policy', + summary='添加P权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:p:add')), + ], +) async def create_policy(p: CreatePolicy): """ p 规则: @@ -62,37 +77,79 @@ async def create_policy(p: CreatePolicy): return await response_base.success(data=data) -@router.post('/policies', summary='添加多组P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '/policies', + summary='添加多组P权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:p:group:add')), + ], +) async def create_policies(ps: list[CreatePolicy]): data = await CasbinService.create_policies(ps=ps) return await response_base.success(data=data) -@router.put('/policy', summary='更新P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/policy', + summary='更新P权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:p:edit')), + ], +) async def update_policy(old: UpdatePolicy, new: UpdatePolicy): data = await CasbinService.update_policy(old=old, new=new) return await response_base.success(data=data) -@router.put('/policies', summary='更新多组P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/policies', + summary='更新多组P权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:p:group:edit')), + ], +) async def update_policies(old: list[UpdatePolicy], new: list[UpdatePolicy]): data = await CasbinService.update_policies(old=old, new=new) return await response_base.success(data=data) -@router.delete('/policy', summary='删除P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/policy', + summary='删除P权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:p:del')), + ], +) async def delete_policy(p: DeletePolicy): data = await CasbinService.delete_policy(p=p) return await response_base.success(data=data) -@router.delete('/policies', summary='删除多组P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/policies', + summary='删除多组P权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:p:group:del')), + ], +) async def delete_policies(ps: list[DeletePolicy]): data = await CasbinService.delete_policies(ps=ps) return await response_base.success(data=data) -@router.delete('/policies/all', summary='删除所有P权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/policies/all', + summary='删除所有P权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:p:empty')), + ], +) async def delete_all_policies(sub: DeleteAllPolicies): count = await CasbinService.delete_all_policies(sub=sub) if count > 0: @@ -106,7 +163,14 @@ async def get_all_groups(): return await response_base.success(data=data) -@router.post('/group', summary='添加G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '/group', + summary='添加G权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:g:add')), + ], +) async def create_group(g: CreateUserRole): """ g 规则 (**依赖 p 规则**): @@ -121,25 +185,53 @@ async def create_group(g: CreateUserRole): return await response_base.success(data=data) -@router.post('/groups', summary='添加多组G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '/groups', + summary='添加多组G权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:g:group:add')), + ], +) async def create_groups(gs: list[CreateUserRole]): data = await CasbinService.create_groups(gs=gs) return await response_base.success(data=data) -@router.delete('/group', summary='删除G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/group', + summary='删除G权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:g:del')), + ], +) async def delete_group(g: DeleteUserRole): data = await CasbinService.delete_group(g=g) return await response_base.success(data=data) -@router.delete('/groups', summary='删除多组G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/groups', + summary='删除多组G权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:g:group:del')), + ], +) async def delete_groups(gs: list[DeleteUserRole]): data = await CasbinService.delete_groups(gs=gs) return await response_base.success(data=data) -@router.delete('/groups/all', summary='删除所有G权限规则', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/groups/all', + summary='删除所有G权限规则', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('casbin:g:empty')), + ], +) async def delete_all_groups(uuid: DeleteAllUserRoles): count = await CasbinService.delete_all_groups(uuid=uuid) if count > 0: diff --git a/backend/app/api/v1/dept.py b/backend/app/api/v1/dept.py index 6c3327ac..1b99c693 100644 --- a/backend/app/api/v1/dept.py +++ b/backend/app/api/v1/dept.py @@ -5,6 +5,7 @@ from fastapi import APIRouter, Depends, Query from backend.app.common.jwt import jwt_auth +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.schemas.dept import CreateDept, GetAllDept, UpdateDept @@ -32,13 +33,27 @@ async def get_all_depts( return await response_base.success(data=dept) -@router.post('', summary='创建部门', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '', + summary='创建部门', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dept:add')), + ], +) async def create_dept(obj: CreateDept): await DeptService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新部门', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}', + summary='更新部门', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dept:edit')), + ], +) async def update_dept(pk: int, obj: UpdateDept): count = await DeptService.update(pk=pk, obj=obj) if count > 0: @@ -46,7 +61,14 @@ async def update_dept(pk: int, obj: UpdateDept): return await response_base.fail() -@router.delete('{pk}', summary='删除部门', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '{pk}', + summary='删除部门', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dept:del')), + ], +) async def delete_dept(pk: int): count = await DeptService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/dict_data.py b/backend/app/api/v1/dict_data.py index 37ecf4fd..44b3ac0c 100644 --- a/backend/app/api/v1/dict_data.py +++ b/backend/app/api/v1/dict_data.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, Query from backend.app.common.jwt import jwt_auth -from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.pagination import DependsPagination, paging_data +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession @@ -23,7 +24,14 @@ async def get_dict_data(pk: int): return await response_base.success(data=data) -@router.get('', summary='(模糊条件)分页获取所有字典', dependencies=[Depends(jwt_auth), PageDepends]) +@router.get( + '', + summary='(模糊条件)分页获取所有字典', + dependencies=[ + Depends(jwt_auth), + DependsPagination, + ], +) async def get_all_dict_datas( db: CurrentSession, label: Annotated[str | None, Query()] = None, @@ -35,13 +43,27 @@ async def get_all_dict_datas( return await response_base.success(data=page_data) -@router.post('', summary='创建字典', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '', + summary='创建字典', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dict:data:add')), + ], +) async def create_dict_data(obj: CreateDictData): await DictDataService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新字典', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}', + summary='更新字典', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dict:data:edit')), + ], +) async def update_dict_data(pk: int, obj: UpdateDictData): count = await DictDataService.update(pk=pk, obj=obj) if count > 0: @@ -49,7 +71,14 @@ async def update_dict_data(pk: int, obj: UpdateDictData): return await response_base.fail() -@router.delete('', summary='(批量)删除字典', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '', + summary='(批量)删除字典', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dict:data:del')), + ], +) async def delete_dict_data(pk: Annotated[list[int], Query(...)]): count = await DictDataService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/dict_type.py b/backend/app/api/v1/dict_type.py index 3e57fb52..a93fd41a 100644 --- a/backend/app/api/v1/dict_type.py +++ b/backend/app/api/v1/dict_type.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, Query from backend.app.common.jwt import jwt_auth -from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.pagination import DependsPagination, paging_data +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession @@ -15,7 +16,14 @@ router = APIRouter() -@router.get('', summary='(模糊条件)分页获取所有字典类型', dependencies=[Depends(jwt_auth), PageDepends]) +@router.get( + '', + summary='(模糊条件)分页获取所有字典类型', + dependencies=[ + Depends(jwt_auth), + DependsPagination, + ], +) async def get_all_dict_types( db: CurrentSession, name: Annotated[str | None, Query()] = None, @@ -27,13 +35,27 @@ async def get_all_dict_types( return await response_base.success(data=page_data) -@router.post('', summary='创建字典类型', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '', + summary='创建字典类型', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dict:type:add')), + ], +) async def create_dict_type(obj: CreateDictType): await DictTypeService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新字典类型', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}', + summary='更新字典类型', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dict:type:edit')), + ], +) async def update_dict_type(pk: int, obj: UpdateDictType): count = await DictTypeService.update(pk=pk, obj=obj) if count > 0: @@ -41,7 +63,14 @@ async def update_dict_type(pk: int, obj: UpdateDictType): return await response_base.fail() -@router.delete('', summary='(批量)删除字典类型', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '', + summary='(批量)删除字典类型', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:dict:type:del')), + ], +) async def delete_dict_type(pk: Annotated[list[int], Query(...)]): count = await DictTypeService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/log/login_log.py b/backend/app/api/v1/log/login_log.py index 05d2247c..1e6b3b49 100644 --- a/backend/app/api/v1/log/login_log.py +++ b/backend/app/api/v1/log/login_log.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, Query from backend.app.common.jwt import jwt_auth -from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.pagination import DependsPagination, paging_data +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession @@ -15,7 +16,14 @@ router = APIRouter() -@router.get('', summary='(模糊条件)分页获取登录日志', dependencies=[Depends(jwt_auth), PageDepends]) +@router.get( + '', + summary='(模糊条件)分页获取登录日志', + dependencies=[ + Depends(jwt_auth), + DependsPagination, + ], +) async def get_all_login_logs( db: CurrentSession, username: Annotated[str | None, Query()] = None, @@ -27,7 +35,14 @@ async def get_all_login_logs( return await response_base.success(data=page_data) -@router.delete('', summary='(批量)删除登录日志', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '', + summary='(批量)删除登录日志', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('log:login:del')), + ], +) async def delete_login_log(pk: Annotated[list[int], Query(...)]): count = await LoginLogService.delete(pk=pk) if count > 0: @@ -35,7 +50,14 @@ async def delete_login_log(pk: Annotated[list[int], Query(...)]): return await response_base.fail() -@router.delete('/all', summary='清空登录日志', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/all', + summary='清空登录日志', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('log:login:empty')), + ], +) async def delete_all_login_logs(): count = await LoginLogService.delete_all() if count > 0: diff --git a/backend/app/api/v1/log/opera_log.py b/backend/app/api/v1/log/opera_log.py index 5b372e84..8dae87d2 100644 --- a/backend/app/api/v1/log/opera_log.py +++ b/backend/app/api/v1/log/opera_log.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, Query from backend.app.common.jwt import jwt_auth -from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.pagination import DependsPagination, paging_data +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession @@ -15,7 +16,14 @@ router = APIRouter() -@router.get('', summary='(模糊条件)分页获取操作日志', dependencies=[Depends(jwt_auth), PageDepends]) +@router.get( + '', + summary='(模糊条件)分页获取操作日志', + dependencies=[ + Depends(jwt_auth), + DependsPagination, + ], +) async def get_all_opera_logs( db: CurrentSession, username: Annotated[str | None, Query()] = None, @@ -27,7 +35,14 @@ async def get_all_opera_logs( return await response_base.success(data=page_data) -@router.delete('', summary='(批量)删除操作日志', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '', + summary='(批量)删除操作日志', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('log:opera:del')), + ], +) async def delete_opera_log(pk: Annotated[list[int], Query(...)]): count = await OperaLogService.delete(pk=pk) if count > 0: @@ -35,7 +50,14 @@ async def delete_opera_log(pk: Annotated[list[int], Query(...)]): return await response_base.fail() -@router.delete('/all', summary='清空操作日志', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/all', + summary='清空操作日志', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('log:opera:empty')), + ], +) async def delete_all_opera_logs(): count = await OperaLogService.delete_all() if count > 0: diff --git a/backend/app/api/v1/menu.py b/backend/app/api/v1/menu.py index 41e257e5..89a12265 100644 --- a/backend/app/api/v1/menu.py +++ b/backend/app/api/v1/menu.py @@ -5,6 +5,7 @@ from fastapi import APIRouter, Depends, Query, Request from backend.app.common.jwt import jwt_auth +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.schemas.menu import CreateMenu, GetAllMenu, UpdateMenu @@ -36,13 +37,27 @@ async def get_all_menus( return await response_base.success(data=menu) -@router.post('', summary='创建菜单', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '', + summary='创建菜单', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:menu:edit')), + ], +) async def create_menu(obj: CreateMenu): await MenuService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新菜单', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}', + summary='更新菜单', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:menu:edit')), + ], +) async def update_menu(pk: int, obj: UpdateMenu): count = await MenuService.update(pk=pk, obj=obj) if count > 0: @@ -50,7 +65,14 @@ async def update_menu(pk: int, obj: UpdateMenu): return await response_base.fail() -@router.delete('/{pk}', summary='删除菜单', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '/{pk}', + summary='删除菜单', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:menu:edit')), + ], +) async def delete_menu(pk: int): count = await MenuService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/mixed/config.py b/backend/app/api/v1/mixed/config.py index 0ea2aac4..d603b4f2 100644 --- a/backend/app/api/v1/mixed/config.py +++ b/backend/app/api/v1/mixed/config.py @@ -3,13 +3,21 @@ from fastapi import APIRouter, Depends, Request from fastapi.routing import APIRoute +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base router = APIRouter() -@router.get('/routers', summary='获取所有路由', dependencies=[Depends(RBAC.rbac_verify)]) +@router.get( + '/routers', + summary='获取所有路由', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:route:list')), + ], +) async def get_all_route(request: Request): data = [] for route in request.app.routes: diff --git a/backend/app/api/v1/role.py b/backend/app/api/v1/role.py index a1228b81..d77722d1 100644 --- a/backend/app/api/v1/role.py +++ b/backend/app/api/v1/role.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, Query from backend.app.common.jwt import jwt_auth -from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.pagination import DependsPagination, paging_data +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession @@ -44,7 +45,14 @@ async def get_role(pk: int): return await response_base.success(data=data) -@router.get('', summary='(模糊条件)分页获取所有角色', dependencies=[Depends(jwt_auth), PageDepends]) +@router.get( + '', + summary='(模糊条件)分页获取所有角色', + dependencies=[ + Depends(jwt_auth), + DependsPagination, + ], +) async def get_all_role_list( db: CurrentSession, name: Annotated[str | None, Query()] = None, @@ -56,13 +64,27 @@ async def get_all_role_list( return await response_base.success(data=page_data) -@router.post('', summary='创建角色', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '', + summary='创建角色', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:role:add')), + ], +) async def create_role(obj: CreateRole): await RoleService.create(obj=obj) return await response_base.success() -@router.put('/{pk}', summary='更新角色', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}', + summary='更新角色', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:role:edit')), + ], +) async def update_role(pk: int, obj: UpdateRole): count = await RoleService.update(pk=pk, obj=obj) if count > 0: @@ -70,7 +92,14 @@ async def update_role(pk: int, obj: UpdateRole): return await response_base.fail() -@router.put('/{pk}/menu', summary='更新角色菜单', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}/menu', + summary='更新角色菜单', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:role:menu:edit')), + ], +) async def update_role_menu(pk: int, menu_ids: UpdateRoleMenu): count = await RoleService.update_menus(pk=pk, menu_ids=menu_ids) if count > 0: @@ -78,7 +107,14 @@ async def update_role_menu(pk: int, menu_ids: UpdateRoleMenu): return await response_base.fail() -@router.delete('', summary='(批量)删除角色', dependencies=[Depends(RBAC.rbac_verify)]) +@router.delete( + '', + summary='(批量)删除角色', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:role:del')), + ], +) async def delete_role(pk: Annotated[list[int], Query(...)]): count = await RoleService.delete(pk=pk) if count > 0: diff --git a/backend/app/api/v1/task.py b/backend/app/api/v1/task.py index 68c4edad..317277dd 100644 --- a/backend/app/api/v1/task.py +++ b/backend/app/api/v1/task.py @@ -5,6 +5,7 @@ from fastapi import APIRouter, Body, Depends, Path from backend.app.common.jwt import jwt_auth +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_code import CustomResponseCode from backend.app.common.response.response_schema import response_base @@ -27,7 +28,14 @@ async def get_task_result(pk: str = Path(description='任务ID')): return await response_base.success(data=task.result) -@router.post('/{module}', summary='执行任务', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '/{module}', + summary='执行任务', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:task:run')), + ], +) async def run_task( module: Annotated[str, Path(description='任务模块')], args: Annotated[list | None, Body()] = None, diff --git a/backend/app/api/v1/user.py b/backend/app/api/v1/user.py index 6a477a75..2dc21117 100644 --- a/backend/app/api/v1/user.py +++ b/backend/app/api/v1/user.py @@ -5,7 +5,8 @@ from fastapi import APIRouter, Depends, Query, Request from backend.app.common.jwt import jwt_auth -from backend.app.common.pagination import PageDepends, paging_data +from backend.app.common.pagination import DependsPagination, paging_data +from backend.app.common.permission import RequestPermission from backend.app.common.rbac import RBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession @@ -31,7 +32,14 @@ async def user_register(obj: RegisterUser): return await response_base.success() -@router.post('/add', summary='添加用户', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post( + '/add', + summary='添加用户', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:user:add')), + ], +) async def add_user(obj: AddUser): await UserService.add(obj=obj) current_user = await UserService.get_userinfo(username=obj.username) @@ -68,7 +76,14 @@ async def update_userinfo(request: Request, username: str, obj: UpdateUser): return await response_base.fail() -@router.put('/{username}/role', summary='更新用户角色', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{username}/role', + summary='更新用户角色', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:user:role:edit')), + ], +) async def update_user_role(request: Request, username: str, obj: UpdateUserRole): await UserService.update_role(request=request, username=username, obj=obj) return await response_base.success() @@ -82,7 +97,14 @@ async def update_avatar(request: Request, username: str, avatar: Avatar): return await response_base.fail() -@router.get('', summary='(模糊条件)分页获取所有用户', dependencies=[Depends(jwt_auth), PageDepends]) +@router.get( + '', + summary='(模糊条件)分页获取所有用户', + dependencies=[ + Depends(jwt_auth), + DependsPagination, + ], +) async def get_all_users( db: CurrentSession, dept: Annotated[int | None, Query()] = None, @@ -95,7 +117,14 @@ async def get_all_users( return await response_base.success(data=page_data) -@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}/super', + summary='修改用户超级权限', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:user:super:edit')), + ], +) async def super_set(request: Request, pk: int): count = await UserService.update_permission(request=request, pk=pk) if count > 0: @@ -103,7 +132,14 @@ async def super_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/staff', summary='修改用户后台登录权限', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}/staff', + summary='修改用户后台登录权限', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:user:staff:edit')), + ], +) async def staff_set(request: Request, pk: int): count = await UserService.update_staff(request=request, pk=pk) if count > 0: @@ -111,7 +147,14 @@ async def staff_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/status', summary='修改用户状态', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}/status', + summary='修改用户状态', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:user:status:edit')), + ], +) async def status_set(request: Request, pk: int): count = await UserService.update_status(request=request, pk=pk) if count > 0: @@ -119,7 +162,14 @@ async def status_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/multi', summary='修改用户多点登录状态', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put( + '/{pk}/multi', + summary='修改用户多点登录状态', + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:user:multi:edit')), + ], +) async def multi_set(request: Request, pk: int): count = await UserService.update_multi_login(request=request, pk=pk) if count > 0: @@ -131,7 +181,10 @@ async def multi_set(request: Request, pk: int): path='/{username}', summary='用户注销', description='用户注销 != 用户登出,注销之后用户将从数据库删除', - dependencies=[Depends(RBAC.rbac_verify)], + dependencies=[ + Depends(RBAC.rbac_verify), + Depends(RequestPermission('sys:user:del')), + ], ) async def delete_user(request: Request, username: str): count = await UserService.delete(request=request, username=username) diff --git a/backend/app/common/pagination.py b/backend/app/common/pagination.py index 5ae7074d..11969b93 100644 --- a/backend/app/common/pagination.py +++ b/backend/app/common/pagination.py @@ -84,4 +84,4 @@ async def paging_data(db: AsyncSession, select: Select, page_data_schema: Schema # 分页依赖注入 -PageDepends = Depends(pagination_ctx(_Page)) +DependsPagination = Depends(pagination_ctx(_Page)) From 89975b7e72ae7e03fe6899f1d15fff3bf3dc9434 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sat, 6 Jan 2024 01:46:42 +0800 Subject: [PATCH 10/22] Add request permission dependency description --- backend/app/common/permission.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/app/common/permission.py b/backend/app/common/permission.py index 27d385f3..0e0199b1 100644 --- a/backend/app/common/permission.py +++ b/backend/app/common/permission.py @@ -7,6 +7,13 @@ class RequestPermission: + """ + 请求权限,仅用于角色菜单RBAC + + Tip: + 使用此请求权限时,需要将 `Depends(RBAC.rbac_verify)` 在 `Depends(RequestPermission('xxx'))` 之前设置, + 这是因为 fastapi 当前版本的依赖导入顺序为逆向导入,意味着 RBAC 标识会在验证前被设置 + """ def __init__(self, value: str): self.value = value From 98ffb80854a6eb81fdfcaab59b183c94f1e42251 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sat, 6 Jan 2024 23:09:38 +0800 Subject: [PATCH 11/22] Migrate casbin redis adapter to sqla --- backend/app/.env.example | 2 - backend/app/api/v1/casbin.py | 7 +-- backend/app/api/v1/monitor/redis.py | 10 +++- backend/app/api/v1/monitor/server.py | 10 +++- backend/app/api/v1/role.py | 6 +- backend/app/api/v1/user.py | 2 +- backend/app/common/permission.py | 1 + backend/app/common/rbac.py | 81 +++++++++++++++----------- backend/app/core/conf.py | 15 ++--- backend/app/crud/crud_casbin.py | 6 +- backend/app/crud/crud_menu.py | 6 +- backend/app/models/sys_casbin_rule.py | 6 +- backend/app/schemas/casbin_rule.py | 8 +-- backend/app/services/casbin_service.py | 3 +- backend/app/services/role_service.py | 6 +- backend/app/services/user_service.py | 3 +- requirements.txt | 2 +- 17 files changed, 101 insertions(+), 73 deletions(-) diff --git a/backend/app/.env.example b/backend/app/.env.example index 2ecb8301..1863b8c2 100644 --- a/backend/app/.env.example +++ b/backend/app/.env.example @@ -10,8 +10,6 @@ REDIS_HOST='127.0.0.1' REDIS_PORT=6379 REDIS_PASSWORD='' REDIS_DATABASE=0 -# Casbin -CASBIN_REDIS_DATABASE=9 # Celery CELERY_REDIS_HOST='127.0.0.1' CELERY_REDIS_PORT=6379 diff --git a/backend/app/api/v1/casbin.py b/backend/app/api/v1/casbin.py index 8f8762ca..35b7c214 100644 --- a/backend/app/api/v1/casbin.py +++ b/backend/app/api/v1/casbin.py @@ -14,7 +14,6 @@ CreatePolicy, CreateUserRole, DeleteAllPolicies, - DeleteAllUserRoles, DeletePolicy, DeleteUserRole, GetAllPolicy, @@ -35,8 +34,8 @@ ) async def get_all_casbin( db: CurrentSession, - ptype: Annotated[str | None, Query()] = None, - sub: Annotated[str | None, Query()] = None, + ptype: Annotated[str | None, Query(description='规则类型, p / g')] = None, + sub: Annotated[str | None, Query(description='用户 uuid / 角色')] = None, ): casbin_select = await CasbinService.get_casbin_list(ptype=ptype, sub=sub) page_data = await paging_data(db, casbin_select, GetAllPolicy) @@ -232,7 +231,7 @@ async def delete_groups(gs: list[DeleteUserRole]): Depends(RequestPermission('casbin:g:empty')), ], ) -async def delete_all_groups(uuid: DeleteAllUserRoles): +async def delete_all_groups(uuid: str): count = await CasbinService.delete_all_groups(uuid=uuid) if count > 0: return await response_base.success() diff --git a/backend/app/api/v1/monitor/redis.py b/backend/app/api/v1/monitor/redis.py index b553caed..bfcc25b5 100644 --- a/backend/app/api/v1/monitor/redis.py +++ b/backend/app/api/v1/monitor/redis.py @@ -3,13 +3,21 @@ from fastapi import APIRouter, Depends from backend.app.common.jwt import jwt_auth +from backend.app.common.permission import RequestPermission from backend.app.common.response.response_schema import response_base from backend.app.utils.redis_info import redis_info router = APIRouter() -@router.get('/redis', summary='redis 监控', dependencies=[Depends(jwt_auth)]) +@router.get( + '/redis', + summary='redis 监控', + dependencies=[ + Depends(jwt_auth), + Depends(RequestPermission('sys:monitor:redis')), + ], +) async def get_redis_info(): data = {'info': await redis_info.get_info(), 'stats': await redis_info.get_stats()} return await response_base.success(data=data) diff --git a/backend/app/api/v1/monitor/server.py b/backend/app/api/v1/monitor/server.py index dd31aa59..be49773e 100644 --- a/backend/app/api/v1/monitor/server.py +++ b/backend/app/api/v1/monitor/server.py @@ -4,13 +4,21 @@ from starlette.concurrency import run_in_threadpool from backend.app.common.jwt import jwt_auth +from backend.app.common.permission import RequestPermission from backend.app.common.response.response_schema import response_base from backend.app.utils.server_info import server_info router = APIRouter() -@router.get('/server', summary='server 监控', dependencies=[Depends(jwt_auth)]) +@router.get( + '/server', + summary='server 监控', + dependencies=[ + Depends(jwt_auth), + Depends(RequestPermission('sys:monitor:server')), + ], +) async def get_server_info(): """IO密集型任务,使用线程池尽量减少性能损耗""" data = { diff --git a/backend/app/api/v1/role.py b/backend/app/api/v1/role.py index d77722d1..360cf57c 100644 --- a/backend/app/api/v1/role.py +++ b/backend/app/api/v1/role.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, Query, Request from backend.app.common.jwt import jwt_auth from backend.app.common.pagination import DependsPagination, paging_data @@ -100,8 +100,8 @@ async def update_role(pk: int, obj: UpdateRole): Depends(RequestPermission('sys:role:menu:edit')), ], ) -async def update_role_menu(pk: int, menu_ids: UpdateRoleMenu): - count = await RoleService.update_menus(pk=pk, menu_ids=menu_ids) +async def update_role_menu(request: Request, pk: int, menu_ids: UpdateRoleMenu): + count = await RoleService.update_menus(request=request, pk=pk, menu_ids=menu_ids) if count > 0: return await response_base.success() return await response_base.fail() diff --git a/backend/app/api/v1/user.py b/backend/app/api/v1/user.py index 2dc21117..84af8c85 100644 --- a/backend/app/api/v1/user.py +++ b/backend/app/api/v1/user.py @@ -85,7 +85,7 @@ async def update_userinfo(request: Request, username: str, obj: UpdateUser): ], ) async def update_user_role(request: Request, username: str, obj: UpdateUserRole): - await UserService.update_role(request=request, username=username, obj=obj) + await UserService.update_roles(request=request, username=username, obj=obj) return await response_base.success() diff --git a/backend/app/common/permission.py b/backend/app/common/permission.py index 0e0199b1..b506118f 100644 --- a/backend/app/common/permission.py +++ b/backend/app/common/permission.py @@ -14,6 +14,7 @@ class RequestPermission: 使用此请求权限时,需要将 `Depends(RBAC.rbac_verify)` 在 `Depends(RequestPermission('xxx'))` 之前设置, 这是因为 fastapi 当前版本的依赖导入顺序为逆向导入,意味着 RBAC 标识会在验证前被设置 """ + def __init__(self, value: str): self.value = value diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index 9300ea1e..290039bd 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -1,14 +1,17 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- import casbin +import casbin_async_sqlalchemy_adapter -from casbin_async_redis_adapter.adapter import Adapter from fastapi import Depends, Request from backend.app.common.enums import StatusType from backend.app.common.exception.errors import AuthorizationError, TokenError from backend.app.common.jwt import jwt_auth +from backend.app.common.redis import redis_client from backend.app.core.conf import settings +from backend.app.database.db_mysql import async_engine +from backend.app.models import CasbinRule class RBAC: @@ -36,12 +39,7 @@ async def enforcer() -> casbin.AsyncEnforcer: [matchers] m = g(r.sub, p.sub) && (keyMatch(r.obj, p.obj) || keyMatch3(r.obj, p.obj)) && (r.act == p.act || p.act == "*") """ - adapter = Adapter( - host=settings.REDIS_HOST, - port=settings.REDIS_PORT, - db=settings.CASBIN_REDIS_DATABASE, - password=settings.REDIS_PASSWORD, - ) + adapter = casbin_async_sqlalchemy_adapter.Adapter(async_engine, db_class=CasbinRule) model = casbin.AsyncEnforcer.new_model(text=_CASBIN_RBAC_MODEL_CONF_TEXT) enforcer = casbin.AsyncEnforcer(model, adapter) await enforcer.load_policy() @@ -76,38 +74,55 @@ async def rbac_verify(self, request: Request, _token: str = Depends(jwt_auth)) - data_scope = any(role.data_scope == 1 for role in user_roles) if data_scope: return - method = request.method - # TODO: 手动编写每一个路由权限标识,使用 fastapi Depends 实现 - path_auth = 'todo' + user_id = request.user.id + path_auth_perm = request.state.permission if settings.PERMISSION_MODE == 'role-menu': - # 菜单权限校验 - if path_auth in set(settings.MENU_EXCLUDE): + # 角色菜单权限校验 + if path_auth_perm in set(settings.ROLE_MENU_EXCLUDE): return - menu_perms = [] - forbid_menu_perms = [] - for role in user_roles: - user_menus = role.menus - if user_menus: - for menu in user_menus: - perms = menu.perms - if menu.status == StatusType.enable: - menu_perms.extend(perms.split(',')) - else: - forbid_menu_perms.extend(perms.split(',')) - if path_auth in set(forbid_menu_perms): + user_menu_perms = await redis_client.get(f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:enable') + user_forbid_menu_perms = await redis_client.get(f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:disable') + if not user_menu_perms or not user_forbid_menu_perms: + user_menu_perms = [] + user_forbid_menu_perms = [] + for role in user_roles: + user_menus = role.menus + if user_menus: + for menu in user_menus: + perms = menu.perms + if menu.status == StatusType.enable: + user_menu_perms.extend(perms.split(',')) + else: + user_forbid_menu_perms.extend(perms.split(',')) + await redis_client.rset( + f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:enable', ','.join(user_menu_perms) + ) + await redis_client.rset( + f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:disable', ','.join(user_forbid_menu_perms) + ) + if path_auth_perm in user_forbid_menu_perms: raise AuthorizationError(msg='菜单已禁用,授权失败') - if path_auth not in set(menu_perms): + if path_auth_perm not in user_menu_perms: raise AuthorizationError else: # casbin 权限校验 - forbid_menu_path = [] - for role in user_roles: - user_menus = role.menus - if user_menus: - for menu in user_menus: - if menu.status == StatusType.disable: - forbid_menu_path.append(menu.perms) - if path_auth in forbid_menu_path: + method = request.method + user_forbid_menu_perms = await redis_client.get( + f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.id}:disable' + ) + if not user_forbid_menu_perms: + user_forbid_menu_perms = [] + for role in user_roles: + user_menus = role.menus + if user_menus: + for menu in user_menus: + perms = menu.perms + if menu.status == StatusType.disable: + user_forbid_menu_perms.extend(perms.split(',')) + await redis_client.rset( + f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:disable', ','.join(user_forbid_menu_perms) + ) + if path_auth_perm in user_forbid_menu_perms: raise AuthorizationError(msg='菜单已禁用,授权失败') if (method, path) in settings.CASBIN_EXCLUDE: return diff --git a/backend/app/core/conf.py b/backend/app/core/conf.py index 4ce3f94a..44a619a3 100644 --- a/backend/app/core/conf.py +++ b/backend/app/core/conf.py @@ -25,9 +25,6 @@ class Settings(BaseSettings): REDIS_PASSWORD: str REDIS_DATABASE: int - # Env Casbin - CASBIN_REDIS_DATABASE: int - # Env Celery CELERY_REDIS_HOST: str CELERY_REDIS_PORT: int @@ -123,8 +120,9 @@ def validate_openapi_url(cls, values): MIDDLEWARE_GZIP: bool = True MIDDLEWARE_ACCESS: bool = False - # RBAC Permission Mode + # RBAC Permission PERMISSION_MODE: Literal['casbin', 'role-menu'] = 'casbin' + PERMISSION_REDIS_PREFIX: str = 'fba_permission' # Casbin Auth CASBIN_EXCLUDE: set[tuple[str, str]] = { @@ -136,12 +134,9 @@ def validate_openapi_url(cls, values): } # Role Menu Auth - MENU_EXCLUDE: list[str] = [ - 'auth:swagger_login:post', - 'auth:login:post', - 'auth:logout:post', - 'auth:register:post', - 'auth:captcha:get', + ROLE_MENU_EXCLUDE: list[str] = [ + 'sys:monitor:redis', + 'sys:monitor:server', ] # Opera log diff --git a/backend/app/crud/crud_casbin.py b/backend/app/crud/crud_casbin.py index 022221a4..3a3287e8 100644 --- a/backend/app/crud/crud_casbin.py +++ b/backend/app/crud/crud_casbin.py @@ -5,7 +5,7 @@ from backend.app.crud.base import CRUDBase from backend.app.models import CasbinRule -from backend.app.schemas.casbin_rule import CreatePolicy, DeleteAllPolicies, DeleteAllUserRoles, UpdatePolicy +from backend.app.schemas.casbin_rule import CreatePolicy, DeleteAllPolicies, UpdatePolicy class CRUDCasbin(CRUDBase[CasbinRule, CreatePolicy, UpdatePolicy]): @@ -28,8 +28,8 @@ async def delete_policies_by_sub(self, db: AsyncSession, sub: DeleteAllPolicies) result = await db.execute(delete(self.model).where(or_(*where_list))) return result.rowcount - async def delete_groups_by_uuid(self, db: AsyncSession, sub: DeleteAllUserRoles) -> int: - result = await db.execute(delete(self.model).where(self.model.v0 == sub.uuid)) + async def delete_groups_by_uuid(self, db: AsyncSession, uuid: str) -> int: + result = await db.execute(delete(self.model).where(self.model.v0 == uuid)) return result.rowcount diff --git a/backend/app/crud/crud_menu.py b/backend/app/crud/crud_menu.py index e031dfde..a0cc1e8b 100644 --- a/backend/app/crud/crud_menu.py +++ b/backend/app/crud/crud_menu.py @@ -5,6 +5,8 @@ from sqlalchemy import and_, asc, select from sqlalchemy.orm import selectinload +from backend.app.common.redis import redis_client +from backend.app.core.conf import settings from backend.app.crud.base import CRUDBase from backend.app.models import Menu from backend.app.schemas.menu import CreateMenu, UpdateMenu @@ -43,7 +45,9 @@ async def create(self, db, obj_in: CreateMenu) -> None: await self.create_(db, obj_in) async def update(self, db, menu_id: int, obj_in: UpdateMenu) -> int: - return await self.update_(db, menu_id, obj_in) + count = await self.update_(db, menu_id, obj_in) + await redis_client.delete_prefix(settings.PERMISSION_REDIS_PREFIX) + return count async def delete(self, db, menu_id: int) -> int: return await self.delete_(db, menu_id) diff --git a/backend/app/models/sys_casbin_rule.py b/backend/app/models/sys_casbin_rule.py index 0d71e7c9..ec7fb96a 100644 --- a/backend/app/models/sys_casbin_rule.py +++ b/backend/app/models/sys_casbin_rule.py @@ -8,13 +8,13 @@ class CasbinRule(MappedBase): - """重写 casbin 中的 casbinRule model 类, 使用自定义 Base, 避免产生 alembic 迁移问题""" + """重写 casbin 中的 CasbinRule model 类, 使用自定义 Base, 避免产生 alembic 迁移问题""" __tablename__ = 'sys_casbin_rule' id: Mapped[id_key] - ptype: Mapped[str] = mapped_column(String(255), comment='策略类型: p 或者 g') - v0: Mapped[str] = mapped_column(String(255), comment='角色 / 用户uuid') + ptype: Mapped[str] = mapped_column(String(255), comment='策略类型: p / g') + v0: Mapped[str] = mapped_column(String(255), comment='角色ID / 用户uuid') v1: Mapped[str] = mapped_column(LONGTEXT, comment='api路径 / 角色名称') v2: Mapped[str | None] = mapped_column(String(255), comment='请求方法') v3: Mapped[str | None] = mapped_column(String(255)) diff --git a/backend/app/schemas/casbin_rule.py b/backend/app/schemas/casbin_rule.py index 28f03efc..43c2ea7e 100644 --- a/backend/app/schemas/casbin_rule.py +++ b/backend/app/schemas/casbin_rule.py @@ -7,7 +7,7 @@ class CreatePolicy(SchemaBase): - sub: str = Field(..., description='用户uuid / 角色') + sub: str = Field(..., description='用户uuid / 角色ID') path: str = Field(..., description='api 路径') method: MethodType = Field(default=MethodType.GET, description='请求方法') @@ -41,15 +41,11 @@ class DeleteUserRole(CreateUserRole): pass -class DeleteAllUserRoles(SchemaBase): - uuid: str - - class GetAllPolicy(SchemaBase): model_config = ConfigDict(from_attributes=True) id: int - ptype: str = Field(..., description='规则类型, p 或 g') + ptype: str = Field(..., description='规则类型, p / g') v0: str = Field(..., description='用户 uuid / 角色') v1: str = Field(..., description='api 路径 / 角色') v2: str | None = None diff --git a/backend/app/services/casbin_service.py b/backend/app/services/casbin_service.py index 6b884dd7..e4c63673 100644 --- a/backend/app/services/casbin_service.py +++ b/backend/app/services/casbin_service.py @@ -10,7 +10,6 @@ CreatePolicy, CreateUserRole, DeleteAllPolicies, - DeleteAllUserRoles, DeletePolicy, DeleteUserRole, UpdatePolicy, @@ -130,7 +129,7 @@ async def delete_groups(*, gs: list[DeleteUserRole]): return data @staticmethod - async def delete_all_groups(*, uuid: DeleteAllUserRoles) -> int: + async def delete_all_groups(*, uuid: str) -> int: async with async_db_session.begin() as db: count = await CasbinDao.delete_groups_by_uuid(db, uuid) return count diff --git a/backend/app/services/role_service.py b/backend/app/services/role_service.py index 91c5cb3d..5c6db2e8 100644 --- a/backend/app/services/role_service.py +++ b/backend/app/services/role_service.py @@ -2,9 +2,12 @@ # -*- coding: utf-8 -*- from typing import Sequence +from fastapi import Request from sqlalchemy import Select from backend.app.common.exception import errors +from backend.app.common.redis import redis_client +from backend.app.core.conf import settings from backend.app.crud.crud_menu import MenuDao from backend.app.crud.crud_role import RoleDao from backend.app.database.db_mysql import async_db_session @@ -59,7 +62,7 @@ async def update(*, pk: int, obj: UpdateRole) -> int: return count @staticmethod - async def update_menus(*, pk: int, menu_ids: UpdateRoleMenu) -> int: + async def update_menus(*, request: Request, pk: int, menu_ids: UpdateRoleMenu) -> int: async with async_db_session.begin() as db: role = await RoleDao.get(db, pk) if not role: @@ -69,6 +72,7 @@ async def update_menus(*, pk: int, menu_ids: UpdateRoleMenu) -> int: if not menu: raise errors.NotFoundError(msg='菜单不存在') count = await RoleDao.update_menus(db, pk, menu_ids) + await redis_client.delete_prefix(f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.id}') return count @staticmethod diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py index b5ae0ad9..0f69b5e7 100644 --- a/backend/app/services/user_service.py +++ b/backend/app/services/user_service.py @@ -105,7 +105,7 @@ async def update(*, request: Request, username: str, obj: UpdateUser) -> int: return count @staticmethod - async def update_role(*, request: Request, username: str, obj: UpdateUserRole) -> None: + async def update_roles(*, request: Request, username: str, obj: UpdateUserRole) -> None: async with async_db_session.begin() as db: if not request.user.is_superuser: if request.user.username != username: @@ -118,6 +118,7 @@ async def update_role(*, request: Request, username: str, obj: UpdateUserRole) - if not role: raise errors.NotFoundError(msg='角色不存在') await UserDao.update_role(db, input_user, obj) + await redis_client.delete_prefix(f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.id}') @staticmethod async def update_avatar(*, request: Request, username: str, avatar: Avatar) -> int: diff --git a/requirements.txt b/requirements.txt index 5f8e0274..33b0e02e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ asgiref==3.7.2 asyncmy==0.2.9 bcrypt==4.0.1 casbin==1.34.0 -casbin_async_redis_adapter==1.0.0 +casbin-async-sqlalchemy-adapter==1.4.0 celery==5.3.6 cryptography==41.0.7 email-validator==2.0.0 From dead9839e8dac71e4a3be8773796b19bd9f6f93d Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 01:55:37 +0800 Subject: [PATCH 12/22] Update menu model and add function --- backend/app/crud/crud_menu.py | 2 +- backend/app/models/sys_menu.py | 2 +- backend/sql/create_tables.sql | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/app/crud/crud_menu.py b/backend/app/crud/crud_menu.py index a0cc1e8b..e247b32a 100644 --- a/backend/app/crud/crud_menu.py +++ b/backend/app/crud/crud_menu.py @@ -17,7 +17,7 @@ async def get(self, db, menu_id: int) -> Menu | None: return await self.get_(db, pk=menu_id) async def get_by_title(self, db, title: str) -> Menu | None: - result = await db.execute(select(self.model).where(self.model.title == title)) + result = await db.execute(select(self.model).where(and_(self.model.title == title, self.model.menu_type != 2))) return result.scalars().first() async def get_all(self, db, title: str | None = None, status: int | None = None) -> Sequence[Menu]: diff --git a/backend/app/models/sys_menu.py b/backend/app/models/sys_menu.py index f2b6f1db..8c4aebca 100644 --- a/backend/app/models/sys_menu.py +++ b/backend/app/models/sys_menu.py @@ -16,7 +16,7 @@ class Menu(Base): __tablename__ = 'sys_menu' id: Mapped[id_key] = mapped_column(init=False) - title: Mapped[str] = mapped_column(String(50), unique=True, comment='菜单标题') + title: Mapped[str] = mapped_column(String(50), comment='菜单标题') name: Mapped[str] = mapped_column(String(50), comment='菜单名称') level: Mapped[int] = mapped_column(default=0, comment='菜单层级') sort: Mapped[int] = mapped_column(default=0, comment='排序') diff --git a/backend/sql/create_tables.sql b/backend/sql/create_tables.sql index 5d04ae76..572d0371 100644 --- a/backend/sql/create_tables.sql +++ b/backend/sql/create_tables.sql @@ -114,8 +114,7 @@ CREATE TABLE sys_menu created_time DATETIME NOT NULL COMMENT '创建时间', updated_time DATETIME COMMENT '更新时间', PRIMARY KEY (id), - FOREIGN KEY (parent_id) REFERENCES sys_menu (id) ON DELETE SET NULL, - UNIQUE (title) + FOREIGN KEY (parent_id) REFERENCES sys_menu (id) ON DELETE SET NULL ); CREATE INDEX ix_sys_menu_id ON sys_menu (id); From d1d2b93cbeab3cd978ea03f57adf5dd2515974d5 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 02:01:13 +0800 Subject: [PATCH 13/22] Fix menu permission identification --- backend/app/api/v1/menu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/api/v1/menu.py b/backend/app/api/v1/menu.py index 89a12265..050502ed 100644 --- a/backend/app/api/v1/menu.py +++ b/backend/app/api/v1/menu.py @@ -42,7 +42,7 @@ async def get_all_menus( summary='创建菜单', dependencies=[ Depends(RBAC.rbac_verify), - Depends(RequestPermission('sys:menu:edit')), + Depends(RequestPermission('sys:menu:add')), ], ) async def create_menu(obj: CreateMenu): @@ -70,7 +70,7 @@ async def update_menu(pk: int, obj: UpdateMenu): summary='删除菜单', dependencies=[ Depends(RBAC.rbac_verify), - Depends(RequestPermission('sys:menu:edit')), + Depends(RequestPermission('sys:menu:del')), ], ) async def delete_menu(pk: int): From 7e3b29b83779339cfbc077bb62acb8f0c0832534 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 02:30:11 +0800 Subject: [PATCH 14/22] Update user partial interface permissions --- backend/app/api/v1/user.py | 53 +++++----------------------- backend/app/crud/crud_user.py | 2 +- backend/app/services/user_service.py | 10 +++--- 3 files changed, 16 insertions(+), 49 deletions(-) diff --git a/backend/app/api/v1/user.py b/backend/app/api/v1/user.py index 84af8c85..8ccb4ee7 100644 --- a/backend/app/api/v1/user.py +++ b/backend/app/api/v1/user.py @@ -32,16 +32,9 @@ async def user_register(obj: RegisterUser): return await response_base.success() -@router.post( - '/add', - summary='添加用户', - dependencies=[ - Depends(RBAC.rbac_verify), - Depends(RequestPermission('sys:user:add')), - ], -) -async def add_user(obj: AddUser): - await UserService.add(obj=obj) +@router.post('/add', summary='添加用户', dependencies=[Depends(RBAC.rbac_verify)]) +async def add_user(request: Request, obj: AddUser): + await UserService.add(request=request, obj=obj) current_user = await UserService.get_userinfo(username=obj.username) data = GetAllUserInfo(**await select_as_dict(current_user)) return await response_base.success(data=data) @@ -117,14 +110,7 @@ async def get_all_users( return await response_base.success(data=page_data) -@router.put( - '/{pk}/super', - summary='修改用户超级权限', - dependencies=[ - Depends(RBAC.rbac_verify), - Depends(RequestPermission('sys:user:super:edit')), - ], -) +@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[Depends(RBAC.rbac_verify)]) async def super_set(request: Request, pk: int): count = await UserService.update_permission(request=request, pk=pk) if count > 0: @@ -132,14 +118,7 @@ async def super_set(request: Request, pk: int): return await response_base.fail() -@router.put( - '/{pk}/staff', - summary='修改用户后台登录权限', - dependencies=[ - Depends(RBAC.rbac_verify), - Depends(RequestPermission('sys:user:staff:edit')), - ], -) +@router.put('/{pk}/staff', summary='修改用户后台登录权限', dependencies=[Depends(RBAC.rbac_verify)]) async def staff_set(request: Request, pk: int): count = await UserService.update_staff(request=request, pk=pk) if count > 0: @@ -147,14 +126,7 @@ async def staff_set(request: Request, pk: int): return await response_base.fail() -@router.put( - '/{pk}/status', - summary='修改用户状态', - dependencies=[ - Depends(RBAC.rbac_verify), - Depends(RequestPermission('sys:user:status:edit')), - ], -) +@router.put('/{pk}/status', summary='修改用户状态', dependencies=[Depends(RBAC.rbac_verify)]) async def status_set(request: Request, pk: int): count = await UserService.update_status(request=request, pk=pk) if count > 0: @@ -162,14 +134,7 @@ async def status_set(request: Request, pk: int): return await response_base.fail() -@router.put( - '/{pk}/multi', - summary='修改用户多点登录状态', - dependencies=[ - Depends(RBAC.rbac_verify), - Depends(RequestPermission('sys:user:multi:edit')), - ], -) +@router.put('/{pk}/multi', summary='修改用户多点登录状态', dependencies=[Depends(RBAC.rbac_verify)]) async def multi_set(request: Request, pk: int): count = await UserService.update_multi_login(request=request, pk=pk) if count > 0: @@ -186,8 +151,8 @@ async def multi_set(request: Request, pk: int): Depends(RequestPermission('sys:user:del')), ], ) -async def delete_user(request: Request, username: str): - count = await UserService.delete(request=request, username=username) +async def delete_user(username: str): + count = await UserService.delete(username=username) if count > 0: return await response_base.success() return await response_base.fail() diff --git a/backend/app/crud/crud_user.py b/backend/app/crud/crud_user.py index 776d5a72..5e844e57 100644 --- a/backend/app/crud/crud_user.py +++ b/backend/app/crud/crud_user.py @@ -44,7 +44,7 @@ async def create(self, db: AsyncSession, obj: RegisterUser) -> None: async def add(self, db: AsyncSession, obj: AddUser) -> None: salt = text_captcha(5) obj.password = await jwt.get_hash_password(obj.password + salt) - dict_obj = obj.dict(exclude={'roles'}) + dict_obj = obj.model_dump(exclude={'roles'}) dict_obj.update({'salt': salt}) new_user = self.model(**dict_obj) role_list = [] diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py index 0f69b5e7..400556f6 100644 --- a/backend/app/services/user_service.py +++ b/backend/app/services/user_service.py @@ -34,8 +34,9 @@ async def register(*, obj: RegisterUser) -> None: await UserDao.create(db, obj) @staticmethod - async def add(*, obj: AddUser) -> None: + async def add(*, request: Request, obj: AddUser) -> None: async with async_db_session.begin() as db: + await superuser_verify(request) username = await UserDao.get_by_username(db, obj.username) if username: raise errors.ForbiddenError(msg='此用户名已注册') @@ -85,7 +86,9 @@ async def get_userinfo(*, username: str) -> User: @staticmethod async def update(*, request: Request, username: str, obj: UpdateUser) -> int: async with async_db_session.begin() as db: - await superuser_verify(request) + if not request.user.is_superuser: + if request.user.username != username: + raise errors.ForbiddenError(msg='你只能修改自己的信息') input_user = await UserDao.get_with_relation(db, username=username) if not input_user: raise errors.NotFoundError(msg='用户不存在') @@ -197,9 +200,8 @@ async def update_multi_login(*, request: Request, pk: int) -> int: return count @staticmethod - async def delete(*, request: Request, username: str) -> int: + async def delete(*, username: str) -> int: async with async_db_session.begin() as db: - await superuser_verify(request) input_user = await UserDao.get_by_username(db, username) if not input_user: raise errors.NotFoundError(msg='用户不存在') From 2468033d53df64ce2076c43481de6a6c797f30da Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 02:49:26 +0800 Subject: [PATCH 15/22] Update menu table SQL --- backend/sql/init_pytest_data.sql | 31 +++++++++++++++++++++++-------- backend/sql/init_test_data.sql | 31 +++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/backend/sql/init_pytest_data.sql b/backend/sql/init_pytest_data.sql index e9c99c73..81a12002 100644 --- a/backend/sql/init_pytest_data.sql +++ b/backend/sql/init_pytest_data.sql @@ -2,7 +2,7 @@ INSERT INTO fba_test.sys_dept (id, name, level, sort, leader, phone, email, stat VALUES (1, 'test', 0, 0, null, null, null, 1, 0, null, '2023-06-26 17:13:45', null); INSERT INTO fba_test.sys_menu (id, title, name, level, sort, icon, path, menu_type, component, perms, status, `show`, cache, remark, parent_id, created_time, updated_time) -VALUES (1, '测试', 'test', 0, 0, '', null, 0, null, null, 0, 0, 1, null, null, '2023-07-27 19:14:10', '2023-07-27 19:14:52'), +VALUES (1, '测试', 'test', 0, 0, '', null, 0, null, null, 0, 0, 1, null, null, '2023-07-27 19:14:10', null), (2, '仪表盘', 'dashboard', 0, 0, 'IconDashboard', 'dashboard', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:15:45', null), (3, '工作台', 'Workplace', 0, 0, null, 'workplace', 1, '/dashboard/workplace/index.vue', null, 1, 1, 1, null, 2, '2023-07-27 19:17:59', null), (4, 'arco官网', 'arcoWebsite', 0, 888, 'IconLink', 'https://arco.design', 1, null, null, 1, 1, 1, null, null, '2023-07-27 19:19:23', null), @@ -12,13 +12,28 @@ VALUES (1, '测试', 'test', 0, 0, '', null, 0, null, null, 0, 0, 1, null, null (8, '常见问题', 'faq', 0, 999, 'IconQuestion', 'https://arco.design/vue/docs/pro/faq', 1, null, null, 1, 1, 1, null, null, '2023-07-27 19:22:24', null), (9, '系统管理', 'admin', 0, 6, 'IconSettings', 'admin', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:23:00', null), (10, '部门管理', 'SysDept', 0, 0, null, 'sys-dept', 1, '/admin/dept/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:23:42', null), - (11, 'API管理', 'SysApi', 0, 1, null, 'sys-api', 1, '/admin/api/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:24:12', null), - (12, '用户管理', 'SysUser', 0, 0, null, 'sys-user', 1, '/admin/user/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:13', null), - (13, '角色管理', 'SysRole', 0, 2, null, 'sys-role', 1, '/admin/role/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:45', null), - (14, '菜单管理', 'SysMenu', 0, 2, null, 'sys-menu', 1, '/admin/menu/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:45:29', null), - (15, '系统监控', 'monitor', 0, 88, 'IconComputer', 'monitor', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:27:08', null), - (16, 'Redis监控', 'Redis', 0, 0, null, 'redis', 1, '/monitor/redis/index.vue', null, 1, 1, 1, null, 15, '2023-07-27 19:28:03', null), - (17, '服务器监控', 'Server', 0, 0, null, 'server', 1, '/monitor/server/index.vue', null, 1, 1, 1, null, 15, '2023-07-27 19:28:29', null); + (11, '新增', '', 0, 0, null, null, 2, null, 'sys:dept:add', 1, 1, 1, null, 10, '2024-01-07 11:37:00', null), + (12, '编辑', '', 0, 0, null, null, 2, null, 'sys:dept:edit', 1, 1, 1, null, 10, '2024-01-07 11:37:29', null), + (13, '删除', '', 0, 0, null, null, 2, null, 'sys:dept:del', 1, 1, 1, null, 10, '2024-01-07 11:37:44', null), + (14, 'API管理', 'SysApi', 0, 1, null, 'sys-api', 1, '/admin/api/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:24:12', null), + (15, '新增', '', 0, 0, null, null, 2, null, 'sys:api:add', 1, 1, 1, null, 14, '2024-01-07 11:57:09', null), + (16, '编辑', '', 0, 0, null, null, 2, null, 'sys:api:edit', 1, 1, 1, null, 14, '2024-01-07 11:57:44', null), + (17, '删除', '', 0, 0, null, null, 2, null, 'sys:api:del', 1, 1, 1, null, 14, '2024-01-07 11:57:56', null), + (18, '用户管理', 'SysUser', 0, 0, null, 'sys-user', 1, '/admin/user/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:13', null), + (19, '编辑用户角色', '', 0, 0, null, null, 2, null, 'sys:user:role:edit', 1, 1, 1, null, 18, '2024-01-07 12:04:20', null), + (20, '注销', '', 0, 0, null, null, 2, null, 'sys:user:del', 1, 1, 1, '用户注销 != 用户登出,注销之后用户将从数据库删除', 18, '2024-01-07 02:28:09', null), + (21, '角色管理', 'SysRole', 0, 2, null, 'sys-role', 1, '/admin/role/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:45', null), + (22, '新增', '', 0, 0, null, null, 2, null, 'sys:role:add', 1, 1, 1, null, 21, '2024-01-07 11:58:37', null), + (23, '编辑', '', 0, 0, null, null, 2, null, 'sys:role:edit', 1, 1, 1, null, 21, '2024-01-07 11:58:52', null), + (24, '删除', '', 0, 0, null, null, 2, null, 'sys:role:del', 1, 1, 1, null, 21, '2024-01-07 11:59:07', null), + (25, '编辑角色菜单', '', 0, 0, null, null, 2, null, 'sys:role:menu:edit', 1, 1, 1, null, 21, '2024-01-07 01:59:39', null), + (26, '菜单管理', 'SysMenu', 0, 2, null, 'sys-menu', 1, '/admin/menu/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:45:29', null), + (27, '新增', '', 0, 0, null, null, 2, null, 'sys:menu:add', 1, 1, 1, null, 26, '2024-01-07 12:01:24', null), + (28, '编辑', '', 0, 0, null, null, 2, null, 'sys:menu:edit', 1, 1, 1, null, 26, '2024-01-07 12:01:34', null), + (29, '删除', '', 0, 0, null, null, 2, null, 'sys:menu:del', 1, 1, 1, null, 26, '2024-01-07 12:01:48', null), + (30, '系统监控', 'monitor', 0, 88, 'IconComputer', 'monitor', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:27:08', null), + (31, 'Redis监控', 'Redis', 0, 0, null, 'redis', 1, '/monitor/redis/index.vue', 'sys:monitor:redis', 1, 1, 1, null, 30, '2023-07-27 19:28:03', null), + (32, '服务器监控', 'Server', 0, 0, null, 'server', 1, '/monitor/server/index.vue', 'sys:monitor:server', 1, 1, 1, null, 30, '2023-07-27 19:28:29', null); INSERT INTO fba_test.sys_role (id, name, data_scope, status, remark, created_time, updated_time) VALUES (1, 'test', 2, 1, null, '2023-06-26 17:13:45', null); diff --git a/backend/sql/init_test_data.sql b/backend/sql/init_test_data.sql index a6d17806..d13082c4 100644 --- a/backend/sql/init_test_data.sql +++ b/backend/sql/init_test_data.sql @@ -2,7 +2,7 @@ INSERT INTO fba.sys_dept (id, name, level, sort, leader, phone, email, status, d VALUES (1, 'test', 0, 0, null, null, null, 1, 0, null, '2023-06-26 17:13:45', null); INSERT INTO fba.sys_menu (id, title, name, level, sort, icon, path, menu_type, component, perms, status, `show`, cache, remark, parent_id, created_time, updated_time) -VALUES (1, '测试', 'test', 0, 0, '', null, 0, null, null, 0, 0, 1, null, null, '2023-07-27 19:14:10', '2023-07-27 19:14:52'), +VALUES (1, '测试', 'test', 0, 0, '', null, 0, null, null, 0, 0, 1, null, null, '2023-07-27 19:14:10', null), (2, '仪表盘', 'dashboard', 0, 0, 'IconDashboard', 'dashboard', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:15:45', null), (3, '工作台', 'Workplace', 0, 0, null, 'workplace', 1, '/dashboard/workplace/index.vue', null, 1, 1, 1, null, 2, '2023-07-27 19:17:59', null), (4, 'arco官网', 'arcoWebsite', 0, 888, 'IconLink', 'https://arco.design', 1, null, null, 1, 1, 1, null, null, '2023-07-27 19:19:23', null), @@ -12,13 +12,28 @@ VALUES (1, '测试', 'test', 0, 0, '', null, 0, null, null, 0, 0, 1, null, null (8, '常见问题', 'faq', 0, 999, 'IconQuestion', 'https://arco.design/vue/docs/pro/faq', 1, null, null, 1, 1, 1, null, null, '2023-07-27 19:22:24', null), (9, '系统管理', 'admin', 0, 6, 'IconSettings', 'admin', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:23:00', null), (10, '部门管理', 'SysDept', 0, 0, null, 'sys-dept', 1, '/admin/dept/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:23:42', null), - (11, 'API管理', 'SysApi', 0, 1, null, 'sys-api', 1, '/admin/api/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:24:12', null), - (12, '用户管理', 'SysUser', 0, 0, null, 'sys-user', 1, '/admin/user/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:13', null), - (13, '角色管理', 'SysRole', 0, 2, null, 'sys-role', 1, '/admin/role/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:45', null), - (14, '菜单管理', 'SysMenu', 0, 2, null, 'sys-menu', 1, '/admin/menu/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:45:29', null), - (15, '系统监控', 'monitor', 0, 88, 'IconComputer', 'monitor', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:27:08', null), - (16, 'Redis监控', 'Redis', 0, 0, null, 'redis', 1, '/monitor/redis/index.vue', null, 1, 1, 1, null, 15, '2023-07-27 19:28:03', null), - (17, '服务器监控', 'Server', 0, 0, null, 'server', 1, '/monitor/server/index.vue', null, 1, 1, 1, null, 15, '2023-07-27 19:28:29', null); + (11, '新增', '', 0, 0, null, null, 2, null, 'sys:dept:add', 1, 1, 1, null, 10, '2024-01-07 11:37:00', null), + (12, '编辑', '', 0, 0, null, null, 2, null, 'sys:dept:edit', 1, 1, 1, null, 10, '2024-01-07 11:37:29', null), + (13, '删除', '', 0, 0, null, null, 2, null, 'sys:dept:del', 1, 1, 1, null, 10, '2024-01-07 11:37:44', null), + (14, 'API管理', 'SysApi', 0, 1, null, 'sys-api', 1, '/admin/api/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:24:12', null), + (15, '新增', '', 0, 0, null, null, 2, null, 'sys:api:add', 1, 1, 1, null, 14, '2024-01-07 11:57:09', null), + (16, '编辑', '', 0, 0, null, null, 2, null, 'sys:api:edit', 1, 1, 1, null, 14, '2024-01-07 11:57:44', null), + (17, '删除', '', 0, 0, null, null, 2, null, 'sys:api:del', 1, 1, 1, null, 14, '2024-01-07 11:57:56', null), + (18, '用户管理', 'SysUser', 0, 0, null, 'sys-user', 1, '/admin/user/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:13', null), + (19, '编辑用户角色', '', 0, 0, null, null, 2, null, 'sys:user:role:edit', 1, 1, 1, null, 18, '2024-01-07 12:04:20', null), + (20, '注销', '', 0, 0, null, null, 2, null, 'sys:user:del', 1, 1, 1, '用户注销 != 用户登出,注销之后用户将从数据库删除', 18, '2024-01-07 02:28:09', null), + (21, '角色管理', 'SysRole', 0, 2, null, 'sys-role', 1, '/admin/role/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:45', null), + (22, '新增', '', 0, 0, null, null, 2, null, 'sys:role:add', 1, 1, 1, null, 21, '2024-01-07 11:58:37', null), + (23, '编辑', '', 0, 0, null, null, 2, null, 'sys:role:edit', 1, 1, 1, null, 21, '2024-01-07 11:58:52', null), + (24, '删除', '', 0, 0, null, null, 2, null, 'sys:role:del', 1, 1, 1, null, 21, '2024-01-07 11:59:07', null), + (25, '编辑角色菜单', '', 0, 0, null, null, 2, null, 'sys:role:menu:edit', 1, 1, 1, null, 21, '2024-01-07 01:59:39', null), + (26, '菜单管理', 'SysMenu', 0, 2, null, 'sys-menu', 1, '/admin/menu/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:45:29', null), + (27, '新增', '', 0, 0, null, null, 2, null, 'sys:menu:add', 1, 1, 1, null, 26, '2024-01-07 12:01:24', null), + (28, '编辑', '', 0, 0, null, null, 2, null, 'sys:menu:edit', 1, 1, 1, null, 26, '2024-01-07 12:01:34', null), + (29, '删除', '', 0, 0, null, null, 2, null, 'sys:menu:del', 1, 1, 1, null, 26, '2024-01-07 12:01:48', null), + (30, '系统监控', 'monitor', 0, 88, 'IconComputer', 'monitor', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:27:08', null), + (31, 'Redis监控', 'Redis', 0, 0, null, 'redis', 1, '/monitor/redis/index.vue', 'sys:monitor:redis', 1, 1, 1, null, 30, '2023-07-27 19:28:03', null), + (32, '服务器监控', 'Server', 0, 0, null, 'server', 1, '/monitor/server/index.vue', 'sys:monitor:server', 1, 1, 1, null, 30, '2023-07-27 19:28:29', null); INSERT INTO fba.sys_role (id, name, data_scope, status, remark, created_time, updated_time) VALUES (1, 'test', 2, 1, null, '2023-06-26 17:13:45', null); From 44bf5a65015af504ba6fd5707f6a4a38ce7245bf Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 03:11:18 +0800 Subject: [PATCH 16/22] Add role menu permission description to README --- README.md | 1 + README.zh-CN.md | 1 + backend/app/core/conf.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3af56df9..a9548989 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ See a preview of some of the screenshots - [x] Global SQLAlchemy 2.0 syntax - [x] Pydantic v1 and v2 (different branches) - [x] Casbin RBAC access control model +- [x] Role menu RBAC access control model - [x] Celery asynchronous tasks - [x] JWT middleware whitelist authentication - [x] Global customizable time zone time diff --git a/README.zh-CN.md b/README.zh-CN.md index 60310a00..8a6b099b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -47,6 +47,7 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三 - [x] 全局 SQLAlchemy 2.0 语法 - [x] Pydantic v1 和 v2 (不同分支) - [x] Casbin RBAC 访问控制模型 +- [x] 角色菜单 RBAC 访问控制模型 - [x] Celery 异步任务 - [x] JWT 中间件白名单认证 - [x] 全局自定义时区时间 diff --git a/backend/app/core/conf.py b/backend/app/core/conf.py index 44a619a3..c64d5d84 100644 --- a/backend/app/core/conf.py +++ b/backend/app/core/conf.py @@ -103,7 +103,7 @@ def validate_openapi_url(cls, values): TOKEN_URL_SWAGGER: str = f'{API_V1_STR}/auth/swagger_login' TOKEN_REDIS_PREFIX: str = 'fba_token' TOKEN_REFRESH_REDIS_PREFIX: str = 'fba_refresh_token' - TOKEN_EXCLUDE: list[str] = [ # 路由白名单 + TOKEN_EXCLUDE: list[str] = [ # JWT / RBAC 白名单 f'{API_V1_STR}/auth/login', ] From 1a8356cb21a2c4f725859dece6d82f3a1c4c92e5 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 03:13:58 +0800 Subject: [PATCH 17/22] fix README typo --- README.zh-CN.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/README.zh-CN.md b/README.zh-CN.md index 8a6b099b..2a102a89 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -217,14 +217,9 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三 有且仅有当前一个频道,请注意辨别真伪 - - - - - - - -
直链跳转
Telegram(科学上网)
+| [直链跳转](https://t.me/+ZlPhIFkPp7E4NGI1) | +|----------------------------------------| +| Telegram(科学上网) | ## 赞助我们 From 661a0337097bc6324c5a9c98421aa3b79fd67221 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 03:27:26 +0800 Subject: [PATCH 18/22] Simplify permission dependency injection --- backend/app/api/v1/api.py | 16 ++++++------- backend/app/api/v1/auth/auth.py | 6 ++--- backend/app/api/v1/casbin.py | 36 ++++++++++++++-------------- backend/app/api/v1/dept.py | 14 +++++------ backend/app/api/v1/dict_data.py | 14 +++++------ backend/app/api/v1/dict_type.py | 12 +++++----- backend/app/api/v1/log/login_log.py | 10 ++++---- backend/app/api/v1/log/opera_log.py | 10 ++++---- backend/app/api/v1/menu.py | 16 ++++++------- backend/app/api/v1/mixed/config.py | 4 ++-- backend/app/api/v1/monitor/redis.py | 4 ++-- backend/app/api/v1/monitor/server.py | 4 ++-- backend/app/api/v1/role.py | 22 ++++++++--------- backend/app/api/v1/task.py | 10 ++++---- backend/app/api/v1/user.py | 30 +++++++++++------------ backend/app/common/jwt.py | 5 ++-- backend/app/common/permission.py | 2 +- backend/app/common/rbac.py | 6 +++-- 18 files changed, 112 insertions(+), 109 deletions(-) diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py index 548d8b5f..09aa5084 100644 --- a/backend/app/api/v1/api.py +++ b/backend/app/api/v1/api.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import DependsPagination, paging_data from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.api import CreateApi, GetAllApi, UpdateApi @@ -16,13 +16,13 @@ router = APIRouter() -@router.get('/all', summary='获取所有接口', dependencies=[Depends(jwt_auth)]) +@router.get('/all', summary='获取所有接口', dependencies=[DependsJwtAuth]) async def get_all_apis(): data = await ApiService.get_all() return await response_base.success(data=data) -@router.get('/{pk}', summary='获取接口详情', dependencies=[Depends(jwt_auth)]) +@router.get('/{pk}', summary='获取接口详情', dependencies=[DependsJwtAuth]) async def get_api(pk: int): api = await ApiService.get(pk=pk) return await response_base.success(data=api) @@ -32,7 +32,7 @@ async def get_api(pk: int): '', summary='(模糊条件)分页获取所有接口', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, DependsPagination, ], ) @@ -51,7 +51,7 @@ async def get_api_list( '', summary='创建接口', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:api:add')), ], ) @@ -64,7 +64,7 @@ async def create_api(obj: CreateApi): '/{pk}', summary='更新接口', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:api:edit')), ], ) @@ -79,7 +79,7 @@ async def update_api(pk: int, obj: UpdateApi): '', summary='(批量)删除接口', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:api:del')), ], ) diff --git a/backend/app/api/v1/auth/auth.py b/backend/app/api/v1/auth/auth.py index 79cbfc75..f7d53e23 100644 --- a/backend/app/api/v1/auth/auth.py +++ b/backend/app/api/v1/auth/auth.py @@ -7,7 +7,7 @@ from fastapi_limiter.depends import RateLimiter from starlette.background import BackgroundTasks -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.response.response_schema import response_base from backend.app.schemas.token import GetLoginToken, GetNewToken, GetSwaggerToken from backend.app.schemas.user import AuthLogin @@ -47,7 +47,7 @@ async def user_login(request: Request, obj: AuthLogin, background_tasks: Backgro return await response_base.success(data=data) -@router.post('/new_token', summary='创建新 token', dependencies=[Depends(jwt_auth)]) +@router.post('/new_token', summary='创建新 token', dependencies=[DependsJwtAuth]) async def create_new_token(request: Request, refresh_token: Annotated[str, Query(...)]): ( new_access_token, @@ -64,7 +64,7 @@ async def create_new_token(request: Request, refresh_token: Annotated[str, Query return await response_base.success(data=data) -@router.post('/logout', summary='用户登出', dependencies=[Depends(jwt_auth)]) +@router.post('/logout', summary='用户登出', dependencies=[DependsJwtAuth]) async def user_logout(request: Request): await AuthService.logout(request=request) return await response_base.success() diff --git a/backend/app/api/v1/casbin.py b/backend/app/api/v1/casbin.py index 35b7c214..c9b5a72f 100644 --- a/backend/app/api/v1/casbin.py +++ b/backend/app/api/v1/casbin.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, Path, Query -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import DependsPagination, paging_data from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.casbin_rule import ( @@ -28,7 +28,7 @@ '', summary='(模糊条件)分页获取所有权限规则', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, DependsPagination, ], ) @@ -42,13 +42,13 @@ async def get_all_casbin( return await response_base.success(data=page_data) -@router.get('/policy', summary='获取所有P权限规则', dependencies=[Depends(jwt_auth)]) +@router.get('/policy', summary='获取所有P权限规则', dependencies=[DependsJwtAuth]) async def get_all_policies(): policies = await CasbinService.get_policy_list() return await response_base.success(data=policies) -@router.get('/policy/{role}/all', summary='获取指定角色的所有P权限规则', dependencies=[Depends(jwt_auth)]) +@router.get('/policy/{role}/all', summary='获取指定角色的所有P权限规则', dependencies=[DependsJwtAuth]) async def get_role_policies(role: Annotated[str, Path(description='角色ID')]): policies = await CasbinService.get_policy_list_by_role(role=role) return await response_base.success(data=policies) @@ -58,7 +58,7 @@ async def get_role_policies(role: Annotated[str, Path(description='角色ID')]): '/policy', summary='添加P权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:p:add')), ], ) @@ -80,7 +80,7 @@ async def create_policy(p: CreatePolicy): '/policies', summary='添加多组P权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:p:group:add')), ], ) @@ -93,7 +93,7 @@ async def create_policies(ps: list[CreatePolicy]): '/policy', summary='更新P权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:p:edit')), ], ) @@ -106,7 +106,7 @@ async def update_policy(old: UpdatePolicy, new: UpdatePolicy): '/policies', summary='更新多组P权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:p:group:edit')), ], ) @@ -119,7 +119,7 @@ async def update_policies(old: list[UpdatePolicy], new: list[UpdatePolicy]): '/policy', summary='删除P权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:p:del')), ], ) @@ -132,7 +132,7 @@ async def delete_policy(p: DeletePolicy): '/policies', summary='删除多组P权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:p:group:del')), ], ) @@ -145,7 +145,7 @@ async def delete_policies(ps: list[DeletePolicy]): '/policies/all', summary='删除所有P权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:p:empty')), ], ) @@ -156,7 +156,7 @@ async def delete_all_policies(sub: DeleteAllPolicies): return await response_base.fail() -@router.get('/group', summary='获取所有G权限规则', dependencies=[Depends(jwt_auth)]) +@router.get('/group', summary='获取所有G权限规则', dependencies=[DependsJwtAuth]) async def get_all_groups(): data = await CasbinService.get_group_list() return await response_base.success(data=data) @@ -166,7 +166,7 @@ async def get_all_groups(): '/group', summary='添加G权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:g:add')), ], ) @@ -188,7 +188,7 @@ async def create_group(g: CreateUserRole): '/groups', summary='添加多组G权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:g:group:add')), ], ) @@ -201,7 +201,7 @@ async def create_groups(gs: list[CreateUserRole]): '/group', summary='删除G权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:g:del')), ], ) @@ -214,7 +214,7 @@ async def delete_group(g: DeleteUserRole): '/groups', summary='删除多组G权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:g:group:del')), ], ) @@ -227,7 +227,7 @@ async def delete_groups(gs: list[DeleteUserRole]): '/groups/all', summary='删除所有G权限规则', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('casbin:g:empty')), ], ) diff --git a/backend/app/api/v1/dept.py b/backend/app/api/v1/dept.py index 1b99c693..a6e8e094 100644 --- a/backend/app/api/v1/dept.py +++ b/backend/app/api/v1/dept.py @@ -4,9 +4,9 @@ from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.schemas.dept import CreateDept, GetAllDept, UpdateDept from backend.app.services.dept_service import DeptService @@ -15,14 +15,14 @@ router = APIRouter() -@router.get('/{pk}', summary='获取部门详情', dependencies=[Depends(jwt_auth)]) +@router.get('/{pk}', summary='获取部门详情', dependencies=[DependsJwtAuth]) async def get_dept(pk: int): dept = await DeptService.get(pk=pk) data = GetAllDept(**await select_as_dict(dept)) return await response_base.success(data=data) -@router.get('', summary='获取所有部门展示树', dependencies=[Depends(jwt_auth)]) +@router.get('', summary='获取所有部门展示树', dependencies=[DependsJwtAuth]) async def get_all_depts( name: Annotated[str | None, Query()] = None, leader: Annotated[str | None, Query()] = None, @@ -37,7 +37,7 @@ async def get_all_depts( '', summary='创建部门', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dept:add')), ], ) @@ -50,7 +50,7 @@ async def create_dept(obj: CreateDept): '/{pk}', summary='更新部门', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dept:edit')), ], ) @@ -65,7 +65,7 @@ async def update_dept(pk: int, obj: UpdateDept): '{pk}', summary='删除部门', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dept:del')), ], ) diff --git a/backend/app/api/v1/dict_data.py b/backend/app/api/v1/dict_data.py index 44b3ac0c..00f0e0f5 100644 --- a/backend/app/api/v1/dict_data.py +++ b/backend/app/api/v1/dict_data.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import DependsPagination, paging_data from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.dict_data import CreateDictData, GetAllDictData, UpdateDictData @@ -17,7 +17,7 @@ router = APIRouter() -@router.get('/{pk}', summary='获取字典详情', dependencies=[Depends(jwt_auth)]) +@router.get('/{pk}', summary='获取字典详情', dependencies=[DependsJwtAuth]) async def get_dict_data(pk: int): dict_data = await DictDataService.get(pk=pk) data = GetAllDictData(**await select_as_dict(dict_data)) @@ -28,7 +28,7 @@ async def get_dict_data(pk: int): '', summary='(模糊条件)分页获取所有字典', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, DependsPagination, ], ) @@ -47,7 +47,7 @@ async def get_all_dict_datas( '', summary='创建字典', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dict:data:add')), ], ) @@ -60,7 +60,7 @@ async def create_dict_data(obj: CreateDictData): '/{pk}', summary='更新字典', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dict:data:edit')), ], ) @@ -75,7 +75,7 @@ async def update_dict_data(pk: int, obj: UpdateDictData): '', summary='(批量)删除字典', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dict:data:del')), ], ) diff --git a/backend/app/api/v1/dict_type.py b/backend/app/api/v1/dict_type.py index a93fd41a..51fe1efe 100644 --- a/backend/app/api/v1/dict_type.py +++ b/backend/app/api/v1/dict_type.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import DependsPagination, paging_data from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.dict_type import CreateDictType, GetAllDictType, UpdateDictType @@ -20,7 +20,7 @@ '', summary='(模糊条件)分页获取所有字典类型', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, DependsPagination, ], ) @@ -39,7 +39,7 @@ async def get_all_dict_types( '', summary='创建字典类型', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dict:type:add')), ], ) @@ -52,7 +52,7 @@ async def create_dict_type(obj: CreateDictType): '/{pk}', summary='更新字典类型', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dict:type:edit')), ], ) @@ -67,7 +67,7 @@ async def update_dict_type(pk: int, obj: UpdateDictType): '', summary='(批量)删除字典类型', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:dict:type:del')), ], ) diff --git a/backend/app/api/v1/log/login_log.py b/backend/app/api/v1/log/login_log.py index 1e6b3b49..94d0cd12 100644 --- a/backend/app/api/v1/log/login_log.py +++ b/backend/app/api/v1/log/login_log.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import DependsPagination, paging_data from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.login_log import GetAllLoginLog @@ -20,7 +20,7 @@ '', summary='(模糊条件)分页获取登录日志', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, DependsPagination, ], ) @@ -39,7 +39,7 @@ async def get_all_login_logs( '', summary='(批量)删除登录日志', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('log:login:del')), ], ) @@ -54,7 +54,7 @@ async def delete_login_log(pk: Annotated[list[int], Query(...)]): '/all', summary='清空登录日志', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('log:login:empty')), ], ) diff --git a/backend/app/api/v1/log/opera_log.py b/backend/app/api/v1/log/opera_log.py index 8dae87d2..4d31dc18 100644 --- a/backend/app/api/v1/log/opera_log.py +++ b/backend/app/api/v1/log/opera_log.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, Query -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import DependsPagination, paging_data from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.opera_log import GetAllOperaLog @@ -20,7 +20,7 @@ '', summary='(模糊条件)分页获取操作日志', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, DependsPagination, ], ) @@ -39,7 +39,7 @@ async def get_all_opera_logs( '', summary='(批量)删除操作日志', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('log:opera:del')), ], ) @@ -54,7 +54,7 @@ async def delete_opera_log(pk: Annotated[list[int], Query(...)]): '/all', summary='清空操作日志', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('log:opera:empty')), ], ) diff --git a/backend/app/api/v1/menu.py b/backend/app/api/v1/menu.py index 050502ed..4ede228b 100644 --- a/backend/app/api/v1/menu.py +++ b/backend/app/api/v1/menu.py @@ -4,9 +4,9 @@ from fastapi import APIRouter, Depends, Query, Request -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.schemas.menu import CreateMenu, GetAllMenu, UpdateMenu from backend.app.services.menu_service import MenuService @@ -15,20 +15,20 @@ router = APIRouter() -@router.get('/sidebar', summary='获取用户菜单展示树', dependencies=[Depends(jwt_auth)]) +@router.get('/sidebar', summary='获取用户菜单展示树', dependencies=[DependsJwtAuth]) async def get_user_menus(request: Request): menu = await MenuService.get_user_menu_tree(request=request) return await response_base.success(data=menu) -@router.get('/{pk}', summary='获取菜单详情', dependencies=[Depends(jwt_auth)]) +@router.get('/{pk}', summary='获取菜单详情', dependencies=[DependsJwtAuth]) async def get_menu(pk: int): menu = await MenuService.get(pk=pk) data = GetAllMenu(**await select_as_dict(menu)) return await response_base.success(data=data) -@router.get('', summary='获取所有菜单展示树', dependencies=[Depends(jwt_auth)]) +@router.get('', summary='获取所有菜单展示树', dependencies=[DependsJwtAuth]) async def get_all_menus( title: Annotated[str | None, Query()] = None, status: Annotated[int | None, Query()] = None, @@ -41,7 +41,7 @@ async def get_all_menus( '', summary='创建菜单', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:menu:add')), ], ) @@ -54,7 +54,7 @@ async def create_menu(obj: CreateMenu): '/{pk}', summary='更新菜单', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:menu:edit')), ], ) @@ -69,7 +69,7 @@ async def update_menu(pk: int, obj: UpdateMenu): '/{pk}', summary='删除菜单', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:menu:del')), ], ) diff --git a/backend/app/api/v1/mixed/config.py b/backend/app/api/v1/mixed/config.py index d603b4f2..3ac58fbf 100644 --- a/backend/app/api/v1/mixed/config.py +++ b/backend/app/api/v1/mixed/config.py @@ -4,7 +4,7 @@ from fastapi.routing import APIRoute from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base router = APIRouter() @@ -14,7 +14,7 @@ '/routers', summary='获取所有路由', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:route:list')), ], ) diff --git a/backend/app/api/v1/monitor/redis.py b/backend/app/api/v1/monitor/redis.py index bfcc25b5..3aa097c1 100644 --- a/backend/app/api/v1/monitor/redis.py +++ b/backend/app/api/v1/monitor/redis.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from fastapi import APIRouter, Depends -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.permission import RequestPermission from backend.app.common.response.response_schema import response_base from backend.app.utils.redis_info import redis_info @@ -14,7 +14,7 @@ '/redis', summary='redis 监控', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, Depends(RequestPermission('sys:monitor:redis')), ], ) diff --git a/backend/app/api/v1/monitor/server.py b/backend/app/api/v1/monitor/server.py index be49773e..05a6357a 100644 --- a/backend/app/api/v1/monitor/server.py +++ b/backend/app/api/v1/monitor/server.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends from starlette.concurrency import run_in_threadpool -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.permission import RequestPermission from backend.app.common.response.response_schema import response_base from backend.app.utils.server_info import server_info @@ -15,7 +15,7 @@ '/server', summary='server 监控', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, Depends(RequestPermission('sys:monitor:server')), ], ) diff --git a/backend/app/api/v1/role.py b/backend/app/api/v1/role.py index 360cf57c..fbfd4fb1 100644 --- a/backend/app/api/v1/role.py +++ b/backend/app/api/v1/role.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, Query, Request -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import DependsPagination, paging_data from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.role import CreateRole, GetAllRole, UpdateRole, UpdateRoleMenu @@ -18,27 +18,27 @@ router = APIRouter() -@router.get('/all', summary='获取所有角色', dependencies=[Depends(jwt_auth)]) +@router.get('/all', summary='获取所有角色', dependencies=[DependsJwtAuth]) async def get_all_roles(): roles = await RoleService.get_all() data = await select_list_serialize(roles) return await response_base.success(data=data) -@router.get('/{pk}/all', summary='获取用户所有角色', dependencies=[Depends(jwt_auth)]) +@router.get('/{pk}/all', summary='获取用户所有角色', dependencies=[DependsJwtAuth]) async def get_user_all_roles(pk: int): roles = await RoleService.get_user_all(pk=pk) data = await select_list_serialize(roles) return await response_base.success(data=data) -@router.get('/{pk}/menus', summary='获取角色所有菜单', dependencies=[Depends(jwt_auth)]) +@router.get('/{pk}/menus', summary='获取角色所有菜单', dependencies=[DependsJwtAuth]) async def get_role_all_menus(pk: int): menu = await MenuService.get_role_menu_tree(pk=pk) return await response_base.success(data=menu) -@router.get('/{pk}', summary='获取角色详情', dependencies=[Depends(jwt_auth)]) +@router.get('/{pk}', summary='获取角色详情', dependencies=[DependsJwtAuth]) async def get_role(pk: int): role = await RoleService.get(pk=pk) data = GetAllRole(**await select_as_dict(role)) @@ -49,7 +49,7 @@ async def get_role(pk: int): '', summary='(模糊条件)分页获取所有角色', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, DependsPagination, ], ) @@ -68,7 +68,7 @@ async def get_all_role_list( '', summary='创建角色', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:role:add')), ], ) @@ -81,7 +81,7 @@ async def create_role(obj: CreateRole): '/{pk}', summary='更新角色', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:role:edit')), ], ) @@ -96,7 +96,7 @@ async def update_role(pk: int, obj: UpdateRole): '/{pk}/menu', summary='更新角色菜单', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:role:menu:edit')), ], ) @@ -111,7 +111,7 @@ async def update_role_menu(request: Request, pk: int, menu_ids: UpdateRoleMenu): '', summary='(批量)删除角色', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:role:del')), ], ) diff --git a/backend/app/api/v1/task.py b/backend/app/api/v1/task.py index 317277dd..ee6c75b9 100644 --- a/backend/app/api/v1/task.py +++ b/backend/app/api/v1/task.py @@ -4,9 +4,9 @@ from fastapi import APIRouter, Body, Depends, Path -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_code import CustomResponseCode from backend.app.common.response.response_schema import response_base from backend.app.services.task_service import TaskService @@ -14,13 +14,13 @@ router = APIRouter() -@router.get('', summary='获取所有可执行任务模块', dependencies=[Depends(jwt_auth)]) +@router.get('', summary='获取所有可执行任务模块', dependencies=[DependsJwtAuth]) async def get_all_tasks(): tasks = TaskService.gets() return await response_base.success(data=tasks) -@router.get('/{pk}', summary='获取任务结果', dependencies=[Depends(jwt_auth)]) +@router.get('/{pk}', summary='获取任务结果', dependencies=[DependsJwtAuth]) async def get_task_result(pk: str = Path(description='任务ID')): task = TaskService.get(pk) if not task: @@ -32,7 +32,7 @@ async def get_task_result(pk: str = Path(description='任务ID')): '/{module}', summary='执行任务', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:task:run')), ], ) diff --git a/backend/app/api/v1/user.py b/backend/app/api/v1/user.py index 8ccb4ee7..ca1747ea 100644 --- a/backend/app/api/v1/user.py +++ b/backend/app/api/v1/user.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Depends, Query, Request -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.pagination import DependsPagination, paging_data from backend.app.common.permission import RequestPermission -from backend.app.common.rbac import RBAC +from backend.app.common.rbac import DependsRBAC from backend.app.common.response.response_schema import response_base from backend.app.database.db_mysql import CurrentSession from backend.app.schemas.user import ( @@ -32,7 +32,7 @@ async def user_register(obj: RegisterUser): return await response_base.success() -@router.post('/add', summary='添加用户', dependencies=[Depends(RBAC.rbac_verify)]) +@router.post('/add', summary='添加用户', dependencies=[DependsRBAC]) async def add_user(request: Request, obj: AddUser): await UserService.add(request=request, obj=obj) current_user = await UserService.get_userinfo(username=obj.username) @@ -40,7 +40,7 @@ async def add_user(request: Request, obj: AddUser): return await response_base.success(data=data) -@router.post('/password/reset', summary='密码重置', dependencies=[Depends(jwt_auth)]) +@router.post('/password/reset', summary='密码重置', dependencies=[DependsJwtAuth]) async def password_reset(request: Request, obj: ResetPassword): count = await UserService.pwd_reset(request=request, obj=obj) if count > 0: @@ -48,20 +48,20 @@ async def password_reset(request: Request, obj: ResetPassword): return await response_base.fail() -@router.get('/me', summary='获取当前用户信息', dependencies=[Depends(jwt_auth)]) +@router.get('/me', summary='获取当前用户信息', dependencies=[DependsJwtAuth]) async def get_current_userinfo(request: Request): data = GetCurrentUserInfo(**await select_as_dict(request.user)) return await response_base.success(data=data, exclude={'password'}) -@router.get('/{username}', summary='查看用户信息', dependencies=[Depends(jwt_auth)]) +@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtAuth]) async def get_user(username: str): current_user = await UserService.get_userinfo(username=username) data = GetAllUserInfo(**await select_as_dict(current_user)) return await response_base.success(data=data) -@router.put('/{username}', summary='更新用户信息', dependencies=[Depends(jwt_auth)]) +@router.put('/{username}', summary='更新用户信息', dependencies=[DependsJwtAuth]) async def update_userinfo(request: Request, username: str, obj: UpdateUser): count = await UserService.update(request=request, username=username, obj=obj) if count > 0: @@ -73,7 +73,7 @@ async def update_userinfo(request: Request, username: str, obj: UpdateUser): '/{username}/role', summary='更新用户角色', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:user:role:edit')), ], ) @@ -82,7 +82,7 @@ async def update_user_role(request: Request, username: str, obj: UpdateUserRole) return await response_base.success() -@router.put('/{username}/avatar', summary='更新头像', dependencies=[Depends(jwt_auth)]) +@router.put('/{username}/avatar', summary='更新头像', dependencies=[DependsJwtAuth]) async def update_avatar(request: Request, username: str, avatar: Avatar): count = await UserService.update_avatar(request=request, username=username, avatar=avatar) if count > 0: @@ -94,7 +94,7 @@ async def update_avatar(request: Request, username: str, avatar: Avatar): '', summary='(模糊条件)分页获取所有用户', dependencies=[ - Depends(jwt_auth), + DependsJwtAuth, DependsPagination, ], ) @@ -110,7 +110,7 @@ async def get_all_users( return await response_base.success(data=page_data) -@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[DependsRBAC]) async def super_set(request: Request, pk: int): count = await UserService.update_permission(request=request, pk=pk) if count > 0: @@ -118,7 +118,7 @@ async def super_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/staff', summary='修改用户后台登录权限', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put('/{pk}/staff', summary='修改用户后台登录权限', dependencies=[DependsRBAC]) async def staff_set(request: Request, pk: int): count = await UserService.update_staff(request=request, pk=pk) if count > 0: @@ -126,7 +126,7 @@ async def staff_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/status', summary='修改用户状态', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put('/{pk}/status', summary='修改用户状态', dependencies=[DependsRBAC]) async def status_set(request: Request, pk: int): count = await UserService.update_status(request=request, pk=pk) if count > 0: @@ -134,7 +134,7 @@ async def status_set(request: Request, pk: int): return await response_base.fail() -@router.put('/{pk}/multi', summary='修改用户多点登录状态', dependencies=[Depends(RBAC.rbac_verify)]) +@router.put('/{pk}/multi', summary='修改用户多点登录状态', dependencies=[DependsRBAC]) async def multi_set(request: Request, pk: int): count = await UserService.update_multi_login(request=request, pk=pk) if count > 0: @@ -147,7 +147,7 @@ async def multi_set(request: Request, pk: int): summary='用户注销', description='用户注销 != 用户登出,注销之后用户将从数据库删除', dependencies=[ - Depends(RBAC.rbac_verify), + DependsRBAC, Depends(RequestPermission('sys:user:del')), ], ) diff --git a/backend/app/common/jwt.py b/backend/app/common/jwt.py index f820e257..e23ce39b 100644 --- a/backend/app/common/jwt.py +++ b/backend/app/common/jwt.py @@ -3,7 +3,7 @@ from datetime import datetime, timedelta from asgiref.sync import sync_to_async -from fastapi import Request +from fastapi import Depends, Request from fastapi.security import HTTPBearer, OAuth2PasswordBearer from fastapi.security.utils import get_authorization_scheme_param from jose import jwt @@ -22,7 +22,8 @@ # Deprecated, may be enabled when oauth2 is actually integrated oauth2_schema = OAuth2PasswordBearer(tokenUrl=settings.TOKEN_URL_SWAGGER) -jwt_auth = HTTPBearer() +# JWT authorizes dependency injection +DependsJwtAuth = Depends(HTTPBearer()) @sync_to_async diff --git a/backend/app/common/permission.py b/backend/app/common/permission.py index b506118f..04521efc 100644 --- a/backend/app/common/permission.py +++ b/backend/app/common/permission.py @@ -11,7 +11,7 @@ class RequestPermission: 请求权限,仅用于角色菜单RBAC Tip: - 使用此请求权限时,需要将 `Depends(RBAC.rbac_verify)` 在 `Depends(RequestPermission('xxx'))` 之前设置, + 使用此请求权限时,需要将 `DependsRBAC` 在 `Depends(RequestPermission('xxx'))` 之前设置, 这是因为 fastapi 当前版本的依赖导入顺序为逆向导入,意味着 RBAC 标识会在验证前被设置 """ diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index 290039bd..9a7218b0 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -7,7 +7,7 @@ from backend.app.common.enums import StatusType from backend.app.common.exception.errors import AuthorizationError, TokenError -from backend.app.common.jwt import jwt_auth +from backend.app.common.jwt import DependsJwtAuth from backend.app.common.redis import redis_client from backend.app.core.conf import settings from backend.app.database.db_mysql import async_engine @@ -45,7 +45,7 @@ async def enforcer() -> casbin.AsyncEnforcer: await enforcer.load_policy() return enforcer - async def rbac_verify(self, request: Request, _token: str = Depends(jwt_auth)) -> None: + async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> None: """ RBAC 权限校验 @@ -133,3 +133,5 @@ async def rbac_verify(self, request: Request, _token: str = Depends(jwt_auth)) - RBAC = RBAC() +# RBAC 授权依赖注入 +DependsRBAC = Depends(RBAC.rbac_verify) From 42a4a4bbd1f6fba1ff4bbac160659beca227542f Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 04:23:51 +0800 Subject: [PATCH 19/22] Fix menu authorization store --- backend/app/common/rbac.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index 9a7218b0..ea6b268f 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -90,14 +90,15 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N if user_menus: for menu in user_menus: perms = menu.perms - if menu.status == StatusType.enable: - user_menu_perms.extend(perms.split(',')) - else: - user_forbid_menu_perms.extend(perms.split(',')) - await redis_client.rset( + if perms: + if menu.status == StatusType.enable: + user_menu_perms.extend(perms.split(',')) + else: + user_forbid_menu_perms.extend(perms.split(',')) + await redis_client.set( f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:enable', ','.join(user_menu_perms) ) - await redis_client.rset( + await redis_client.set( f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:disable', ','.join(user_forbid_menu_perms) ) if path_auth_perm in user_forbid_menu_perms: @@ -117,9 +118,10 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N if user_menus: for menu in user_menus: perms = menu.perms - if menu.status == StatusType.disable: - user_forbid_menu_perms.extend(perms.split(',')) - await redis_client.rset( + if perms: + if menu.status == StatusType.disable: + user_forbid_menu_perms.extend(perms.split(',')) + await redis_client.set( f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:disable', ','.join(user_forbid_menu_perms) ) if path_auth_perm in user_forbid_menu_perms: From ca9fcfde905037815d8ba634140669d539765885 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 04:47:38 +0800 Subject: [PATCH 20/22] Fix interface permission dependency order --- backend/app/api/v1/api.py | 6 +++--- backend/app/api/v1/casbin.py | 24 ++++++++++++------------ backend/app/api/v1/dept.py | 6 +++--- backend/app/api/v1/dict_data.py | 6 +++--- backend/app/api/v1/dict_type.py | 6 +++--- backend/app/api/v1/log/login_log.py | 4 ++-- backend/app/api/v1/log/opera_log.py | 4 ++-- backend/app/api/v1/menu.py | 6 +++--- backend/app/api/v1/mixed/config.py | 2 +- backend/app/api/v1/monitor/redis.py | 2 +- backend/app/api/v1/monitor/server.py | 2 +- backend/app/api/v1/role.py | 8 ++++---- backend/app/api/v1/task.py | 2 +- backend/app/api/v1/user.py | 4 ++-- backend/app/common/permission.py | 4 ++-- 15 files changed, 43 insertions(+), 43 deletions(-) diff --git a/backend/app/api/v1/api.py b/backend/app/api/v1/api.py index 09aa5084..4b1a1e27 100644 --- a/backend/app/api/v1/api.py +++ b/backend/app/api/v1/api.py @@ -51,8 +51,8 @@ async def get_api_list( '', summary='创建接口', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:api:add')), + DependsRBAC, ], ) async def create_api(obj: CreateApi): @@ -64,8 +64,8 @@ async def create_api(obj: CreateApi): '/{pk}', summary='更新接口', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:api:edit')), + DependsRBAC, ], ) async def update_api(pk: int, obj: UpdateApi): @@ -79,8 +79,8 @@ async def update_api(pk: int, obj: UpdateApi): '', summary='(批量)删除接口', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:api:del')), + DependsRBAC, ], ) async def delete_api(pk: Annotated[list[int], Query(...)]): diff --git a/backend/app/api/v1/casbin.py b/backend/app/api/v1/casbin.py index c9b5a72f..75672b0a 100644 --- a/backend/app/api/v1/casbin.py +++ b/backend/app/api/v1/casbin.py @@ -58,8 +58,8 @@ async def get_role_policies(role: Annotated[str, Path(description='角色ID')]): '/policy', summary='添加P权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:p:add')), + DependsRBAC, ], ) async def create_policy(p: CreatePolicy): @@ -80,8 +80,8 @@ async def create_policy(p: CreatePolicy): '/policies', summary='添加多组P权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:p:group:add')), + DependsRBAC, ], ) async def create_policies(ps: list[CreatePolicy]): @@ -93,8 +93,8 @@ async def create_policies(ps: list[CreatePolicy]): '/policy', summary='更新P权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:p:edit')), + DependsRBAC, ], ) async def update_policy(old: UpdatePolicy, new: UpdatePolicy): @@ -106,8 +106,8 @@ async def update_policy(old: UpdatePolicy, new: UpdatePolicy): '/policies', summary='更新多组P权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:p:group:edit')), + DependsRBAC, ], ) async def update_policies(old: list[UpdatePolicy], new: list[UpdatePolicy]): @@ -119,8 +119,8 @@ async def update_policies(old: list[UpdatePolicy], new: list[UpdatePolicy]): '/policy', summary='删除P权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:p:del')), + DependsRBAC, ], ) async def delete_policy(p: DeletePolicy): @@ -132,8 +132,8 @@ async def delete_policy(p: DeletePolicy): '/policies', summary='删除多组P权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:p:group:del')), + DependsRBAC, ], ) async def delete_policies(ps: list[DeletePolicy]): @@ -145,8 +145,8 @@ async def delete_policies(ps: list[DeletePolicy]): '/policies/all', summary='删除所有P权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:p:empty')), + DependsRBAC, ], ) async def delete_all_policies(sub: DeleteAllPolicies): @@ -166,8 +166,8 @@ async def get_all_groups(): '/group', summary='添加G权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:g:add')), + DependsRBAC, ], ) async def create_group(g: CreateUserRole): @@ -188,8 +188,8 @@ async def create_group(g: CreateUserRole): '/groups', summary='添加多组G权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:g:group:add')), + DependsRBAC, ], ) async def create_groups(gs: list[CreateUserRole]): @@ -201,8 +201,8 @@ async def create_groups(gs: list[CreateUserRole]): '/group', summary='删除G权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:g:del')), + DependsRBAC, ], ) async def delete_group(g: DeleteUserRole): @@ -214,8 +214,8 @@ async def delete_group(g: DeleteUserRole): '/groups', summary='删除多组G权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:g:group:del')), + DependsRBAC, ], ) async def delete_groups(gs: list[DeleteUserRole]): @@ -227,8 +227,8 @@ async def delete_groups(gs: list[DeleteUserRole]): '/groups/all', summary='删除所有G权限规则', dependencies=[ - DependsRBAC, Depends(RequestPermission('casbin:g:empty')), + DependsRBAC, ], ) async def delete_all_groups(uuid: str): diff --git a/backend/app/api/v1/dept.py b/backend/app/api/v1/dept.py index a6e8e094..f3ba41e8 100644 --- a/backend/app/api/v1/dept.py +++ b/backend/app/api/v1/dept.py @@ -37,8 +37,8 @@ async def get_all_depts( '', summary='创建部门', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dept:add')), + DependsRBAC, ], ) async def create_dept(obj: CreateDept): @@ -50,8 +50,8 @@ async def create_dept(obj: CreateDept): '/{pk}', summary='更新部门', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dept:edit')), + DependsRBAC, ], ) async def update_dept(pk: int, obj: UpdateDept): @@ -65,8 +65,8 @@ async def update_dept(pk: int, obj: UpdateDept): '{pk}', summary='删除部门', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dept:del')), + DependsRBAC, ], ) async def delete_dept(pk: int): diff --git a/backend/app/api/v1/dict_data.py b/backend/app/api/v1/dict_data.py index 00f0e0f5..af27b2a9 100644 --- a/backend/app/api/v1/dict_data.py +++ b/backend/app/api/v1/dict_data.py @@ -47,8 +47,8 @@ async def get_all_dict_datas( '', summary='创建字典', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dict:data:add')), + DependsRBAC, ], ) async def create_dict_data(obj: CreateDictData): @@ -60,8 +60,8 @@ async def create_dict_data(obj: CreateDictData): '/{pk}', summary='更新字典', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dict:data:edit')), + DependsRBAC, ], ) async def update_dict_data(pk: int, obj: UpdateDictData): @@ -75,8 +75,8 @@ async def update_dict_data(pk: int, obj: UpdateDictData): '', summary='(批量)删除字典', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dict:data:del')), + DependsRBAC, ], ) async def delete_dict_data(pk: Annotated[list[int], Query(...)]): diff --git a/backend/app/api/v1/dict_type.py b/backend/app/api/v1/dict_type.py index 51fe1efe..e3469949 100644 --- a/backend/app/api/v1/dict_type.py +++ b/backend/app/api/v1/dict_type.py @@ -39,8 +39,8 @@ async def get_all_dict_types( '', summary='创建字典类型', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dict:type:add')), + DependsRBAC, ], ) async def create_dict_type(obj: CreateDictType): @@ -52,8 +52,8 @@ async def create_dict_type(obj: CreateDictType): '/{pk}', summary='更新字典类型', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dict:type:edit')), + DependsRBAC, ], ) async def update_dict_type(pk: int, obj: UpdateDictType): @@ -67,8 +67,8 @@ async def update_dict_type(pk: int, obj: UpdateDictType): '', summary='(批量)删除字典类型', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:dict:type:del')), + DependsRBAC, ], ) async def delete_dict_type(pk: Annotated[list[int], Query(...)]): diff --git a/backend/app/api/v1/log/login_log.py b/backend/app/api/v1/log/login_log.py index 94d0cd12..70cd3a5c 100644 --- a/backend/app/api/v1/log/login_log.py +++ b/backend/app/api/v1/log/login_log.py @@ -39,8 +39,8 @@ async def get_all_login_logs( '', summary='(批量)删除登录日志', dependencies=[ - DependsRBAC, Depends(RequestPermission('log:login:del')), + DependsRBAC, ], ) async def delete_login_log(pk: Annotated[list[int], Query(...)]): @@ -54,8 +54,8 @@ async def delete_login_log(pk: Annotated[list[int], Query(...)]): '/all', summary='清空登录日志', dependencies=[ - DependsRBAC, Depends(RequestPermission('log:login:empty')), + DependsRBAC, ], ) async def delete_all_login_logs(): diff --git a/backend/app/api/v1/log/opera_log.py b/backend/app/api/v1/log/opera_log.py index 4d31dc18..922ec43d 100644 --- a/backend/app/api/v1/log/opera_log.py +++ b/backend/app/api/v1/log/opera_log.py @@ -39,8 +39,8 @@ async def get_all_opera_logs( '', summary='(批量)删除操作日志', dependencies=[ - DependsRBAC, Depends(RequestPermission('log:opera:del')), + DependsRBAC, ], ) async def delete_opera_log(pk: Annotated[list[int], Query(...)]): @@ -54,8 +54,8 @@ async def delete_opera_log(pk: Annotated[list[int], Query(...)]): '/all', summary='清空操作日志', dependencies=[ - DependsRBAC, Depends(RequestPermission('log:opera:empty')), + DependsRBAC, ], ) async def delete_all_opera_logs(): diff --git a/backend/app/api/v1/menu.py b/backend/app/api/v1/menu.py index 4ede228b..04dd9191 100644 --- a/backend/app/api/v1/menu.py +++ b/backend/app/api/v1/menu.py @@ -41,8 +41,8 @@ async def get_all_menus( '', summary='创建菜单', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:menu:add')), + DependsRBAC, ], ) async def create_menu(obj: CreateMenu): @@ -54,8 +54,8 @@ async def create_menu(obj: CreateMenu): '/{pk}', summary='更新菜单', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:menu:edit')), + DependsRBAC, ], ) async def update_menu(pk: int, obj: UpdateMenu): @@ -69,8 +69,8 @@ async def update_menu(pk: int, obj: UpdateMenu): '/{pk}', summary='删除菜单', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:menu:del')), + DependsRBAC, ], ) async def delete_menu(pk: int): diff --git a/backend/app/api/v1/mixed/config.py b/backend/app/api/v1/mixed/config.py index 3ac58fbf..4e117680 100644 --- a/backend/app/api/v1/mixed/config.py +++ b/backend/app/api/v1/mixed/config.py @@ -14,8 +14,8 @@ '/routers', summary='获取所有路由', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:route:list')), + DependsRBAC, ], ) async def get_all_route(request: Request): diff --git a/backend/app/api/v1/monitor/redis.py b/backend/app/api/v1/monitor/redis.py index 3aa097c1..8f6ff6cc 100644 --- a/backend/app/api/v1/monitor/redis.py +++ b/backend/app/api/v1/monitor/redis.py @@ -14,8 +14,8 @@ '/redis', summary='redis 监控', dependencies=[ - DependsJwtAuth, Depends(RequestPermission('sys:monitor:redis')), + DependsJwtAuth, ], ) async def get_redis_info(): diff --git a/backend/app/api/v1/monitor/server.py b/backend/app/api/v1/monitor/server.py index 05a6357a..47aa4599 100644 --- a/backend/app/api/v1/monitor/server.py +++ b/backend/app/api/v1/monitor/server.py @@ -15,8 +15,8 @@ '/server', summary='server 监控', dependencies=[ - DependsJwtAuth, Depends(RequestPermission('sys:monitor:server')), + DependsJwtAuth, ], ) async def get_server_info(): diff --git a/backend/app/api/v1/role.py b/backend/app/api/v1/role.py index fbfd4fb1..cdb7dc58 100644 --- a/backend/app/api/v1/role.py +++ b/backend/app/api/v1/role.py @@ -68,8 +68,8 @@ async def get_all_role_list( '', summary='创建角色', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:role:add')), + DependsRBAC, ], ) async def create_role(obj: CreateRole): @@ -81,8 +81,8 @@ async def create_role(obj: CreateRole): '/{pk}', summary='更新角色', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:role:edit')), + DependsRBAC, ], ) async def update_role(pk: int, obj: UpdateRole): @@ -96,8 +96,8 @@ async def update_role(pk: int, obj: UpdateRole): '/{pk}/menu', summary='更新角色菜单', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:role:menu:edit')), + DependsRBAC, ], ) async def update_role_menu(request: Request, pk: int, menu_ids: UpdateRoleMenu): @@ -111,8 +111,8 @@ async def update_role_menu(request: Request, pk: int, menu_ids: UpdateRoleMenu): '', summary='(批量)删除角色', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:role:del')), + DependsRBAC, ], ) async def delete_role(pk: Annotated[list[int], Query(...)]): diff --git a/backend/app/api/v1/task.py b/backend/app/api/v1/task.py index ee6c75b9..2745a65f 100644 --- a/backend/app/api/v1/task.py +++ b/backend/app/api/v1/task.py @@ -32,8 +32,8 @@ async def get_task_result(pk: str = Path(description='任务ID')): '/{module}', summary='执行任务', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:task:run')), + DependsRBAC, ], ) async def run_task( diff --git a/backend/app/api/v1/user.py b/backend/app/api/v1/user.py index ca1747ea..2cfb8eb1 100644 --- a/backend/app/api/v1/user.py +++ b/backend/app/api/v1/user.py @@ -73,8 +73,8 @@ async def update_userinfo(request: Request, username: str, obj: UpdateUser): '/{username}/role', summary='更新用户角色', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:user:role:edit')), + DependsRBAC, ], ) async def update_user_role(request: Request, username: str, obj: UpdateUserRole): @@ -147,8 +147,8 @@ async def multi_set(request: Request, pk: int): summary='用户注销', description='用户注销 != 用户登出,注销之后用户将从数据库删除', dependencies=[ - DependsRBAC, Depends(RequestPermission('sys:user:del')), + DependsRBAC, ], ) async def delete_user(username: str): diff --git a/backend/app/common/permission.py b/backend/app/common/permission.py index 04521efc..a6e1d81b 100644 --- a/backend/app/common/permission.py +++ b/backend/app/common/permission.py @@ -11,8 +11,8 @@ class RequestPermission: 请求权限,仅用于角色菜单RBAC Tip: - 使用此请求权限时,需要将 `DependsRBAC` 在 `Depends(RequestPermission('xxx'))` 之前设置, - 这是因为 fastapi 当前版本的依赖导入顺序为逆向导入,意味着 RBAC 标识会在验证前被设置 + 使用此请求权限时,需要将 `Depends(RequestPermission('xxx'))` 在 `DependsRBAC` 之前设置, + 因为 fastapi 当前版本的接口依赖注入按正序执行,意味着 RBAC 标识会在验证前被设置 """ def __init__(self, value: str): From 43bb3350b5472c4f3d9bf41c0114c63a514c5d8e Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 06:27:41 +0800 Subject: [PATCH 21/22] Update role menu permission flag --- backend/app/common/rbac.py | 15 +++++++-------- backend/app/crud/crud_menu.py | 3 --- backend/app/services/menu_service.py | 3 +++ backend/app/services/role_service.py | 2 +- backend/app/services/user_service.py | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index ea6b268f..0596c704 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -74,14 +74,14 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N data_scope = any(role.data_scope == 1 for role in user_roles) if data_scope: return - user_id = request.user.id + user_uuid = request.user.uuid path_auth_perm = request.state.permission if settings.PERMISSION_MODE == 'role-menu': # 角色菜单权限校验 if path_auth_perm in set(settings.ROLE_MENU_EXCLUDE): return - user_menu_perms = await redis_client.get(f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:enable') - user_forbid_menu_perms = await redis_client.get(f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:disable') + user_menu_perms = await redis_client.get(f'{settings.PERMISSION_REDIS_PREFIX}:{user_uuid}:enable') + user_forbid_menu_perms = await redis_client.get(f'{settings.PERMISSION_REDIS_PREFIX}:{user_uuid}:disable') if not user_menu_perms or not user_forbid_menu_perms: user_menu_perms = [] user_forbid_menu_perms = [] @@ -96,10 +96,10 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N else: user_forbid_menu_perms.extend(perms.split(',')) await redis_client.set( - f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:enable', ','.join(user_menu_perms) + f'{settings.PERMISSION_REDIS_PREFIX}:{user_uuid}:enable', ','.join(user_menu_perms) ) await redis_client.set( - f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:disable', ','.join(user_forbid_menu_perms) + f'{settings.PERMISSION_REDIS_PREFIX}:{user_uuid}:disable', ','.join(user_forbid_menu_perms) ) if path_auth_perm in user_forbid_menu_perms: raise AuthorizationError(msg='菜单已禁用,授权失败') @@ -109,7 +109,7 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N # casbin 权限校验 method = request.method user_forbid_menu_perms = await redis_client.get( - f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.id}:disable' + f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.uuid}:disable' ) if not user_forbid_menu_perms: user_forbid_menu_perms = [] @@ -122,13 +122,12 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N if menu.status == StatusType.disable: user_forbid_menu_perms.extend(perms.split(',')) await redis_client.set( - f'{settings.PERMISSION_REDIS_PREFIX}:{user_id}:disable', ','.join(user_forbid_menu_perms) + f'{settings.PERMISSION_REDIS_PREFIX}:{user_uuid}:disable', ','.join(user_forbid_menu_perms) ) if path_auth_perm in user_forbid_menu_perms: raise AuthorizationError(msg='菜单已禁用,授权失败') if (method, path) in settings.CASBIN_EXCLUDE: return - user_uuid = request.user.uuid enforcer = await self.enforcer() if not enforcer.enforce(user_uuid, path, method): raise AuthorizationError diff --git a/backend/app/crud/crud_menu.py b/backend/app/crud/crud_menu.py index e247b32a..9b6fc55c 100644 --- a/backend/app/crud/crud_menu.py +++ b/backend/app/crud/crud_menu.py @@ -5,8 +5,6 @@ from sqlalchemy import and_, asc, select from sqlalchemy.orm import selectinload -from backend.app.common.redis import redis_client -from backend.app.core.conf import settings from backend.app.crud.base import CRUDBase from backend.app.models import Menu from backend.app.schemas.menu import CreateMenu, UpdateMenu @@ -46,7 +44,6 @@ async def create(self, db, obj_in: CreateMenu) -> None: async def update(self, db, menu_id: int, obj_in: UpdateMenu) -> int: count = await self.update_(db, menu_id, obj_in) - await redis_client.delete_prefix(settings.PERMISSION_REDIS_PREFIX) return count async def delete(self, db, menu_id: int) -> int: diff --git a/backend/app/services/menu_service.py b/backend/app/services/menu_service.py index 69b5fe4c..f7c1fb90 100644 --- a/backend/app/services/menu_service.py +++ b/backend/app/services/menu_service.py @@ -5,6 +5,8 @@ from fastapi import Request from backend.app.common.exception import errors +from backend.app.common.redis import redis_client +from backend.app.core.conf import settings from backend.app.crud.crud_menu import MenuDao from backend.app.crud.crud_role import RoleDao from backend.app.database.db_mysql import async_db_session @@ -79,6 +81,7 @@ async def update(*, pk: int, obj: UpdateMenu) -> int: if not parent_menu: raise errors.NotFoundError(msg='父级菜单不存在') count = await MenuDao.update(db, pk, obj) + await redis_client.delete_prefix(settings.PERMISSION_REDIS_PREFIX) return count @staticmethod diff --git a/backend/app/services/role_service.py b/backend/app/services/role_service.py index 5c6db2e8..115312a5 100644 --- a/backend/app/services/role_service.py +++ b/backend/app/services/role_service.py @@ -72,7 +72,7 @@ async def update_menus(*, request: Request, pk: int, menu_ids: UpdateRoleMenu) - if not menu: raise errors.NotFoundError(msg='菜单不存在') count = await RoleDao.update_menus(db, pk, menu_ids) - await redis_client.delete_prefix(f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.id}') + await redis_client.delete_prefix(f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.uuid}') return count @staticmethod diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py index 400556f6..7366d1cc 100644 --- a/backend/app/services/user_service.py +++ b/backend/app/services/user_service.py @@ -121,7 +121,7 @@ async def update_roles(*, request: Request, username: str, obj: UpdateUserRole) if not role: raise errors.NotFoundError(msg='角色不存在') await UserDao.update_role(db, input_user, obj) - await redis_client.delete_prefix(f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.id}') + await redis_client.delete_prefix(f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.uuid}') @staticmethod async def update_avatar(*, request: Request, username: str, avatar: Avatar) -> int: From 4565e0ba319630289664e4261128517b2520d217 Mon Sep 17 00:00:00 2001 From: Wu Clan Date: Sun, 7 Jan 2024 16:49:16 +0800 Subject: [PATCH 22/22] Update the background permission logic of the interface --- backend/app/common/enums.py | 1 + backend/app/common/rbac.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/app/common/enums.py b/backend/app/common/enums.py index 950b87d5..9176d051 100644 --- a/backend/app/common/enums.py +++ b/backend/app/common/enums.py @@ -50,6 +50,7 @@ class MethodType(StrEnum): PUT = 'PUT' DELETE = 'DELETE' PATCH = 'PATCH' + OPTIONS = 'OPTIONS' class LoginLogStatusType(IntEnum): diff --git a/backend/app/common/rbac.py b/backend/app/common/rbac.py index 0596c704..b43c643b 100644 --- a/backend/app/common/rbac.py +++ b/backend/app/common/rbac.py @@ -5,7 +5,7 @@ from fastapi import Depends, Request -from backend.app.common.enums import StatusType +from backend.app.common.enums import StatusType, MethodType from backend.app.common.exception.errors import AuthorizationError, TokenError from backend.app.common.jwt import DependsJwtAuth from backend.app.common.redis import redis_client @@ -54,14 +54,14 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N :return: """ path = request.url.path + # 鉴权白名单 if path in settings.TOKEN_EXCLUDE: return - # 强制校验 JWT 授权状态 + # JWT 授权状态强制校验 if not request.auth.scopes: raise TokenError # 超级管理员免校验 - super_user = request.user.is_superuser - if super_user: + if request.user.is_superuser: return # 检测角色数据权限范围 user_roles = request.user.roles @@ -69,8 +69,11 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N raise AuthorizationError(msg='用户未分配角色,授权失败') if not any(len(role.menus) > 0 for role in user_roles): raise AuthorizationError(msg='用户所属角色未分配菜单,授权失败') - if not request.user.is_staff: - raise AuthorizationError(msg='此用户已被禁止后台管理操作') + method = request.method + if method != MethodType.GET or method != MethodType.OPTIONS: + if not request.user.is_staff: + raise AuthorizationError(msg='此用户已被禁止后台管理操作') + # 数据权限范围 data_scope = any(role.data_scope == 1 for role in user_roles) if data_scope: return @@ -107,7 +110,6 @@ async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> N raise AuthorizationError else: # casbin 权限校验 - method = request.method user_forbid_menu_perms = await redis_client.get( f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.uuid}:disable' )