diff --git a/backend/app/admin/crud/crud_dept.py b/backend/app/admin/crud/crud_dept.py index 6bd48de2..743711d8 100644 --- a/backend/app/admin/crud/crud_dept.py +++ b/backend/app/admin/crud/crud_dept.py @@ -63,7 +63,7 @@ async def get_all( if status is not None: filters['status'] = status - data_filtered = await filter_data_permission(db, request_user) + data_filtered = filter_data_permission(request_user) return await self.select_models_order(db, 'sort', 'desc', data_filtered, **filters) async def create(self, db: AsyncSession, obj: CreateDeptParam) -> None: diff --git a/backend/app/admin/crud/crud_user.py b/backend/app/admin/crud/crud_user.py index d6155ce9..96cc8c57 100644 --- a/backend/app/admin/crud/crud_user.py +++ b/backend/app/admin/crud/crud_user.py @@ -6,7 +6,7 @@ from sqlalchemy.sql import Select from sqlalchemy_crud_plus import CRUDPlus -from backend.app.admin.model import Dept, Role, User +from backend.app.admin.model import DataScope, Dept, Role, User from backend.app.admin.schema.user import ( AddOAuth2UserParam, AddUserParam, @@ -281,7 +281,12 @@ async def get_with_relation( return await self.select_model_by_column( db, - load_options=[selectinload(self.model.roles).options(selectinload(Role.menus), selectinload(Role.scopes))], + load_options=[ + selectinload(self.model.roles).options( + selectinload(Role.menus), + selectinload(Role.scopes).options(selectinload(DataScope.rules)), + ) + ], load_strategies=['dept'], **filters, ) diff --git a/backend/app/admin/schema/data_rule.py b/backend/app/admin/schema/data_rule.py index 2b82726a..7bfa0763 100644 --- a/backend/app/admin/schema/data_rule.py +++ b/backend/app/admin/schema/data_rule.py @@ -34,7 +34,7 @@ class DeleteDataRuleParam(SchemaBase): class GetDataRuleDetail(DataRuleSchemaBase): """数据规则详情""" - model_config = ConfigDict(from_attributes=True) + model_config = ConfigDict(from_attributes=True, frozen=True) id: int = Field(description='规则 ID') created_time: datetime = Field(description='创建时间') diff --git a/backend/app/admin/schema/data_scope.py b/backend/app/admin/schema/data_scope.py index a52abefb..b2444a4e 100644 --- a/backend/app/admin/schema/data_scope.py +++ b/backend/app/admin/schema/data_scope.py @@ -47,4 +47,4 @@ class GetDataScopeDetail(DataScopeBase): class GetDataScopeWithRelationDetail(GetDataScopeDetail): """数据范围关联详情""" - rules: list[GetDataRuleDetail] = Field([], description='数据规则列表') + rules: list[GetDataRuleDetail | None] = Field([], description='数据规则列表') diff --git a/backend/app/admin/schema/role.py b/backend/app/admin/schema/role.py index d4c62e24..922af9e0 100644 --- a/backend/app/admin/schema/role.py +++ b/backend/app/admin/schema/role.py @@ -2,7 +2,7 @@ from pydantic import ConfigDict, Field -from backend.app.admin.schema.data_scope import GetDataScopeDetail +from backend.app.admin.schema.data_scope import GetDataScopeWithRelationDetail from backend.app.admin.schema.menu import GetMenuDetail from backend.common.enums import StatusType from backend.common.schema import SchemaBase @@ -57,4 +57,4 @@ class GetRoleWithRelationDetail(GetRoleDetail): """角色关联详情""" menus: list[GetMenuDetail | None] = Field([], description='菜单详情列表') - scopes: list[GetDataScopeDetail | None] = Field([], description='数据范围列表') + scopes: list[GetDataScopeWithRelationDetail | None] = Field([], description='数据范围列表') diff --git a/backend/common/security/permission.py b/backend/common/security/permission.py index d266a49b..59f520a1 100644 --- a/backend/common/security/permission.py +++ b/backend/common/security/permission.py @@ -1,8 +1,6 @@ from fastapi import Request from sqlalchemy import ColumnElement, and_, or_ -from sqlalchemy.ext.asyncio import AsyncSession -from backend.app.admin.crud.crud_data_scope import data_scope_dao from backend.app.admin.schema.user import GetUserInfoWithRelationDetail from backend.common.context import ctx from backend.common.enums import RoleDataRuleExpressionType, RoleDataRuleOperatorType @@ -43,14 +41,13 @@ async def __call__(self, request: Request) -> None: ctx.permission = self.value -async def filter_data_permission(db: AsyncSession, request_user: GetUserInfoWithRelationDetail) -> ColumnElement[bool]: # noqa: C901 +def filter_data_permission(request_user: GetUserInfoWithRelationDetail) -> ColumnElement[bool]: # noqa: C901 """ 过滤数据权限,控制用户可见数据范围 使用场景: - 控制用户能看到哪些数据 - :param db: 数据库会话 :param request_user: 请求用户 :return: """ @@ -62,31 +59,21 @@ async def filter_data_permission(db: AsyncSession, request_user: GetUserInfoWith if not role.is_filter_scopes: return or_(1 == 1) - # 获取数据范围 - data_scope_ids = set() + # 获取数据规则 + data_rules = set() for role in request_user.roles: for scope in role.scopes: if scope.status: - data_scope_ids.add(scope.id) + data_rules.update(scope.rules) # 无规则用户不做过滤 - if not list(data_scope_ids): + if not list(data_rules): return or_(1 == 1) - # 获取数据范围规则 - unique_data_rules = {} - for data_scope_id in list(data_scope_ids): - data_scope_with_relation = await data_scope_dao.get_with_relation(db, data_scope_id) - for rule in data_scope_with_relation.rules: - unique_data_rules[rule.id] = rule - - # 转换为列表 - data_rule_list = list(unique_data_rules.values()) - where_and_list = [] where_or_list = [] - for data_rule in data_rule_list: + for data_rule in list(data_rules): # 验证规则模型 rule_model = data_rule.model if rule_model not in settings.DATA_PERMISSION_MODELS: diff --git a/backend/sql/mysql/init_test_data.sql b/backend/sql/mysql/init_test_data.sql index 1ef86a0b..2947eb46 100644 --- a/backend/sql/mysql/init_test_data.sql +++ b/backend/sql/mysql/init_test_data.sql @@ -111,6 +111,11 @@ values (1, '部门名称等于测试', '部门', 'name', 1, 0, '测试', now(), null), (2, '父部门 ID 等于 1', '部门', 'parent_id', 0, 0, '1', now(), null); +insert into sys_role_data_scope (id, role_id, data_scope_id) +values +(1, 1, 1), +(2, 1, 2); + insert into sys_data_scope_rule (id, data_scope_id, data_rule_id) values (1, 1, 1), diff --git a/backend/sql/postgresql/init_test_data.sql b/backend/sql/postgresql/init_test_data.sql index add2bfcc..78b2bb0d 100644 --- a/backend/sql/postgresql/init_test_data.sql +++ b/backend/sql/postgresql/init_test_data.sql @@ -111,6 +111,11 @@ values (1, '部门名称等于测试', '部门', 'name', 1, 0, '测试', now(), null), (2, '父部门 ID 等于 1', '部门', 'parent_id', 0, 0, '1', now(), null); +insert into sys_role_data_scope (id, role_id, data_scope_id) +values +(1, 1, 1), +(2, 1, 2); + insert into sys_data_scope_rule (id, data_scope_id, data_rule_id) values (1, 1, 1), @@ -125,4 +130,5 @@ select setval(pg_get_serial_sequence('sys_user', 'id'),coalesce(max(id), 0) + 1, select setval(pg_get_serial_sequence('sys_user_role', 'id'),coalesce(max(id), 0) + 1, true) from sys_user_role; select setval(pg_get_serial_sequence('sys_data_scope', 'id'),coalesce(max(id), 0) + 1, true) from sys_data_scope; select setval(pg_get_serial_sequence('sys_data_rule', 'id'),coalesce(max(id), 0) + 1, true) from sys_data_rule; +select setval(pg_get_serial_sequence('sys_role_data_scope', 'id'),coalesce(max(id), 0) + 1, true) from sys_role_data_scope; select setval(pg_get_serial_sequence('sys_data_scope_rule', 'id'),coalesce(max(id), 0) + 1, true) from sys_data_scope_rule;