Files
leaudit-platform-frontend/app/utils/permission-mapper.ts
T
TanWenyan d3418ef31b 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 → 接口可访问
2025-12-08 15:39:39 +08:00

199 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 权限键映射工具
* 用于将数据库中的权限键映射为前端显示的权限键
*
* 问题背景:
* 数据库中的权限键(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 显示权限键(如 '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 权限键
* @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权限不足错误`;
}