Commit 313066b9 by Finley Ge Committed by GitHub

fix(permission): optimize private resource check and deduplicate (#7147)

collaborators

Add `isPrivateResourceByCollaborators` utility to correctly handle
collaborator deduplication for inherited permissions and update related
modules to use the new helper.
parent 806a8111
......@@ -182,3 +182,27 @@ export const mergeCollaboratorList = <T extends CollaboratorItemType>({
return Array.from(idToClb.values());
};
/**
* 判断资源在当前协作者集合下是否仍为私有。
* 继承权限的资源需要先合并父级与自身协作者,避免同一个协作者在父子记录中被重复计数。
*/
export const isPrivateResourceByCollaborators = <T extends CollaboratorItemType>({
resourceClbs,
parentClbs,
inheritPermission
}: {
resourceClbs: T[];
parentClbs?: T[];
inheritPermission?: boolean;
}) => {
const realClbs =
inheritPermission && parentClbs
? mergeCollaboratorList({
parentClbs,
childClbs: resourceClbs
})
: resourceClbs;
return realClbs.length <= 1;
};
......@@ -4,6 +4,7 @@ import {
checkRoleUpdateConflict,
getChangedCollaborators,
getCollaboratorId,
isPrivateResourceByCollaborators,
mergeCollaboratorList
} from '@fastgpt/global/support/permission/utils';
import {
......@@ -812,4 +813,61 @@ describe('Permission Utils', () => {
expect(user3?.permission).toBe(OwnerRoleVal);
});
});
describe('isPrivateResourceByCollaborators', () => {
it('should treat a single owner collaborator as private', () => {
const result = isPrivateResourceByCollaborators({
resourceClbs: [{ tmbId: 'owner', permission: OwnerRoleVal }]
});
expect(result).toBe(true);
});
it('should treat direct extra collaborators as non-private', () => {
const result = isPrivateResourceByCollaborators({
resourceClbs: [
{ tmbId: 'owner', permission: OwnerRoleVal },
{ tmbId: 'user1', permission: ReadRoleVal }
]
});
expect(result).toBe(false);
});
it('should dedupe inherited parent and child owner records before checking privacy', () => {
const result = isPrivateResourceByCollaborators({
inheritPermission: true,
parentClbs: [{ tmbId: 'owner', permission: OwnerRoleVal }],
resourceClbs: [{ tmbId: 'owner', permission: OwnerRoleVal }]
});
expect(result).toBe(true);
});
it('should treat inherited parent collaborators as non-private', () => {
const result = isPrivateResourceByCollaborators({
inheritPermission: true,
parentClbs: [
{ tmbId: 'owner', permission: OwnerRoleVal },
{ groupId: 'group1', permission: ReadRoleVal }
],
resourceClbs: [{ tmbId: 'owner', permission: OwnerRoleVal }]
});
expect(result).toBe(false);
});
it('should treat inherited child extra collaborators as non-private', () => {
const result = isPrivateResourceByCollaborators({
inheritPermission: true,
parentClbs: [{ tmbId: 'owner', permission: OwnerRoleVal }],
resourceClbs: [
{ tmbId: 'owner', permission: OwnerRoleVal },
{ orgId: 'org1', permission: ReadRoleVal }
]
});
expect(result).toBe(false);
});
});
});
......@@ -3,7 +3,7 @@ import { MongoResourcePermission } from '../../../../support/permission/schema';
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { AppTypeEnum, AppFolderTypeList } from '@fastgpt/global/core/app/constants';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
import { sumPer } from '@fastgpt/global/support/permission/utils';
import { isPrivateResourceByCollaborators, sumPer } from '@fastgpt/global/support/permission/utils';
import { getGroupsByTmbId } from '../../../../support/permission/memberGroup/controllers';
import { getOrgIdSetWithParentByTmbId } from '../../../../support/permission/org/controllers';
import { MongoApp } from '../../schema';
......@@ -50,6 +50,13 @@ export const getUserAvaliableWorkflowTools = async ({
myGroupMap.has(String(item.groupId)) ||
myOrgSet.has(String(item.orgId))
);
const roleListMap = new Map<string, (typeof roleList)[number][]>();
roleList.forEach((item) => {
const resourceId = String(item.resourceId);
const list = roleListMap.get(resourceId) ?? [];
list.push(item);
roleListMap.set(resourceId, list);
});
const myApps: {
_id: string;
......@@ -86,21 +93,27 @@ export const getUserAvaliableWorkflowTools = async ({
});
};
const getClbCount = (appId: string) => {
return roleList.filter((item) => String(item.resourceId) === String(appId)).length;
};
// Inherit app, check parent folder clb and it's own clb
if (!AppFolderTypeList.includes(app.type) && app.parentId && app.inheritPermission) {
const resourceClbs = roleListMap.get(String(app._id)) ?? [];
const parentClbs = roleListMap.get(String(app.parentId)) ?? [];
return {
Per: getPer(String(app.parentId)).addRole(getPer(String(app._id)).role),
privateApp: getClbCount(String(app.parentId)) <= 1
privateApp: isPrivateResourceByCollaborators({
resourceClbs,
parentClbs,
inheritPermission: true
})
};
}
const resourceClbs = roleListMap.get(String(app._id)) ?? [];
return {
Per: getPer(String(app._id)),
privateApp: getClbCount(String(app._id)) <= 1
privateApp: isPrivateResourceByCollaborators({
resourceClbs
})
};
})();
......
......@@ -16,7 +16,7 @@ import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGrou
import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers';
import { addSourceMember } from '@fastgpt/service/support/user/utils';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { sumPer } from '@fastgpt/global/support/permission/utils';
import { isPrivateResourceByCollaborators, sumPer } from '@fastgpt/global/support/permission/utils';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
import {
ListAppBodySchema,
......@@ -86,6 +86,13 @@ async function handler(req: ApiRequestProps<ListAppBodyType>): Promise<ListAppRe
tmbId
})
]);
const roleListMap = new Map<string, (typeof roleList)[number][]>();
roleList.forEach((item) => {
const resourceId = String(item.resourceId);
const list = roleListMap.get(resourceId) ?? [];
list.push(item);
roleListMap.set(resourceId, list);
});
// Get my permissions
const myPerList = roleList.filter(
(item) =>
......@@ -186,21 +193,26 @@ async function handler(req: ApiRequestProps<ListAppBodyType>): Promise<ListAppRe
// Inherit app, check parent folder clb and it's own clb
if (!AppFolderTypeList.includes(app.type) && app.parentId && app.inheritPermission) {
const resourceClbs = roleListMap.get(String(app._id)) ?? [];
const parentClbs = roleListMap.get(String(app.parentId)) ?? [];
return {
Per: getPer(String(app.parentId)).addRole(getPer(String(app._id)).role),
privateApp:
roleList.filter(
(item) =>
String(item.resourceId) === String(app._id) ||
String(item.resourceId) === String(app.parentId)
).length <= 1
privateApp: isPrivateResourceByCollaborators({
resourceClbs,
parentClbs,
inheritPermission: true
})
};
}
const resourceClbs = roleListMap.get(String(app._id)) ?? [];
return {
Per: getPer(String(app._id)),
privateApp:
roleList.filter((item) => String(item.resourceId) === String(app._id)).length <= 1
privateApp: isPrivateResourceByCollaborators({
resourceClbs
})
};
})();
......
......@@ -16,7 +16,7 @@ import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGrou
import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers';
import { addSourceMember } from '@fastgpt/service/support/user/utils';
import { getEmbeddingModel } from '@fastgpt/service/core/ai/model';
import { sumPer } from '@fastgpt/global/support/permission/utils';
import { isPrivateResourceByCollaborators, sumPer } from '@fastgpt/global/support/permission/utils';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
import {
GetDatasetListBodySchema,
......@@ -74,6 +74,13 @@ async function handler(req: ApiRequestProps): Promise<GetDatasetListResponse> {
tmbId
})
]);
const roleListMap = new Map<string, (typeof roleList)[number][]>();
roleList.forEach((item) => {
const resourceId = String(item.resourceId);
const list = roleListMap.get(resourceId) ?? [];
list.push(item);
roleListMap.set(resourceId, list);
});
const myRoles = roleList.filter(
(item) =>
String(item.tmbId) === String(tmbId) ||
......@@ -153,20 +160,25 @@ async function handler(req: ApiRequestProps): Promise<GetDatasetListResponse> {
dataset.parentId &&
dataset.type !== DatasetTypeEnum.folder
) {
const resourceClbs = roleListMap.get(String(dataset._id)) ?? [];
const parentClbs = roleListMap.get(String(dataset.parentId)) ?? [];
return {
Per: getPer(String(dataset.parentId)).addRole(getPer(String(dataset._id)).role),
privateDataset:
roleList.filter(
(item) =>
String(item.resourceId) === String(dataset._id) ||
String(item.resourceId) === String(dataset.parentId)
).length <= 1
privateDataset: isPrivateResourceByCollaborators({
resourceClbs,
parentClbs,
inheritPermission: true
})
};
}
const resourceClbs = roleListMap.get(String(dataset._id)) ?? [];
return {
Per: getPer(String(dataset._id)),
privateDataset:
roleList.filter((item) => String(item.resourceId) === String(dataset._id)).length <= 1
privateDataset: isPrivateResourceByCollaborators({
resourceClbs
})
};
})();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment