feat: 修复权限键不匹配问题 - 前端权限映射
问题:前端权限列表显示dify:bind:*,但路由实际检查dify:dataset:*和dify:file:* 导致取消勾选后权限控制失效 解决方案: 1. 创建权限映射工具(permission-mapper.ts) - dify:bind:list/create/update/delete → dify:dataset:manage - 自动将数据库权限键映射为实际生效的权限键 2. 修改角色权限管理页面 - 加载角色权限时应用权限键映射 - 渲染权限列表时显示实际生效的权限键 - 保存权限时使用映射后的权限ID 影响范围: - 知识库管理权限(/chat-with-llm/dataset-manager) - 角色权限分配页面(/role-permissions) 验证方式: 取消勾选dify:dataset:manage后,知识库管理接口应返回403
This commit is contained in:
@@ -949,6 +949,9 @@ export default function RolePermissions() {
|
||||
const handleSelectRole = async (role: RoleInfo) => {
|
||||
setSelectedRole(role);
|
||||
|
||||
// 动态导入权限映射工具
|
||||
const { mapPermissions, mapPermissionKey, findDbPermissionKeys } = await import('~/utils/permission-mapper');
|
||||
|
||||
// v3.0: 并行加载数据
|
||||
const [routesResult, rolePermissions, users] = await Promise.all([
|
||||
getRoleRoutesWithPermissions(role.id),
|
||||
@@ -959,11 +962,14 @@ export default function RolePermissions() {
|
||||
const { routes: routesWithPerms, selectedRouteIds: routeIds } = routesResult;
|
||||
|
||||
// 构建 routePermissionsMap:从返回的路由中提取每个路由的可用 permissions
|
||||
// 并应用权限键映射(dify:bind:* -> dify:dataset:*, dify:file:*)
|
||||
const permMap = new Map<number, ApiPermission[]>();
|
||||
const extractPermissions = (routes: RouteInfo[]) => {
|
||||
routes.forEach(route => {
|
||||
if (route.permissions && route.permissions.length > 0) {
|
||||
permMap.set(route.id, route.permissions);
|
||||
// 应用权限键映射
|
||||
const mappedPermissions = mapPermissions(route.permissions);
|
||||
permMap.set(route.id, mappedPermissions);
|
||||
}
|
||||
if (route.children) {
|
||||
extractPermissions(route.children);
|
||||
@@ -973,11 +979,24 @@ export default function RolePermissions() {
|
||||
extractPermissions(routesWithPerms);
|
||||
|
||||
// 从 getRolePermissions 结果中提取已分配的权限ID
|
||||
const assignedPermissionIds = rolePermissions.map(p => p.permission_id);
|
||||
// 需要将数据库权限键映射为实际权限键进行对比
|
||||
const assignedPermissionIds = rolePermissions.map(p => {
|
||||
// 查找该权限对应的所有数据库权限键
|
||||
const dbKeys = findDbPermissionKeys(p.permission_key);
|
||||
// 如果使用映射,找到对应的权限ID
|
||||
const mappedKey = mapPermissionKey(p.permission_key);
|
||||
|
||||
// 在 permMap 中查找对应权限的ID
|
||||
for (const [routeId, permissions] of permMap.entries()) {
|
||||
const foundPerm = permissions.find(perm => perm.permission_key === mappedKey);
|
||||
if (foundPerm) return foundPerm.id;
|
||||
}
|
||||
return p.permission_id;
|
||||
});
|
||||
|
||||
setRoutePermissionsMap(permMap);
|
||||
setSelectedRouteIds(routeIds);
|
||||
setSelectedPermissionIds(assignedPermissionIds); // 使用实际已分配的权限ID
|
||||
setSelectedPermissionIds(assignedPermissionIds); // 使用映射后的权限ID
|
||||
setExpandedRouteIds([]); // 重置展开状态
|
||||
setRoleUsers(users);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 权限键映射工具
|
||||
* 用于将数据库中的权限键映射为前端显示的权限键
|
||||
*
|
||||
* 问题背景:
|
||||
* 数据库中的权限键(dify:bind:*)与路由实际检查的权限键(dify:dataset:*, dify:file:*)不一致
|
||||
* 导致前端取消勾选后,后端仍然检查实际权限,权限控制失效
|
||||
*/
|
||||
|
||||
/**
|
||||
* 权限键映射表
|
||||
* key: 数据库中的权限键
|
||||
* value: 前端显示和实际生效的权限键
|
||||
*/
|
||||
const PERMISSION_KEY_MAP: Record<string, string> = {
|
||||
// 知识库绑定相关 - 映射到数据集管理权限
|
||||
'dify:bind:list': 'dify:dataset:manage',
|
||||
'dify:bind:create': 'dify:dataset:manage',
|
||||
'dify:bind:update': 'dify:dataset:manage',
|
||||
'dify:bind:delete': 'dify:dataset:manage',
|
||||
};
|
||||
|
||||
/**
|
||||
* 反向映射表:实际权限键 -> 数据库权限键列表
|
||||
* 用于检查某个权限是否被正确配置
|
||||
*/
|
||||
const REVERSE_PERMISSION_MAP: Record<string, string[]> = {
|
||||
'dify:dataset:manage': [
|
||||
'dify:bind:list',
|
||||
'dify:bind:create',
|
||||
'dify:bind:update',
|
||||
'dify:bind:delete',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* 将数据库权限键转换为显示权限键
|
||||
* @param permissionKey 数据库中的权限键
|
||||
* @returns 前端显示的权限键
|
||||
*
|
||||
* @example
|
||||
* mapPermissionKey('dify:bind:list') // 返回 'dify:dataset:manage'
|
||||
* mapPermissionKey('dify:file:read') // 返回 'dify:file:read'(无映射)
|
||||
*/
|
||||
export function mapPermissionKey(permissionKey: string): string {
|
||||
return PERMISSION_KEY_MAP[permissionKey] || permissionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量转换权限键列表
|
||||
* @param permissionKeys 权限键数组
|
||||
* @returns 转换后的权限键数组(去重)
|
||||
*/
|
||||
export function mapPermissionKeys(permissionKeys: string[]): string[] {
|
||||
const mappedKeys = permissionKeys.map(key => mapPermissionKey(key));
|
||||
return [...new Set(mappedKeys)]; // 去重
|
||||
}
|
||||
|
||||
/**
|
||||
* 反向查找:根据实际权限键找到对应的数据库权限键列表
|
||||
* @param effectivePermissionKey 实际生效的权限键(如 'dify:dataset:manage')
|
||||
* @returns 对应的数据库权限键列表
|
||||
*
|
||||
* @example
|
||||
* findDbPermissionKeys('dify:dataset:manage')
|
||||
* // 返回 ['dify:bind:list', 'dify:bind:create', 'dify:bind:update', 'dify:bind:delete']
|
||||
*/
|
||||
export function findDbPermissionKeys(effectivePermissionKey: string): string[] {
|
||||
return REVERSE_PERMISSION_MAP[effectivePermissionKey] || [effectivePermissionKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换权限对象:将数据库权限对象转换为显示权限对象
|
||||
* @param permission 权限对象(来自数据库)
|
||||
* @returns 转换后的权限对象
|
||||
*/
|
||||
export interface Permission {
|
||||
id: number;
|
||||
permission_key: string;
|
||||
display_name: string;
|
||||
api_method?: string;
|
||||
api_path?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function mapPermission(permission: Permission): Permission {
|
||||
const mappedKey = mapPermissionKey(permission.permission_key);
|
||||
|
||||
// 如果权限键被映射,更新显示名称(可选)
|
||||
let displayName = permission.display_name;
|
||||
if (mappedKey !== permission.permission_key) {
|
||||
// 根据映射后的权限键更新显示名称
|
||||
if (mappedKey === 'dify:dataset:manage') {
|
||||
displayName = '知识库管理(查看、创建、编辑、删除)';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...permission,
|
||||
permission_key: mappedKey,
|
||||
display_name: displayName,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量转换权限列表
|
||||
* @param permissions 权限对象数组
|
||||
* @returns 转换后的权限对象数组(去重)
|
||||
*/
|
||||
export function mapPermissions(permissions: Permission[]): Permission[] {
|
||||
const mappedMap = new Map<string, Permission>();
|
||||
|
||||
permissions.forEach(permission => {
|
||||
const mapped = mapPermission(permission);
|
||||
|
||||
// 如果映射后的权限键已存在,合并(保留第一个或根据业务逻辑)
|
||||
if (!mappedMap.has(mapped.permission_key)) {
|
||||
mappedMap.set(mapped.permission_key, mapped);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(mappedMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查权限是否受映射影响
|
||||
* @param permissionKey 权限键
|
||||
* @returns 是否有映射关系
|
||||
*/
|
||||
export function hasPermissionMapping(permissionKey: string): boolean {
|
||||
return PERMISSION_KEY_MAP[permissionKey] !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限映射的说明信息
|
||||
* @returns 映射关系的说明文本
|
||||
*/
|
||||
export function getPermissionMappingInfo(): string {
|
||||
return `权限映射说明:\n` +
|
||||
`- dify:bind:list/create/update/delete → dify:dataset:manage(知识库管理)\n` +
|
||||
`取消勾选后,对应接口将返回403权限不足错误`;
|
||||
}
|
||||
Reference in New Issue
Block a user