From 5d1d4ff64f73397b92bdad5a05db0438b7afec62 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Mon, 30 Dec 2024 21:44:51 +0800 Subject: [PATCH] perf: org permission check (#3500) --- .../service/support/permission/controller.ts | 107 ++++++------- .../permission/memberGroup/controllers.ts | 45 +----- .../support/permission/org/controllers.ts | 146 ++++-------------- .../support/permission/org/orgMemberSchema.ts | 7 + projects/app/src/pages/api/core/app/list.ts | 47 +++--- .../app/src/pages/api/core/dataset/list.ts | 47 +++--- 6 files changed, 132 insertions(+), 267 deletions(-) diff --git a/packages/service/support/permission/controller.ts b/packages/service/support/permission/controller.ts index c6e5b9fa644..d0b2fd0953f 100644 --- a/packages/service/support/permission/controller.ts +++ b/packages/service/support/permission/controller.ts @@ -22,7 +22,7 @@ import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/member import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; import { UserModelSchema } from '@fastgpt/global/support/user/type'; import { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type'; -import { getOrgsWithParentByTmbId } from './org/controllers'; +import { getOrgIdSetWithParentByTmbId } from './org/controllers'; /** get resource permission for a team member * If there is no permission for the team member, it will return undefined @@ -41,15 +41,15 @@ export const getResourcePermission = async ({ teamId: string; tmbId: string; } & ( - | { + | { resourceType: 'team'; resourceId?: undefined; } - | { + | { resourceType: Omit; resourceId: string; } - )): Promise => { +)): Promise => { // Personal permission has the highest priority const tmbPer = ( await MongoResourcePermission.findOne( @@ -69,61 +69,42 @@ export const getResourcePermission = async ({ } // If there is no personal permission, get the group permission - const groupPer = await (async () => { - const groupIdList = (await getGroupsByTmbId({ tmbId, teamId })).map((item) => item._id); - - if (groupIdList.length === 0) { - return undefined; - } - - // get the maximum permission of the group - const pers = ( - await MongoResourcePermission.find( - { - teamId, - resourceType, - groupId: { - $in: groupIdList + const [groupPers, orgPers] = await Promise.all([ + getGroupsByTmbId({ tmbId, teamId }) + .then((res) => res.map((item) => item._id)) + .then((groupIdList) => + MongoResourcePermission.find( + { + teamId, + resourceType, + groupId: { + $in: groupIdList + }, + resourceId }, - resourceId - }, - 'permission' - ).lean() - ).map((item) => item.permission); - - return getGroupPer(pers); - })(); - - const orgIds = await getOrgsWithParentByTmbId({ tmbId, teamId }).then((item) => Array.from(item)); - - if (orgIds.length === 0) { - return groupPer; - } - - // get the maximum permission of the org - const orgPers = ( - await MongoResourcePermission.find( - { - teamId, - resourceType, - orgId: { - $in: Array.from(orgIds) - }, - resourceId - }, - 'permission' - ).lean() - ).map((item) => item.permission); - - const orgPer = getGroupPer(orgPers); - - if (groupPer === undefined) { - return orgPer; - } else if (orgPer === undefined) { - return groupPer; - } + 'permission' + ).lean() + ) + .then((perList) => perList.map((item) => item.permission)), + getOrgIdSetWithParentByTmbId({ tmbId, teamId }) + .then((item) => Array.from(item)) + .then((orgIds) => + MongoResourcePermission.find( + { + teamId, + resourceType, + orgId: { + $in: Array.from(orgIds) + }, + resourceId + }, + 'permission' + ).lean() + ) + .then((perList) => perList.map((item) => item.permission)) + ]); - return new Permission().addPer(groupPer, orgPer).value; + return concatPer([...groupPers, ...orgPers]); }; /* 仅取 members 不取 groups */ @@ -136,15 +117,15 @@ export async function getResourceAllClbs({ teamId: string; session?: ClientSession; } & ( - | { + | { resourceType: 'team'; resourceId?: undefined; } - | { + | { resourceType: Omit; resourceId?: string | null; } - )): Promise { +)): Promise { return MongoResourcePermission.find( { resourceType: resourceType, @@ -497,10 +478,10 @@ export const authFileToken = (token?: string) => }); }); -export const getGroupPer = (groups: PermissionValueType[] = []) => { - if (groups.length === 0) { +export const concatPer = (perList: PermissionValueType[] = []) => { + if (perList.length === 0) { return undefined; } - return new Permission().addPer(...groups).value; + return new Permission().addPer(...perList).value; }; diff --git a/packages/service/support/permission/memberGroup/controllers.ts b/packages/service/support/permission/memberGroup/controllers.ts index 78bae10c910..1b672e4ebaa 100644 --- a/packages/service/support/permission/memberGroup/controllers.ts +++ b/packages/service/support/permission/memberGroup/controllers.ts @@ -1,9 +1,6 @@ import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; import { MongoGroupMemberModel } from './groupMemberSchema'; -import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; -import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; -import { MongoResourcePermission } from '../schema'; -import { getGroupPer, parseHeaderCert } from '../controller'; +import { parseHeaderCert } from '../controller'; import { MongoMemberGroupModel } from './memberGroupSchema'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { ClientSession } from 'mongoose'; @@ -79,46 +76,6 @@ export const getGroupMembersByGroupId = async (groupId: string) => { }).lean(); }; -/** - * Get tmb's group permission: the maximum permission of the group - * @param tmbId - * @param resourceId - * @param resourceType - * @returns the maximum permission of the group - */ -export const getGroupPermission = async ({ - tmbId, - resourceId, - teamId, - resourceType -}: { - tmbId: string; - teamId: string; -} & ( - | { - resourceId?: undefined; - resourceType: 'team'; - } - | { - resourceId: string; - resourceType: Omit; - } -)) => { - const groupIds = (await getGroupsByTmbId({ tmbId, teamId })).map((item) => item._id); - const groupPermissions = ( - await MongoResourcePermission.find({ - groupId: { - $in: groupIds - }, - resourceType, - resourceId, - teamId - }) - ).map((item) => item.permission); - - return getGroupPer(groupPermissions); -}; - // auth group member role export const authGroupMemberRole = async ({ groupId, diff --git a/packages/service/support/permission/org/controllers.ts b/packages/service/support/permission/org/controllers.ts index d46e83cf3a4..863d0288c3d 100644 --- a/packages/service/support/permission/org/controllers.ts +++ b/packages/service/support/permission/org/controllers.ts @@ -4,50 +4,34 @@ import type { ClientSession } from 'mongoose'; import { MongoOrgModel } from './orgSchema'; import { MongoOrgMemberModel } from './orgMemberSchema'; -// if role1 > role2, return 1 -// if role1 < role2, return -1 -// else return 0 -// export const compareRole = (role1: OrgMemberRole, role2: OrgMemberRole) => { -// if (role1 === OrgMemberRole.owner) { -// if (role2 === OrgMemberRole.owner) { -// return 0; -// } -// return 1; -// } -// if (role2 === OrgMemberRole.owner) { -// return -1; -// } -// if (role1 === OrgMemberRole.admin) { -// if (role2 === OrgMemberRole.admin) { -// return 0; -// } -// return 1; -// } -// if (role2 === OrgMemberRole.admin) { -// return -1; -// } -// return 0; -// }; - -// export const checkOrgRole = (role: OrgMemberRole, targetRole: OrgMemberRole) => { -// return compareRole(role, targetRole) >= 0; -// }; - export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) => MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean(); -export const getOrgsWithParentByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) => - MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean().then((orgs) => { - const orgIds = new Set(); - for (const org of orgs) { - const orgId = String(org.orgId); - const parentIds = orgId.split('/').filter((id) => id); - for (const parentId of parentIds) { - orgIds.add(parentId); - } +export const getOrgIdSetWithParentByTmbId = async ({ + teamId, + tmbId +}: { + teamId: string; + tmbId: string; +}) => { + const orgMembers = await MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId') + .populate<{ org: { path: string } }>('org', 'path') + .lean(); + + const orgIds = new Set(); + + for (const orgMember of orgMembers) { + orgIds.add(String(orgMember.orgId)); + + // Add parent org + const parentIds = orgMember.org.path.split('/').filter(Boolean); + for (const parentId of parentIds) { + orgIds.add(parentId); } - return orgIds; - }); + } + + return orgIds; +}; export const getChildrenByOrg = async ({ org, @@ -58,14 +42,9 @@ export const getChildrenByOrg = async ({ teamId: string; session?: ClientSession; }) => { - const children = await MongoOrgModel.find( - { teamId, path: { $regex: `^${org.path}/${org._id}` } }, - undefined, - { - session - } - ).lean(); - return children; + return MongoOrgModel.find({ teamId, path: { $regex: `^${org.path}/${org._id}` } }, undefined, { + session + }).lean(); }; export const getOrgAndChildren = async ({ @@ -92,8 +71,7 @@ export async function createRootOrg({ teamId: string; session?: ClientSession; }) { - // Create the root org - const [org] = await MongoOrgModel.create( + return MongoOrgModel.create( [ { teamId, @@ -103,72 +81,4 @@ export async function createRootOrg({ ], { session } ); - // Find the team's owner - // const owner = await MongoTeamMember.findOne({ teamId, role: 'owner' }, undefined); - // if (!owner) { - // return Promise.reject(TeamErrEnum.unAuthTeam); - // } - - // Set the owner as the org admin - // await MongoOrgMemberModel.create( - // [ - // { - // orgId: org._id, - // tmbId: owner._id - - // } - // ], - // { session } - // ); } - -// export const getOrgMemberRole = async ({ -// orgId, -// tmbId -// }: { -// orgId: string; -// tmbId: string; -// }): Promise => { -// let role: OrgMemberRole | undefined; -// const orgMember = await MongoOrgMemberModel.findOne({ -// orgId, -// tmbId -// }) -// .populate('orgId') -// .lean(); -// if (orgMember) { -// role = OrgMemberRole[orgMember.role]; -// } else { -// return role; -// } -// if (role === OrgMemberRole.owner) { -// return role; -// } -// // Check the parent orgs -// const org = orgMember.orgId as unknown as OrgSchemaType; -// if (!org) { -// return Promise.reject(TeamErrEnum.orgNotExist); -// } -// const parentIds = org.path.split('/').filter((id) => id); -// if (parentIds.length === 0) { -// return role; -// } -// const parentOrgMembers = await MongoOrgMemberModel.find({ -// orgId: { -// $in: parentIds -// }, -// tmbId -// }).lean(); -// // Update the role to the highest role -// for (const parentOrgMember of parentOrgMembers) { -// const parentRole = OrgMemberRole[parentOrgMember.role]; -// if (parentRole === OrgMemberRole.owner) { -// role = parentRole; -// break; -// } -// if (parentRole === OrgMemberRole.admin && role === OrgMemberRole.member) { -// role = parentRole; -// } -// } -// return role; -// }; diff --git a/packages/service/support/permission/org/orgMemberSchema.ts b/packages/service/support/permission/org/orgMemberSchema.ts index 1b70c555651..3abc5c233ed 100644 --- a/packages/service/support/permission/org/orgMemberSchema.ts +++ b/packages/service/support/permission/org/orgMemberSchema.ts @@ -33,6 +33,13 @@ export const OrgMemberSchema = new Schema({ // } }); +OrgMemberSchema.virtual('org', { + ref: OrgCollectionName, + localField: 'orgId', + foreignField: '_id', + justOne: true +}); + try { OrgMemberSchema.index( { diff --git a/projects/app/src/pages/api/core/app/list.ts b/projects/app/src/pages/api/core/app/list.ts index 29493908244..ee2b605e2d4 100644 --- a/projects/app/src/pages/api/core/app/list.ts +++ b/projects/app/src/pages/api/core/app/list.ts @@ -15,9 +15,9 @@ import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/ import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; import { replaceRegChars } from '@fastgpt/global/common/string/tools'; -import { getGroupPer } from '@fastgpt/service/support/permission/controller'; +import { concatPer } from '@fastgpt/service/support/permission/controller'; import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers'; -import { getOrgsWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; +import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; export type ListAppBody = { parentId?: ParentIdType; @@ -49,14 +49,14 @@ async function handler(req: ApiRequestProps): Promise): Promise String(item.tmbId) === String(tmbId) || myGroupMap.has(String(item.groupId)) || myOrgSet.has(String(item.orgId)) + (item) => + String(item.tmbId) === String(tmbId) || + myGroupMap.has(String(item.groupId)) || + myOrgSet.has(String(item.orgId)) ); const findAppsQuery = (() => { @@ -104,17 +107,17 @@ async function handler(req: ApiRequestProps): Promise): Promise String(item.resourceId) === appId && !!item.tmbId )?.permission; - const groupPer = getGroupPer( + const groupPer = concatPer( myPerList - .filter((item) => String(item.resourceId) === appId && (!!item.groupId || !!item.orgId)) + .filter( + (item) => String(item.resourceId) === appId && (!!item.groupId || !!item.orgId) + ) .map((item) => item.permission) ); diff --git a/projects/app/src/pages/api/core/dataset/list.ts b/projects/app/src/pages/api/core/dataset/list.ts index 0daf6caf51c..2a3e76c6236 100644 --- a/projects/app/src/pages/api/core/dataset/list.ts +++ b/projects/app/src/pages/api/core/dataset/list.ts @@ -17,8 +17,8 @@ import { ApiRequestProps } from '@fastgpt/service/type/next'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { replaceRegChars } from '@fastgpt/global/common/string/tools'; import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers'; -import { getGroupPer } from '@fastgpt/service/support/permission/controller'; -import { getOrgsWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; +import { concatPer } from '@fastgpt/service/support/permission/controller'; +import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; export type GetDatasetListBody = { parentId: ParentIdType; @@ -39,14 +39,14 @@ async function handler(req: ApiRequestProps) { }), ...(parentId ? [ - authDataset({ - req, - authToken: true, - authApiKey: true, - per: ReadPermissionVal, - datasetId: parentId - }) - ] + authDataset({ + req, + authToken: true, + authApiKey: true, + per: ReadPermissionVal, + datasetId: parentId + }) + ] : []) ]); @@ -69,13 +69,16 @@ async function handler(req: ApiRequestProps) { }); return map; }), - getOrgsWithParentByTmbId({ + getOrgIdSetWithParentByTmbId({ teamId, tmbId }) ]); const myPerList = perList.filter( - (item) => String(item.tmbId) === String(tmbId) || myGroupMap.has(String(item.groupId)) || myOrgSet.has(String(item.orgId)) + (item) => + String(item.tmbId) === String(tmbId) || + myGroupMap.has(String(item.groupId)) || + myOrgSet.has(String(item.orgId)) ); const findDatasetQuery = (() => { @@ -85,17 +88,17 @@ async function handler(req: ApiRequestProps) { ? {} : parentId ? { - $or: [idList, parseParentIdInMongo(parentId)] - } + $or: [idList, parseParentIdInMongo(parentId)] + } : { $or: [idList, { parentId: null }] }; const searchMatch = searchKey ? { - $or: [ - { name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }, - { intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } } - ] - } + $or: [ + { name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }, + { intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } } + ] + } : {}; if (searchKey) { @@ -127,9 +130,11 @@ async function handler(req: ApiRequestProps) { const tmbPer = myPerList.find( (item) => String(item.resourceId) === datasetId && !!item.tmbId )?.permission; - const groupPer = getGroupPer( + const groupPer = concatPer( myPerList - .filter((item) => String(item.resourceId) === datasetId && (!!item.groupId || !!item.orgId)) + .filter( + (item) => String(item.resourceId) === datasetId && (!!item.groupId || !!item.orgId) + ) .map((item) => item.permission) ); return new DatasetPermission({