Files
FastGPT/packages/service/support/permission/inheritPermission.ts
Archer 2ed1545eb5 V4.12.4 features (#5626)
* fix: push again, user select option button and form input radio content overflow (#5601)

* fix: push again, user select option button and form input radio content overflow

* fix: use useCallback instead of useMemo, fix unnecessary delete

* fix: Move the variable inside the component

* fix: do not pass valueLabel to MySelect

* ui

* del collection api adapt

* refactor: inherit permission (#5529)

* refactor: permission update conflict check function

* refactor(permission): app collaborator update api

* refactor(permission): support app update collaborator

* feat: support fe permission conflict check

* refactor(permission): app permission

* refactor(permission): dataset permission

* refactor(permission): team permission

* chore: fe adjust

* fix: type error

* fix: audit pagiation

* fix: tc

* chore: initv4130

* fix: app/dataset auth logic

* chore: move code

* refactor(permission): remove selfPermission

* fix: mock

* fix: test

* fix: app & dataset auth

* fix: inherit

* test(inheritPermission): test syncChildrenPermission

* prompt editor add list plugin (#5620)

* perf: search result (#5608)

* fix: table size (#5598)

* temp: list value

* backspace

* optimize code

---------

Co-authored-by: Archer <545436317@qq.com>
Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com>

* fix: fe & member list (#5619)

* chore: initv4130

* fix: MemberItemCard

* fix: MemberItemCard

* chore: fe adjust & init script

* perf: test code

* doc

* fix debug variables (#5617)

* perf: search result (#5608)

* fix: table size (#5598)

* fix debug variables

* fix

---------

Co-authored-by: Archer <545436317@qq.com>
Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com>

* perf: member ui

* fix: inherit bug (#5624)

* refactor(permission): remove getClbsWithInfo, which is useless

* fix: app list privateApp

* fix: get infos

* perf(fe): remove delete icon when it is disable in MemberItemCard

* fix: dataset private dataset

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Archer <545436317@qq.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* perf: auto coupon

* chore: upgrade script & get infos avatar  (#5625)

* fix: get infos

* chore: initv4130

* feat: support WecomRobot publish, and fix AesKey can not save bug (#5526)

* feat: resolve conflicts

* fix: add param 'show_publish_wecom'

* feat: abstract out WecomCrypto type

* doc: wecom robot document

* fix: solve instability in AI output

* doc: update some pictures

* feat: remove functions from request.ts to chat.ts and toolCall.ts

* doc: wecom robot doc update

* fix

* delete unused code

* doc: update version and prompt

* feat: remove wecom crypto, delete wecom code in workflow

* feat: delete unused codes

---------

Co-authored-by: heheer <zhiyu44@qq.com>

* remove test

* rename init shell

* feat: collection page store

* reload sandbox

* pysandbox

* remove log

* chore: remove useless code (#5629)

* chore: remove useless code

* fix: checkConflict

* perf: support hidden type for RoleList

* fix: copy node

* update doc

* fix(permission): some bug (#5632)

* fix: app/dataset list

* fix: inherit bug

* perf: del app;i18n;save chat

* fix: test

* i18n

* fix: sumper overflow return OwnerRoleVal (#5633)

* remove invalid code

* fix: scroll

* fix: objectId

* update next

* update package

* object id

* mock redis

* feat: add redis append to resolve wecom stream response  (#5643)

* feat: resolve conflicts

* fix: add param 'show_publish_wecom'

* feat: abstract out WecomCrypto type

* doc: wecom robot document

* fix: solve instability in AI output

* doc: update some pictures

* feat: remove functions from request.ts to chat.ts and toolCall.ts

* doc: wecom robot doc update

* fix

* delete unused code

* doc: update version and prompt

* feat: remove wecom crypto, delete wecom code in workflow

* feat: delete unused codes

* feat: add redis append method

---------

Co-authored-by: heheer <zhiyu44@qq.com>

* cache per

* fix(test): init team sub when creating mocked user (#5646)

* fix: button is not vertically centered (#5647)

* doc

* fix: gridFs objectId (#5649)

---------

Co-authored-by: Zeng Qingwen <143274079+fishwww-ww@users.noreply.github.com>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: heheer <zhiyu44@qq.com>
2025-09-15 20:02:54 +08:00

352 lines
9.5 KiB
TypeScript

import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import {
ManageRoleVal,
NullPermissionVal,
OwnerRoleVal,
type PerResourceTypeEnum
} from '@fastgpt/global/support/permission/constant';
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
import { mongoSessionRun } from '../../common/mongo/sessionRun';
import { getResourceOwnedClbs } from './controller';
import { MongoResourcePermission } from './schema';
import type { ClientSession, Model, AnyBulkWriteOperation } from '../../common/mongo';
import {
getCollaboratorId,
mergeCollaboratorList,
sumPer
} from '@fastgpt/global/support/permission/utils';
import type { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
import { pickCollaboratorIdFields } from './utils';
export type SyncChildrenPermissionResourceType = {
_id: string;
type: string;
teamId: string;
parentId?: ParentIdType;
};
/**
* sync the permission to all children folders.
*/
export async function syncChildrenPermission({
resource,
folderTypeList,
resourceType,
resourceModel,
session,
collaborators: latestClbList
}: {
resource: SyncChildrenPermissionResourceType;
// when the resource is a folder
folderTypeList: string[];
resourceModel: typeof Model;
resourceType: PerResourceTypeEnum;
// should be provided when inheritPermission is true
session: ClientSession;
collaborators: CollaboratorItemType[];
}) {
// only folder has permission
const isFolder = folderTypeList.includes(resource.type);
const teamId = resource.teamId;
// If the 'root' is not a folder, which means the 'root' has no children, no need to sync.
if (!isFolder) return;
// get all the resource permission of the app
const allFolders = await resourceModel
.find(
{
teamId,
inheritPermission: true,
type: {
$in: folderTypeList
}
},
'_id parentId'
)
.lean<SyncChildrenPermissionResourceType[]>()
.session(session);
const allClbs = await MongoResourcePermission.find({
resourceType,
teamId,
resourceId: {
$in: allFolders.map((folder) => folder._id)
}
})
.lean()
.session(session);
/** ResourceMap<resourceId, resourceType> */
const resourceMap = new Map<string, SyncChildrenPermissionResourceType>();
/** parentChildrenMap<parentId, resourceType[]> */
const parentChildrenMap = new Map<string, SyncChildrenPermissionResourceType[]>();
// init the map
allFolders.forEach((resource) => {
resourceMap.set(resource._id, resource);
const parentId = String(resource.parentId);
if (!parentChildrenMap.has(parentId)) {
parentChildrenMap.set(parentId, []);
}
parentChildrenMap.get(parentId)!.push(resource);
});
/** resourceIdPermissionMap<resourceId, CollaboratorItemType[]>
* save the clb virtual state, not the real state at present in the DB.
*/
const resourceIdClbMap = new Map<string, ResourcePermissionType[]>();
// Initialize the resourceIdPermissionMap
for (const clb of allClbs) {
const resourceId = clb.resourceId;
const arr = resourceIdClbMap.get(resourceId);
if (!arr) {
resourceIdClbMap.set(resourceId, [clb]);
} else {
arr.push(clb);
}
}
// BFS to get all children
const queue = [String(resource._id)];
const ops: AnyBulkWriteOperation<ResourcePermissionType>[] = [];
const latestClbMap = new Map(latestClbList.map((clb) => [getCollaboratorId(clb), { ...clb }]));
while (queue.length) {
const parentId = String(queue.shift());
const _children = parentChildrenMap.get(parentId) || [];
if (_children.length === 0) continue;
for (const child of _children) {
// 1. get parent's permission and what permission I have.
const parentClbs = resourceIdClbMap.get(String(child.parentId)) || [];
const myClbs = resourceIdClbMap.get(child._id) || [];
const myClbsIdSet = new Set(myClbs.map((clb) => getCollaboratorId(clb)));
// add or update
for (const latestClb of latestClbList) {
if (latestClb.permission === OwnerRoleVal) {
continue;
}
if (!myClbsIdSet.has(getCollaboratorId(latestClb))) {
ops.push({
insertOne: {
document: {
resourceId: child._id,
resourceType,
teamId,
permission: latestClb.permission,
...pickCollaboratorIdFields(latestClb)
} as ResourcePermissionType
}
});
} else {
const myclb = myClbs.find(
(clb) => getCollaboratorId(latestClb) === getCollaboratorId(clb)
)!;
ops.push({
updateOne: {
filter: {
resourceId: child._id,
teamId,
...pickCollaboratorIdFields(latestClb),
resourceType
},
update: {
permission: sumPer(myclb.permission, latestClb.permission)
}
}
});
}
}
// delele
for (const myClb of myClbs) {
const parentClb = parentClbs.find(
(clb) => getCollaboratorId(clb) === getCollaboratorId(myClb)
);
// the new collaborators doesnt have it, and the permission is same.
// remove it
if (
!latestClbMap.get(getCollaboratorId(myClb)) &&
parentClb &&
myClb.permission === parentClb.permission
) {
ops.push({
deleteOne: {
filter: {
resourceId: child._id,
teamId,
...pickCollaboratorIdFields(myClb),
resourceType
}
}
});
}
}
queue.push(child._id);
}
}
await MongoResourcePermission.bulkWrite(ops, { session });
return;
}
/** Resume the inherit permission of the resource.
1. Folder: Sync parent's defaultPermission and clbs, and sync its children.
2. Resource: Sync parent's defaultPermission, and delete all its clbs.
*/
export async function resumeInheritPermission({
resource,
folderTypeList,
resourceType,
resourceModel,
session
}: {
resource: SyncChildrenPermissionResourceType;
folderTypeList: string[];
resourceType: PerResourceTypeEnum;
resourceModel: typeof Model;
session?: ClientSession;
}) {
const isFolder = folderTypeList.includes(resource.type);
// Folder resource, need to sync children
const [parentClbs, oldMyClbs] = await Promise.all([
getResourceOwnedClbs({
resourceId: resource.parentId,
teamId: resource.teamId,
resourceType
}),
getResourceOwnedClbs({
resourceId: resource._id,
teamId: resource.teamId,
resourceType
})
]);
const parentOwner = parentClbs.find((clb) => clb.permission === OwnerRoleVal);
const collaborators = mergeCollaboratorList({
parentClbs,
childClbs: oldMyClbs
});
const parentManage = collaborators.find(
(clb) => parentOwner?.tmbId && clb.tmbId && parentOwner?.tmbId === clb.tmbId
);
if (parentManage) parentManage.permission = ManageRoleVal;
console.log(collaborators);
const fn = async (session: ClientSession) => {
if (isFolder) {
// sync self
await syncCollaborators({
resourceType,
collaborators,
teamId: resource.teamId,
resourceId: resource._id,
session
});
// sync children
await syncChildrenPermission({
resource,
resourceModel,
folderTypeList,
resourceType,
session,
collaborators
});
}
await resourceModel.updateOne(
{
_id: resource._id
},
{
inheritPermission: true
},
{ session }
);
};
if (session) {
return fn(session);
} else {
return mongoSessionRun(fn);
}
}
/**
* sync parent collaborators to children.
*/
export async function syncCollaborators({
resourceType,
teamId,
resourceId,
collaborators,
session
}: {
resourceType: PerResourceTypeEnum;
teamId: string;
resourceId: string;
collaborators: CollaboratorItemType[];
session: ClientSession;
}) {
// should change parent owner permission into manage
collaborators.forEach((clb) => {
if (clb.permission === OwnerRoleVal) {
clb.permission = ManageRoleVal;
}
});
const parentClbMap = new Map(collaborators.map((clb) => [getCollaboratorId(clb), clb]));
const clbsNow = await MongoResourcePermission.find({
resourceType,
teamId,
resourceId
})
.lean()
.session(session);
const ops: AnyBulkWriteOperation<ResourcePermissionType>[] = [];
for (const clb of clbsNow) {
const parentClb = parentClbMap.get(getCollaboratorId(clb));
const permission = sumPer(parentClb?.permission ?? NullPermissionVal, clb.permission);
ops.push({
updateOne: {
filter: {
teamId,
resourceId,
resourceType,
...pickCollaboratorIdFields(clb)
},
update: {
permission
}
}
});
}
const parentHasAndIHaveNot = collaborators.filter(
(clb) => !clbsNow.some((myClb) => getCollaboratorId(clb) === getCollaboratorId(myClb))
);
for (const clb of parentHasAndIHaveNot) {
ops.push({
insertOne: {
document: {
teamId,
resourceId,
resourceType,
...pickCollaboratorIdFields(clb),
permission: clb.permission
} as ResourcePermissionType
}
});
}
await MongoResourcePermission.bulkWrite(ops, { session });
}