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>({ ...@@ -182,3 +182,27 @@ export const mergeCollaboratorList = <T extends CollaboratorItemType>({
return Array.from(idToClb.values()); 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 { ...@@ -4,6 +4,7 @@ import {
checkRoleUpdateConflict, checkRoleUpdateConflict,
getChangedCollaborators, getChangedCollaborators,
getCollaboratorId, getCollaboratorId,
isPrivateResourceByCollaborators,
mergeCollaboratorList mergeCollaboratorList
} from '@fastgpt/global/support/permission/utils'; } from '@fastgpt/global/support/permission/utils';
import { import {
...@@ -812,4 +813,61 @@ describe('Permission Utils', () => { ...@@ -812,4 +813,61 @@ describe('Permission Utils', () => {
expect(user3?.permission).toBe(OwnerRoleVal); 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'; ...@@ -3,7 +3,7 @@ import { MongoResourcePermission } from '../../../../support/permission/schema';
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { AppTypeEnum, AppFolderTypeList } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum, AppFolderTypeList } from '@fastgpt/global/core/app/constants';
import { AppPermission } from '@fastgpt/global/support/permission/app/controller'; 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 { getGroupsByTmbId } from '../../../../support/permission/memberGroup/controllers';
import { getOrgIdSetWithParentByTmbId } from '../../../../support/permission/org/controllers'; import { getOrgIdSetWithParentByTmbId } from '../../../../support/permission/org/controllers';
import { MongoApp } from '../../schema'; import { MongoApp } from '../../schema';
...@@ -50,6 +50,13 @@ export const getUserAvaliableWorkflowTools = async ({ ...@@ -50,6 +50,13 @@ export const getUserAvaliableWorkflowTools = async ({
myGroupMap.has(String(item.groupId)) || myGroupMap.has(String(item.groupId)) ||
myOrgSet.has(String(item.orgId)) 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: { const myApps: {
_id: string; _id: string;
...@@ -86,21 +93,27 @@ export const getUserAvaliableWorkflowTools = async ({ ...@@ -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 // Inherit app, check parent folder clb and it's own clb
if (!AppFolderTypeList.includes(app.type) && app.parentId && app.inheritPermission) { if (!AppFolderTypeList.includes(app.type) && app.parentId && app.inheritPermission) {
const resourceClbs = roleListMap.get(String(app._id)) ?? [];
const parentClbs = roleListMap.get(String(app.parentId)) ?? [];
return { return {
Per: getPer(String(app.parentId)).addRole(getPer(String(app._id)).role), 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 { return {
Per: getPer(String(app._id)), 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 ...@@ -16,7 +16,7 @@ import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGrou
import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers';
import { addSourceMember } from '@fastgpt/service/support/user/utils'; import { addSourceMember } from '@fastgpt/service/support/user/utils';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; 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 { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
import { import {
ListAppBodySchema, ListAppBodySchema,
...@@ -86,6 +86,13 @@ async function handler(req: ApiRequestProps<ListAppBodyType>): Promise<ListAppRe ...@@ -86,6 +86,13 @@ async function handler(req: ApiRequestProps<ListAppBodyType>): Promise<ListAppRe
tmbId 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 // Get my permissions
const myPerList = roleList.filter( const myPerList = roleList.filter(
(item) => (item) =>
...@@ -186,21 +193,26 @@ async function handler(req: ApiRequestProps<ListAppBodyType>): Promise<ListAppRe ...@@ -186,21 +193,26 @@ async function handler(req: ApiRequestProps<ListAppBodyType>): Promise<ListAppRe
// Inherit app, check parent folder clb and it's own clb // Inherit app, check parent folder clb and it's own clb
if (!AppFolderTypeList.includes(app.type) && app.parentId && app.inheritPermission) { if (!AppFolderTypeList.includes(app.type) && app.parentId && app.inheritPermission) {
const resourceClbs = roleListMap.get(String(app._id)) ?? [];
const parentClbs = roleListMap.get(String(app.parentId)) ?? [];
return { return {
Per: getPer(String(app.parentId)).addRole(getPer(String(app._id)).role), Per: getPer(String(app.parentId)).addRole(getPer(String(app._id)).role),
privateApp: privateApp: isPrivateResourceByCollaborators({
roleList.filter( resourceClbs,
(item) => parentClbs,
String(item.resourceId) === String(app._id) || inheritPermission: true
String(item.resourceId) === String(app.parentId) })
).length <= 1
}; };
} }
const resourceClbs = roleListMap.get(String(app._id)) ?? [];
return { return {
Per: getPer(String(app._id)), Per: getPer(String(app._id)),
privateApp: privateApp: isPrivateResourceByCollaborators({
roleList.filter((item) => String(item.resourceId) === String(app._id)).length <= 1 resourceClbs
})
}; };
})(); })();
......
...@@ -16,7 +16,7 @@ import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGrou ...@@ -16,7 +16,7 @@ import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGrou
import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers';
import { addSourceMember } from '@fastgpt/service/support/user/utils'; import { addSourceMember } from '@fastgpt/service/support/user/utils';
import { getEmbeddingModel } from '@fastgpt/service/core/ai/model'; 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 { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
import { import {
GetDatasetListBodySchema, GetDatasetListBodySchema,
...@@ -74,6 +74,13 @@ async function handler(req: ApiRequestProps): Promise<GetDatasetListResponse> { ...@@ -74,6 +74,13 @@ async function handler(req: ApiRequestProps): Promise<GetDatasetListResponse> {
tmbId 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( const myRoles = roleList.filter(
(item) => (item) =>
String(item.tmbId) === String(tmbId) || String(item.tmbId) === String(tmbId) ||
...@@ -153,20 +160,25 @@ async function handler(req: ApiRequestProps): Promise<GetDatasetListResponse> { ...@@ -153,20 +160,25 @@ async function handler(req: ApiRequestProps): Promise<GetDatasetListResponse> {
dataset.parentId && dataset.parentId &&
dataset.type !== DatasetTypeEnum.folder dataset.type !== DatasetTypeEnum.folder
) { ) {
const resourceClbs = roleListMap.get(String(dataset._id)) ?? [];
const parentClbs = roleListMap.get(String(dataset.parentId)) ?? [];
return { return {
Per: getPer(String(dataset.parentId)).addRole(getPer(String(dataset._id)).role), Per: getPer(String(dataset.parentId)).addRole(getPer(String(dataset._id)).role),
privateDataset: privateDataset: isPrivateResourceByCollaborators({
roleList.filter( resourceClbs,
(item) => parentClbs,
String(item.resourceId) === String(dataset._id) || inheritPermission: true
String(item.resourceId) === String(dataset.parentId) })
).length <= 1
}; };
} }
const resourceClbs = roleListMap.get(String(dataset._id)) ?? [];
return { return {
Per: getPer(String(dataset._id)), Per: getPer(String(dataset._id)),
privateDataset: privateDataset: isPrivateResourceByCollaborators({
roleList.filter((item) => String(item.resourceId) === String(dataset._id)).length <= 1 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