perf: org permission check (#3500)

This commit is contained in:
Archer
2024-12-30 21:44:51 +08:00
committed by archer
parent fd9600c6f8
commit 5d1d4ff64f
6 changed files with 132 additions and 267 deletions

View File

@@ -22,7 +22,7 @@ import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/member
import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
import { UserModelSchema } from '@fastgpt/global/support/user/type'; import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { OrgSchemaType } from '@fastgpt/global/support/user/team/org/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 /** get resource permission for a team member
* If there is no permission for the team member, it will return undefined * If there is no permission for the team member, it will return undefined
@@ -41,15 +41,15 @@ export const getResourcePermission = async ({
teamId: string; teamId: string;
tmbId: string; tmbId: string;
} & ( } & (
| { | {
resourceType: 'team'; resourceType: 'team';
resourceId?: undefined; resourceId?: undefined;
} }
| { | {
resourceType: Omit<PerResourceTypeEnum, 'team'>; resourceType: Omit<PerResourceTypeEnum, 'team'>;
resourceId: string; resourceId: string;
} }
)): Promise<PermissionValueType | undefined> => { )): Promise<PermissionValueType | undefined> => {
// Personal permission has the highest priority // Personal permission has the highest priority
const tmbPer = ( const tmbPer = (
await MongoResourcePermission.findOne( await MongoResourcePermission.findOne(
@@ -69,61 +69,42 @@ export const getResourcePermission = async ({
} }
// If there is no personal permission, get the group permission // If there is no personal permission, get the group permission
const groupPer = await (async () => { const [groupPers, orgPers] = await Promise.all([
const groupIdList = (await getGroupsByTmbId({ tmbId, teamId })).map((item) => item._id); getGroupsByTmbId({ tmbId, teamId })
.then((res) => res.map((item) => item._id))
if (groupIdList.length === 0) { .then((groupIdList) =>
return undefined; MongoResourcePermission.find(
} {
teamId,
// get the maximum permission of the group resourceType,
const pers = ( groupId: {
await MongoResourcePermission.find( $in: groupIdList
{ },
teamId, resourceId
resourceType,
groupId: {
$in: groupIdList
}, },
resourceId 'permission'
}, ).lean()
'permission' )
).lean() .then((perList) => perList.map((item) => item.permission)),
).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 getGroupPer(pers); return concatPer([...groupPers, ...orgPers]);
})();
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;
}
return new Permission().addPer(groupPer, orgPer).value;
}; };
/* 仅取 members 不取 groups */ /* 仅取 members 不取 groups */
@@ -136,15 +117,15 @@ export async function getResourceAllClbs({
teamId: string; teamId: string;
session?: ClientSession; session?: ClientSession;
} & ( } & (
| { | {
resourceType: 'team'; resourceType: 'team';
resourceId?: undefined; resourceId?: undefined;
} }
| { | {
resourceType: Omit<PerResourceTypeEnum, 'team'>; resourceType: Omit<PerResourceTypeEnum, 'team'>;
resourceId?: string | null; resourceId?: string | null;
} }
)): Promise<ResourcePermissionType[]> { )): Promise<ResourcePermissionType[]> {
return MongoResourcePermission.find( return MongoResourcePermission.find(
{ {
resourceType: resourceType, resourceType: resourceType,
@@ -497,10 +478,10 @@ export const authFileToken = (token?: string) =>
}); });
}); });
export const getGroupPer = (groups: PermissionValueType[] = []) => { export const concatPer = (perList: PermissionValueType[] = []) => {
if (groups.length === 0) { if (perList.length === 0) {
return undefined; return undefined;
} }
return new Permission().addPer(...groups).value; return new Permission().addPer(...perList).value;
}; };

View File

