fix: 修复权限映射的反向保存逻辑
问题:权限映射只在显示层面进行,但保存时还需要反向映射回数据库权限键 解决方案: 1. 分离显示权限和原始权限: - originalRoutePermissionsMap:存储未映射的原始权限(用于保存) - routePermissionsMap:存储映射后的权限(用于显示) - originalAllPermissions:存储所有原始权限的列表 2. 加载角色权限时: - 从API获取角色已分配的权限ID(原始ID) - 直接存储到 selectedPermissionIds - 不做任何映射转换 3. 显示权限列表时: - 从原始权限构建映射后的权限(合并相同的) - 用户看到的就是映射后的权限(如dify:dataset:manage) - 但勾选状态基于原始权限ID 4. 保存权限时: - 直接使用 selectedPermissionIds(原始ID) - 无需反向映射 验证方式: 1. 取消勾选 dify:dataset:manage → 数据库中4个bind权限被DENY → 接口返回403 2. 重新勾选 dify:dataset:manage → 数据库中4个bind权限被GRANT → 接口可访问
This commit is contained in:
@@ -945,12 +945,17 @@ export default function RolePermissions() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==================== 权限状态管理 ====================
|
||||||
|
// 存储原始的、未映射的权限(用于保存时)
|
||||||
|
const [originalRoutePermissionsMap, setOriginalRoutePermissionsMap] = useState<Map<number, ApiPermission[]>>(new Map());
|
||||||
|
const [originalAllPermissions, setOriginalAllPermissions] = useState<ApiPermission[]>([]);
|
||||||
|
|
||||||
// 选择角色
|
// 选择角色
|
||||||
const handleSelectRole = async (role: RoleInfo) => {
|
const handleSelectRole = async (role: RoleInfo) => {
|
||||||
setSelectedRole(role);
|
setSelectedRole(role);
|
||||||
|
|
||||||
// 动态导入权限映射工具
|
// 动态导入权限映射工具
|
||||||
const { mapPermissions, mapPermissionKey, findDbPermissionKeys } = await import('~/utils/permission-mapper');
|
const { mapPermissions } = await import('~/utils/permission-mapper');
|
||||||
|
|
||||||
// v3.0: 并行加载数据
|
// v3.0: 并行加载数据
|
||||||
const [routesResult, rolePermissions, users] = await Promise.all([
|
const [routesResult, rolePermissions, users] = await Promise.all([
|
||||||
@@ -961,42 +966,49 @@ export default function RolePermissions() {
|
|||||||
|
|
||||||
const { routes: routesWithPerms, selectedRouteIds: routeIds } = routesResult;
|
const { routes: routesWithPerms, selectedRouteIds: routeIds } = routesResult;
|
||||||
|
|
||||||
// 构建 routePermissionsMap:从返回的路由中提取每个路由的可用 permissions
|
// 构建原始权限映射(未映射的,用于保存)
|
||||||
// 并应用权限键映射(dify:bind:* -> dify:dataset:*, dify:file:*)
|
const originalPermMap = new Map<number, ApiPermission[]>();
|
||||||
const permMap = new Map<number, ApiPermission[]>();
|
// 存储所有原始权限的列表
|
||||||
const extractPermissions = (routes: RouteInfo[]) => {
|
const allOriginalPerms: ApiPermission[] = [];
|
||||||
|
const extractOriginalPermissions = (routes: RouteInfo[]) => {
|
||||||
routes.forEach(route => {
|
routes.forEach(route => {
|
||||||
if (route.permissions && route.permissions.length > 0) {
|
if (route.permissions && route.permissions.length > 0) {
|
||||||
// 应用权限键映射
|
originalPermMap.set(route.id, route.permissions);
|
||||||
const mappedPermissions = mapPermissions(route.permissions);
|
allOriginalPerms.push(...route.permissions);
|
||||||
permMap.set(route.id, mappedPermissions);
|
|
||||||
}
|
}
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
extractPermissions(route.children);
|
extractOriginalPermissions(route.children);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
extractPermissions(routesWithPerms);
|
extractOriginalPermissions(routesWithPerms);
|
||||||
|
|
||||||
// 从 getRolePermissions 结果中提取已分配的权限ID
|
// 存储原始权限
|
||||||
// 需要将数据库权限键映射为实际权限键进行对比
|
setOriginalRoutePermissionsMap(originalPermMap);
|
||||||
const assignedPermissionIds = rolePermissions.map(p => {
|
setOriginalAllPermissions(allOriginalPerms);
|
||||||
// 查找该权限对应的所有数据库权限键
|
|
||||||
const dbKeys = findDbPermissionKeys(p.permission_key);
|
|
||||||
// 如果使用映射,找到对应的权限ID
|
|
||||||
const mappedKey = mapPermissionKey(p.permission_key);
|
|
||||||
|
|
||||||
// 在 permMap 中查找对应权限的ID
|
// 构建映射后的权限映射(用于显示)
|
||||||
for (const [routeId, permissions] of permMap.entries()) {
|
const displayPermMap = new Map<number, ApiPermission[]>();
|
||||||
const foundPerm = permissions.find(perm => perm.permission_key === mappedKey);
|
const extractDisplayPermissions = (routes: RouteInfo[]) => {
|
||||||
if (foundPerm) return foundPerm.id;
|
routes.forEach(route => {
|
||||||
}
|
if (route.permissions && route.permissions.length > 0) {
|
||||||
return p.permission_id;
|
const mappedPermissions = mapPermissions(route.permissions);
|
||||||
});
|
displayPermMap.set(route.id, mappedPermissions);
|
||||||
|
}
|
||||||
|
if (route.children) {
|
||||||
|
extractDisplayPermissions(route.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
extractDisplayPermissions(routesWithPerms);
|
||||||
|
|
||||||
setRoutePermissionsMap(permMap);
|
// 从 getRolePermissions 结果中提取已分配的权限ID(原始ID)
|
||||||
|
const assignedPermissionIds = rolePermissions.map(p => p.permission_id);
|
||||||
|
|
||||||
|
// 存储状态
|
||||||
|
setRoutePermissionsMap(displayPermMap); // 用于显示
|
||||||
setSelectedRouteIds(routeIds);
|
setSelectedRouteIds(routeIds);
|
||||||
setSelectedPermissionIds(assignedPermissionIds); // 使用映射后的权限ID
|
setSelectedPermissionIds(assignedPermissionIds); // 使用原始权限ID
|
||||||
setExpandedRouteIds([]); // 重置展开状态
|
setExpandedRouteIds([]); // 重置展开状态
|
||||||
setRoleUsers(users);
|
setRoleUsers(users);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -122,6 +122,62 @@ export function mapPermissions(permissions: Permission[]): Permission[] {
|
|||||||
return Array.from(mappedMap.values());
|
return Array.from(mappedMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反向转换:将显示权限键转换为数据库权限键列表(用于保存权限)
|
||||||
|
* @param permissionKey 显示权限键(如 'dify:dataset:manage')
|
||||||
|
* @returns 对应的数据库权限键列表
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* reverseMapPermissionKey('dify:dataset:manage')
|
||||||
|
* // 返回 ['dify:bind:list', 'dify:bind:create', 'dify:bind:update', 'dify:bind:delete']
|
||||||
|
*
|
||||||
|
* reverseMapPermissionKey('dify:file:read')
|
||||||
|
* // 返回 ['dify:file:read'](无映射)
|
||||||
|
*/
|
||||||
|
export function reverseMapPermissionKey(permissionKey: string): string[] {
|
||||||
|
// 查找所有映射到该权限键的原始权限键
|
||||||
|
const originalKeys: string[] = [];
|
||||||
|
for (const [key, mappedKey] of Object.entries(PERMISSION_KEY_MAP)) {
|
||||||
|
if (mappedKey === permissionKey) {
|
||||||
|
originalKeys.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originalKeys.length > 0 ? originalKeys : [permissionKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据显示的权限ID查找对应的数据库权限ID(用于保存)
|
||||||
|
* 这是一个复杂操作,因为前端显示的权限ID是经过映射的
|
||||||
|
*
|
||||||
|
* @param displayPermissionId 前端显示的权限ID(映射后的)
|
||||||
|
* @param allMappedPermissions 所有映射后的权限列表(从路由获取的)
|
||||||
|
* @param originalPermissions 原始的权限列表(未映射的)
|
||||||
|
* @returns 对应的数据库权限ID列表
|
||||||
|
*/
|
||||||
|
export function findDbPermissionIds(
|
||||||
|
displayPermissionId: number,
|
||||||
|
allMappedPermissions: Permission[],
|
||||||
|
originalPermissions: Permission[]
|
||||||
|
): number[] {
|
||||||
|
// 查找显示权限对应的显示权限对象
|
||||||
|
const displayPerm = allMappedPermissions.find(p => p.id === displayPermissionId);
|
||||||
|
if (!displayPerm) return [];
|
||||||
|
|
||||||
|
// 将显示权限键反向映射为数据库权限键列表
|
||||||
|
const dbKeys = reverseMapPermissionKey(displayPerm.permission_key);
|
||||||
|
|
||||||
|
// 在原始权限列表中查找这些数据库权限键对应的ID
|
||||||
|
const dbIds: number[] = [];
|
||||||
|
for (const key of dbKeys) {
|
||||||
|
const originalPerm = originalPermissions.find(p => p.permission_key === key);
|
||||||
|
if (originalPerm) {
|
||||||
|
dbIds.push(originalPerm.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbIds;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查权限是否受映射影响
|
* 检查权限是否受映射影响
|
||||||
* @param permissionKey 权限键
|
* @param permissionKey 权限键
|
||||||
|
|||||||
Reference in New Issue
Block a user