diff --git a/backend/app/admin/api/v1/sys/role.py b/backend/app/admin/api/v1/sys/role.py index e08d4edf..ab8549e2 100644 --- a/backend/app/admin/api/v1/sys/role.py +++ b/backend/app/admin/api/v1/sys/role.py @@ -4,7 +4,13 @@ from fastapi import APIRouter, Depends, Path, Query, Request -from backend.app.admin.schema.role import CreateRoleParam, GetRoleListDetails, UpdateRoleMenuParam, UpdateRoleParam +from backend.app.admin.schema.role import ( + CreateRoleParam, + GetRoleListDetails, + UpdateRoleDeptParam, + UpdateRoleMenuParam, + UpdateRoleParam, +) from backend.app.admin.service.menu_service import menu_service from backend.app.admin.service.role_service import role_service from backend.common.pagination import DependsPagination, paging_data @@ -109,6 +115,23 @@ async def update_role_menus( return response_base.fail() +@router.put( + '/{pk}/dept', + summary='更新角色部门', + dependencies=[ + Depends(RequestPermission('sys:role:dept:edit')), + DependsRBAC, + ], +) +async def update_role_depts( + request: Request, pk: Annotated[int, Path(...)], dept_ids: UpdateRoleDeptParam +) -> ResponseModel: + count = await role_service.update_role_dept(request=request, pk=pk, menu_ids=dept_ids) + if count > 0: + return response_base.success() + return response_base.fail() + + @router.delete( '', summary='(批量)删除角色', diff --git a/backend/app/admin/crud/crud_role.py b/backend/app/admin/crud/crud_role.py index f40e37e5..69d5119a 100644 --- a/backend/app/admin/crud/crud_role.py +++ b/backend/app/admin/crud/crud_role.py @@ -6,8 +6,8 @@ from sqlalchemy.orm import selectinload from sqlalchemy_crud_plus import CRUDPlus -from backend.app.admin.model import Menu, Role, User -from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam +from backend.app.admin.model import Dept, Menu, Role, User +from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleDeptParam, UpdateRoleMenuParam, UpdateRoleParam class CRUDRole(CRUDPlus[Role]): @@ -122,6 +122,21 @@ async def update_menus(self, db, role_id: int, menu_ids: UpdateRoleMenuParam) -> current_role.menus = menus.scalars().all() return len(current_role.menus) + async def update_depts(self, db, role_id: int, dept_ids: UpdateRoleDeptParam) -> int: + """ + 更新角色部门 + + :param db: + :param role_id: + :param dept_ids: + :return: + """ + stmt = select(Dept).where(Dept.id.in_(dept_ids.depts)) + depts = await db.execute(stmt) + current_role = await self.get_with_relation(db, role_id) + current_role.depts = depts.scalars().all() + return len(current_role.depts) + async def delete(self, db, role_id: list[int]) -> int: """ 删除角色 diff --git a/backend/app/admin/crud/crud_user.py b/backend/app/admin/crud/crud_user.py index c6bf35e7..04dee8d9 100644 --- a/backend/app/admin/crud/crud_user.py +++ b/backend/app/admin/crud/crud_user.py @@ -183,8 +183,10 @@ async def get_list(self, dept: int = None, username: str = None, phone: str = No """ stmt = ( select(self.model) - .options(selectinload(self.model.dept)) - .options(selectinload(self.model.roles).selectinload(Role.menus)) + .options( + selectinload(self.model.dept), + selectinload(self.model.roles).selectinload(Role.menus), + ) .order_by(desc(self.model.join_time)) ) where_list = [] @@ -297,10 +299,10 @@ async def get_with_relation(self, db: AsyncSession, *, user_id: int = None, user :param username: :return: """ - stmt = ( - select(self.model) - .options(selectinload(self.model.dept)) - .options(selectinload(self.model.roles).joinedload(Role.menus)) + stmt = select(self.model).options( + selectinload(self.model.dept), + selectinload(self.model.roles).joinedload(Role.menus), + selectinload(self.model.roles).joinedload(Role.depts), ) filters = [] if user_id: diff --git a/backend/app/admin/model/m2m.py b/backend/app/admin/model/m2m.py new file mode 100644 index 00000000..6984e70e --- /dev/null +++ b/backend/app/admin/model/m2m.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from sqlalchemy import INT, Column, ForeignKey, Integer, Table + +from backend.common.model import MappedBase + +sys_user_role = Table( + 'sys_user_role', + MappedBase.metadata, + Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + Column('user_id', Integer, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'), + Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), +) + +sys_role_menu = Table( + 'sys_role_menu', + MappedBase.metadata, + Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), + Column('menu_id', Integer, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'), +) + +sys_role_dept = Table( + 'sys_role_dept', + MappedBase.metadata, + Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), + Column('dept_id', Integer, ForeignKey('sys_dept.id', ondelete='CASCADE'), primary_key=True, comment='部门ID'), +) diff --git a/backend/app/admin/model/sys_dept.py b/backend/app/admin/model/sys_dept.py index 2e1492e9..9bb5aabf 100644 --- a/backend/app/admin/model/sys_dept.py +++ b/backend/app/admin/model/sys_dept.py @@ -22,11 +22,16 @@ class Dept(Base): email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱') status: Mapped[int] = mapped_column(default=1, comment='部门状态(0停用 1正常)') del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)') + # 父级部门一对多 parent_id: Mapped[int | None] = mapped_column( ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, index=True, comment='父部门ID' ) parent: Mapped[Union['Dept', None]] = relationship(init=False, back_populates='children', remote_side=[id]) children: Mapped[list['Dept'] | None] = relationship(init=False, back_populates='parent') + # 部门用户一对多 users: Mapped[list['User']] = relationship(init=False, back_populates='dept') # noqa: F821 + + # 部门角色多对多 + roles: Mapped[list['Role']] = relationship(init=False, back_populates='dept') # noqa: F821 diff --git a/backend/app/admin/model/sys_dict_data.py b/backend/app/admin/model/sys_dict_data.py index 884bdc6b..4eac82b6 100644 --- a/backend/app/admin/model/sys_dict_data.py +++ b/backend/app/admin/model/sys_dict_data.py @@ -18,6 +18,9 @@ class DictData(Base): sort: Mapped[int] = mapped_column(default=0, comment='排序') status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + # 字典类型一对多 - type_id: Mapped[int] = mapped_column(ForeignKey('sys_dict_type.id'), default=0, comment='字典类型关联ID') + type_id: Mapped[int] = mapped_column( + ForeignKey('sys_dict_type.id', ondelete='CASCADE'), default=0, comment='字典类型关联ID' + ) type: Mapped['DictType'] = relationship(init=False, back_populates='datas') # noqa: F821 diff --git a/backend/app/admin/model/sys_dict_type.py b/backend/app/admin/model/sys_dict_type.py index 3190162e..c5676b4a 100644 --- a/backend/app/admin/model/sys_dict_type.py +++ b/backend/app/admin/model/sys_dict_type.py @@ -17,5 +17,6 @@ class DictType(Base): code: Mapped[str] = mapped_column(String(32), unique=True, comment='字典类型编码') status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + # 字典类型一对多 datas: Mapped[list['DictData']] = relationship(init=False, back_populates='type') # noqa: F821 diff --git a/backend/app/admin/model/sys_menu.py b/backend/app/admin/model/sys_menu.py index f3233b2c..8b75d29e 100644 --- a/backend/app/admin/model/sys_menu.py +++ b/backend/app/admin/model/sys_menu.py @@ -6,7 +6,7 @@ from sqlalchemy.dialects.mysql import LONGTEXT from sqlalchemy.orm import Mapped, mapped_column, relationship -from backend.app.admin.model.sys_role_menu import sys_role_menu +from backend.app.admin.model.m2m import sys_role_menu from backend.common.model import Base, id_key @@ -29,13 +29,13 @@ class Menu(Base): show: Mapped[int] = mapped_column(default=1, comment='是否显示(0否 1是)') cache: Mapped[int] = mapped_column(default=1, comment='是否缓存(0否 1是)') remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + # 父级菜单一对多 parent_id: Mapped[int | None] = mapped_column( ForeignKey('sys_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID' ) parent: Mapped[Union['Menu', None]] = relationship(init=False, back_populates='children', remote_side=[id]) children: Mapped[list['Menu'] | None] = relationship(init=False, back_populates='parent') + # 菜单角色多对多 - roles: Mapped[list['Role']] = relationship( # noqa: F821 - init=False, secondary=sys_role_menu, back_populates='menus' - ) + roles: Mapped[list['Role']] = relationship(init=False, secondary=sys_role_menu, back_populates='menus') # noqa: F821 diff --git a/backend/app/admin/model/sys_role.py b/backend/app/admin/model/sys_role.py index acf46c01..6429bff3 100644 --- a/backend/app/admin/model/sys_role.py +++ b/backend/app/admin/model/sys_role.py @@ -4,8 +4,7 @@ from sqlalchemy.dialects.mysql import LONGTEXT from sqlalchemy.orm import Mapped, mapped_column, relationship -from backend.app.admin.model.sys_role_menu import sys_role_menu -from backend.app.admin.model.sys_user_role import sys_user_role +from backend.app.admin.model.m2m import sys_role_dept, sys_role_menu, sys_user_role from backend.common.model import Base, id_key @@ -16,14 +15,18 @@ class Role(Base): id: Mapped[id_key] = mapped_column(init=False) name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称') - data_scope: Mapped[int | None] = mapped_column(default=2, comment='权限范围(1:全部数据权限 2:自定义数据权限)') + data_scope: Mapped[int | None] = mapped_column( + default=0, + comment='权限范围(0: 全部数据,1: 自定义数据,2: 所在部门及以下数据,3: 所在部门数据,4: 仅本人数据)', + ) status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)') remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + # 角色用户多对多 - users: Mapped[list['User']] = relationship( # noqa: F821 - init=False, secondary=sys_user_role, back_populates='roles' - ) + users: Mapped[list['User']] = relationship(init=False, secondary=sys_user_role, back_populates='roles') # noqa: F821 + # 角色菜单多对多 - menus: Mapped[list['Menu']] = relationship( # noqa: F821 - init=False, secondary=sys_role_menu, back_populates='roles' - ) + menus: Mapped[list['Menu']] = relationship(init=False, secondary=sys_role_menu, back_populates='roles') # noqa: F821 + + # 角色部门多对多 + depts: Mapped[list['Dept']] = relationship(init=False, secondary=sys_role_dept, back_populates='roles') # noqa: F821 diff --git a/backend/app/admin/model/sys_role_menu.py b/backend/app/admin/model/sys_role_menu.py deleted file mode 100644 index e1f2cb7d..00000000 --- a/backend/app/admin/model/sys_role_menu.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from sqlalchemy import INT, Column, ForeignKey, Integer, Table - -from backend.common.model import MappedBase - -sys_role_menu = Table( - 'sys_role_menu', - MappedBase.metadata, - Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), - Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), - Column('menu_id', Integer, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'), -) diff --git a/backend/app/admin/model/sys_user.py b/backend/app/admin/model/sys_user.py index dab80193..5a11d429 100644 --- a/backend/app/admin/model/sys_user.py +++ b/backend/app/admin/model/sys_user.py @@ -6,7 +6,7 @@ from sqlalchemy import ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column, relationship -from backend.app.admin.model.sys_user_role import sys_user_role +from backend.app.admin.model.m2m import sys_user_role from backend.common.model import Base, id_key from backend.database.db_mysql import uuid4_str from backend.utils.timezone import timezone @@ -32,14 +32,15 @@ class User(Base): phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机号') join_time: Mapped[datetime] = mapped_column(init=False, default_factory=timezone.now, comment='注册时间') last_login_time: Mapped[datetime | None] = mapped_column(init=False, onupdate=timezone.now, comment='上次登录') + # 部门用户一对多 dept_id: Mapped[int | None] = mapped_column( ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, comment='部门关联ID' ) dept: Mapped[Union['Dept', None]] = relationship(init=False, back_populates='users') # noqa: F821 - # 用户角色多对多 - roles: Mapped[list['Role']] = relationship( # noqa: F821 - init=False, secondary=sys_user_role, back_populates='users' - ) - # 用户 OAuth2 一对多 + + # 用户社交信息一对多 socials: Mapped[list['UserSocial']] = relationship(init=False, back_populates='user') # noqa: F821 + + # 用户角色多对多 + roles: Mapped[list['Role']] = relationship(init=False, secondary=sys_user_role, back_populates='users') # noqa: F821 diff --git a/backend/app/admin/model/sys_user_role.py b/backend/app/admin/model/sys_user_role.py deleted file mode 100644 index 1870360e..00000000 --- a/backend/app/admin/model/sys_user_role.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from sqlalchemy import INT, Column, ForeignKey, Integer, Table - -from backend.common.model import MappedBase - -sys_user_role = Table( - 'sys_user_role', - MappedBase.metadata, - Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), - Column('user_id', Integer, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'), - Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), -) diff --git a/backend/app/admin/model/sys_user_social.py b/backend/app/admin/model/sys_user_social.py index 42c8833a..50439aa6 100644 --- a/backend/app/admin/model/sys_user_social.py +++ b/backend/app/admin/model/sys_user_social.py @@ -20,7 +20,8 @@ class UserSocial(Base): union_id: Mapped[str | None] = mapped_column(String(20), default=None, comment='第三方用户的 union id') scope: Mapped[str | None] = mapped_column(String(120), default=None, comment='第三方用户授予的权限') code: Mapped[str | None] = mapped_column(String(50), default=None, comment='用户的授权 code') - # 用户 OAuth2 一对多 + + # 用户社交信息一对多 user_id: Mapped[int | None] = mapped_column( ForeignKey('sys_user.id', ondelete='SET NULL'), default=None, comment='用户关联ID' ) diff --git a/backend/app/admin/schema/role.py b/backend/app/admin/schema/role.py index e1fd61ef..f5cbd7a5 100644 --- a/backend/app/admin/schema/role.py +++ b/backend/app/admin/schema/role.py @@ -30,6 +30,10 @@ class UpdateRoleMenuParam(SchemaBase): menus: list[int] +class UpdateRoleDeptParam(SchemaBase): + depts: list[int] + + class GetRoleListDetails(RoleSchemaBase): model_config = ConfigDict(from_attributes=True) diff --git a/backend/app/admin/service/role_service.py b/backend/app/admin/service/role_service.py index d1335936..8f33ae84 100644 --- a/backend/app/admin/service/role_service.py +++ b/backend/app/admin/service/role_service.py @@ -5,10 +5,11 @@ from fastapi import Request from sqlalchemy import Select +from backend.app.admin.crud.crud_dept import dept_dao from backend.app.admin.crud.crud_menu import menu_dao from backend.app.admin.crud.crud_role import role_dao from backend.app.admin.model import Role -from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam +from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleDeptParam, UpdateRoleMenuParam, UpdateRoleParam from backend.common.exception import errors from backend.core.conf import settings from backend.database.db_mysql import async_db_session @@ -77,6 +78,21 @@ async def update_role_menu(*, request: Request, pk: int, menu_ids: UpdateRoleMen await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{request.user.id}') return count + @staticmethod + async def update_role_dept(*, request: Request, pk: int, dept_ids: UpdateRoleDeptParam) -> int: + async with async_db_session.begin() as db: + role = await role_dao.get(db, pk) + if not role: + raise errors.NotFoundError(msg='角色不存在') + for dept_id in dept_ids.depts: + dept = await dept_dao.get(db, dept_id) + if not dept: + raise errors.NotFoundError(msg='部门不存在') + count = await role_dao.update_depts(db, pk, dept_ids) + if pk in [role.id for role in request.user.roles]: + await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{request.user.id}') + return count + @staticmethod async def delete(*, pk: list[int]) -> int: async with async_db_session.begin() as db: diff --git a/backend/app/generator/api/v1/gen.py b/backend/app/generator/api/v1/gen.py index d3830bea..5713c29d 100644 --- a/backend/app/generator/api/v1/gen.py +++ b/backend/app/generator/api/v1/gen.py @@ -145,7 +145,7 @@ async def delete_model(pk: Annotated[int, Path(...)]) -> ResponseModel: return response_base.fail() -@router.get('/tables', summary='获取数据库表', dependencies=[DependsRBAC]) +@router.get('/tables', summary='获取数据库表') async def get_all_tables(table_schema: Annotated[str, Query(..., description='数据库名')] = 'fba') -> ResponseModel: data = await gen_service.get_tables(table_schema=table_schema) return response_base.success(data=data) diff --git a/backend/common/security/jwt.py b/backend/common/security/jwt.py index 2fcc4bb1..259e2efc 100644 --- a/backend/common/security/jwt.py +++ b/backend/common/security/jwt.py @@ -183,13 +183,13 @@ async def get_current_user(db: AsyncSession, pk: int) -> User: raise AuthorizationError(msg='用户已被锁定,请联系系统管理员') if user.dept_id: if not user.dept.status: - raise AuthorizationError(msg='用户所属部门已锁定') + raise AuthorizationError(msg='用户所属部门已被锁定,请联系系统管理员') if user.dept.del_flag: - raise AuthorizationError(msg='用户所属部门已删除') + raise AuthorizationError(msg='用户所属部门已被删除,请联系系统管理员') if user.roles: role_status = [role.status for role in user.roles] if all(status == 0 for status in role_status): - raise AuthorizationError(msg='用户所属角色已锁定') + raise AuthorizationError(msg='用户所属角色已被锁定,请联系系统管理员') return user diff --git a/backend/common/security/permission.py b/backend/common/security/permission.py index 3db8b550..f67417a1 100644 --- a/backend/common/security/permission.py +++ b/backend/common/security/permission.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from typing import Any + from fastapi import Request +from sqlalchemy import Select from backend.common.exception.errors import ServerError from backend.core.conf import settings @@ -24,3 +27,38 @@ async def __call__(self, request: Request): raise ServerError # 附加权限标识 request.state.permission = self.value + + +def filter_user_data_scope(request: Request, model: Any, stmt: Select) -> Select: + """ + 筛选用户数据范围 + + 使用场景:对于非后台管理数据,需要在前端界面向用户进行展示的数据 + + :param request: 接口请求对象 + :param model: 当前需要进行数据过滤的 sqlalchemy 模型 + :param stmt: 需要进行数据筛选的 stmt(select) 语句 + :return: + """ + user_roles = request.user.roles + dept_roles = request.user.dept.roles + user_roles.extend(dept_roles) + data_scope = min(role.data_scope for role in set(user_roles)) + + # 全部数据 + if data_scope == 0: + stmt = stmt + # 自定义数据(自选部门) + elif data_scope == 1: + ... + # 所在部门及以下数据 + elif data_scope == 2: + ... # TODO + # 所在部门数据 + elif data_scope == 3: + ... + # 仅本人数据 + elif data_scope == 4: + stmt = stmt.where(model.create_user == request.user.id) + + return stmt diff --git a/backend/common/security/rbac.py b/backend/common/security/rbac.py index a62ae5d6..d6fbea9c 100644 --- a/backend/common/security/rbac.py +++ b/backend/common/security/rbac.py @@ -21,7 +21,7 @@ async def enforcer() -> casbin.AsyncEnforcer: :return: """ - # 规则数据作为死数据直接在方法内定义 + # 模型定义:https://casbin.org/zh/docs/category/model _CASBIN_RBAC_MODEL_CONF_TEXT = """ [request_definition] r = sub, obj, act @@ -46,56 +46,69 @@ async def enforcer() -> casbin.AsyncEnforcer: async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> None: """ - RBAC 权限校验 + RBAC 权限校验(鉴权顺序很重要,谨慎修改) :param request: :param _token: :return: """ path = request.url.path - # 鉴权白名单 + + # API 鉴权白名单 if path in settings.TOKEN_REQUEST_PATH_EXCLUDE: return + # JWT 授权状态强制校验 if not request.auth.scopes: raise TokenError + # 超级管理员免校验 if request.user.is_superuser: return - # 检测角色数据权限范围 + + # 检测用户角色 user_roles = request.user.roles if not user_roles: - raise AuthorizationError(msg='用户未分配角色,授权失败') + raise AuthorizationError + + # 检测用户所属角色菜单 if not any(len(role.menus) > 0 for role in user_roles): - raise AuthorizationError(msg='用户所属角色未分配菜单,授权失败') + raise AuthorizationError + + # 检测后台管理操作权限 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 - user_uuid = request.user.uuid + raise AuthorizationError(msg='用户已被禁止后台管理操作,请联系系统管理员') + + # RBAC 鉴权 if settings.PERMISSION_MODE == 'role-menu': - # 角色菜单权限校验 path_auth_perm = getattr(request.state, 'permission', None) - # 没有菜单权限标识不校验 + + # 没有菜单操作权限标识不校验 if not path_auth_perm: return - if path_auth_perm in set(settings.RBAC_ROLE_MENU_EXCLUDE): + + # 菜单鉴权白名单 + if path_auth_perm in settings.RBAC_ROLE_MENU_EXCLUDE: return + + # 已分配菜单权限校验 allow_perms = [] for role in user_roles: for menu in role.menus: - if menu.status == StatusType.enable: + if menu.perms and menu.status == StatusType.enable: allow_perms.extend(menu.perms.split(',')) if path_auth_perm not in allow_perms: raise AuthorizationError else: - # casbin 权限校验 + # casbin 鉴权白名单 if (method, path) in settings.RBAC_CASBIN_EXCLUDE: return + + # casbin 权限校验 + # 实现机制:backend/app/admin/api/v1/sys/casbin.py + user_uuid = request.user.uuid enforcer = await self.enforcer() if not enforcer.enforce(user_uuid, path, method): raise AuthorizationError