@@ -1,9 +1,6 @@
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
import { MongoGroupMemberModel } from './groupMemberSchema'; import { MongoGroupMemberModel } from './groupMemberSchema';
import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; import { parseHeaderCert } from '../controller';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoResourcePermission } from '../schema';
import { getGroupPer, parseHeaderCert } from '../controller';
import { MongoMemberGroupModel } from './memberGroupSchema'; import { MongoMemberGroupModel } from './memberGroupSchema';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import { ClientSession } from 'mongoose'; import { ClientSession } from 'mongoose';
@@ -79,46 +76,6 @@ export const getGroupMembersByGroupId = async (groupId: string) => {
}).lean(); }).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<PerResourceTypeEnum, 'team'>;
}
)) => {
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 // auth group member role
export const authGroupMemberRole = async ({ export const authGroupMemberRole = async ({
groupId, groupId,

View File

@@ -4,50 +4,34 @@ import type { ClientSession } from 'mongoose';
import { MongoOrgModel } from './orgSchema'; import { MongoOrgModel } from './orgSchema';
import { MongoOrgMemberModel } from './orgMemberSchema'; 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 }) => export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) =>
MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean(); MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean();
export const getOrgsWithParentByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) => export const getOrgIdSetWithParentByTmbId = async ({
MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean().then((orgs) => { teamId,
const orgIds = new Set<string>(); tmbId
for (const org of orgs) { }: {
const orgId = String(org.orgId); teamId: string;
const parentIds = orgId.split('/').filter((id) => id); tmbId: string;
for (const parentId of parentIds) { }) => {
orgIds.add(parentId); const orgMembers = await MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId')
} .populate<{ org: { path: string } }>('org', 'path')
.lean();
const orgIds = new Set<string>();
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 ({ export const getChildrenByOrg = async ({
org, org,
@@ -58,14 +42,9 @@ export const getChildrenByOrg = async ({
teamId: string; teamId: string;
session?: ClientSession; session?: ClientSession;
}) => { }) => {
const children = await MongoOrgModel.find( return MongoOrgModel.find({ teamId, path: { $regex: `^${org.path}/${org._id}` } }, undefined, {
{ teamId, path: { $regex: `^${org.path}/${org._id}` } }, session
undefined, }).lean();
{
session
}
).lean();
return children;
}; };
export const getOrgAndChildren = async ({ export const getOrgAndChildren = async ({
@@ -92,8 +71,7 @@ export async function createRootOrg({
teamId: string; teamId: string;
session?: ClientSession; session?: ClientSession;
}) { }) {
// Create the root org return MongoOrgModel.create(
const [org] = await MongoOrgModel.create(
[ [
{ {
teamId, teamId,
@@ -103,72 +81,4 @@ export async function createRootOrg({
], ],
{ session } { 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<OrgMemberRole | undefined> => {
// 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;
// };

View File

@@ -33,6 +33,13 @@ export const OrgMemberSchema = new Schema({
// } // }
}); });
OrgMemberSchema.virtual('org', {
ref: OrgCollectionName,
localField: 'orgId',
foreignField: '_id',
justOne: true
});
try { try {
OrgMemberSchema.index( OrgMemberSchema.index(
{ {

View File

@@ -15,9 +15,9 @@ import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/
import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { replaceRegChars } from '@fastgpt/global/common/string/tools'; 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 { 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 = { export type ListAppBody = {
parentId?: ParentIdType; parentId?: ParentIdType;
@@ -49,14 +49,14 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
}), }),
...(parentId ...(parentId
? [ ? [
authApp({ authApp({
req, req,
authToken: true, authToken: true,
authApiKey: true, authApiKey: true,
appId: parentId, appId: parentId,
per: ReadPermissionVal per: ReadPermissionVal
}) })
] ]
: []) : [])
]); ]);
@@ -79,14 +79,17 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
}); });
return map; return map;
}), }),
getOrgsWithParentByTmbId({ getOrgIdSetWithParentByTmbId({
teamId, teamId,
tmbId tmbId
}) })
]); ]);
// Get my permissions // Get my permissions
const myPerList = perList.filter( 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 findAppsQuery = (() => { const findAppsQuery = (() => {
@@ -104,17 +107,17 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
? {} ? {}
: parentId : parentId
? { ? {
$or: [idList, parseParentIdInMongo(parentId)] $or: [idList, parseParentIdInMongo(parentId)]
} }
: { $or: [idList, { parentId: null }] }; : { $or: [idList, { parentId: null }] };
const searchMatch = searchKey const searchMatch = searchKey
? { ? {
$or: [ $or: [
{ name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }, { name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } },
{ intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } } { intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }
] ]
} }
: {}; : {};
if (searchKey) { if (searchKey) {
@@ -156,9 +159,11 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
const tmbPer = myPerList.find( const tmbPer = myPerList.find(
(item) => String(item.resourceId) === appId && !!item.tmbId (item) => String(item.resourceId) === appId && !!item.tmbId
)?.permission; )?.permission;
const groupPer = getGroupPer( const groupPer = concatPer(
myPerList 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) .map((item) => item.permission)
); );

View File

@@ -17,8 +17,8 @@ import { ApiRequestProps } from '@fastgpt/service/type/next';
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { replaceRegChars } from '@fastgpt/global/common/string/tools'; import { replaceRegChars } from '@fastgpt/global/common/string/tools';
import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers'; import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers';
import { getGroupPer } from '@fastgpt/service/support/permission/controller'; import { concatPer } from '@fastgpt/service/support/permission/controller';
import { getOrgsWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers';
export type GetDatasetListBody = { export type GetDatasetListBody = {
parentId: ParentIdType; parentId: ParentIdType;
@@ -39,14 +39,14 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
}), }),
...(parentId ...(parentId
? [ ? [
authDataset({ authDataset({
req, req,
authToken: true, authToken: true,
authApiKey: true, authApiKey: true,
per: ReadPermissionVal, per: ReadPermissionVal,
datasetId: parentId datasetId: parentId
}) })
] ]
: []) : [])
]); ]);
@@ -69,13 +69,16 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
}); });
return map; return map;
}), }),
getOrgsWithParentByTmbId({ getOrgIdSetWithParentByTmbId({
teamId, teamId,
tmbId tmbId
}) })
]); ]);
const myPerList = perList.filter( 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 = (() => { const findDatasetQuery = (() => {
@@ -85,17 +88,17 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
? {} ? {}
: parentId : parentId
? { ? {
$or: [idList, parseParentIdInMongo(parentId)] $or: [idList, parseParentIdInMongo(parentId)]
} }
: { $or: [idList, { parentId: null }] }; : { $or: [idList, { parentId: null }] };
const searchMatch = searchKey const searchMatch = searchKey
? { ? {
$or: [ $or: [
{ name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }, { name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } },
{ intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } } { intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }
] ]
} }
: {}; : {};
if (searchKey) { if (searchKey) {
@@ -127,9 +130,11 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
const tmbPer = myPerList.find( const tmbPer = myPerList.find(
(item) => String(item.resourceId) === datasetId && !!item.tmbId (item) => String(item.resourceId) === datasetId && !!item.tmbId
)?.permission; )?.permission;
const groupPer = getGroupPer( const groupPer = concatPer(
myPerList 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) .map((item) => item.permission)
); );
return new DatasetPermission({ return new DatasetPermission({