From ddbc4aeded453c4bf8aa32e48c06735e57d7ee75 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 5 Sep 2025 17:02:29 -0300 Subject: [PATCH 1/3] Improve requestProjectAccess check to retrieve users with permissions to grant access when no admin user is set --- .../src/services/project/project-service.ts | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 4261870b1..901064e25 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -16,6 +16,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionActions, ProjectPermissionCertificateActions, + ProjectPermissionMemberActions, ProjectPermissionPkiSubscriberActions, ProjectPermissionPkiTemplateActions, ProjectPermissionSecretActions, @@ -1852,9 +1853,49 @@ export const projectServiceFactory = ({ if (projectMember) throw new BadRequestError({ message: "User already has access to the project" }); const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId); - const filteredProjectMembers = projectMembers + + let filteredProjectMembers = projectMembers .filter((member) => member.roles.some((role) => role.role === ProjectMembershipRole.Admin)) .map((el) => el.user.email!); + + const customRolesWithMemberCreate = await projectRoleDAL.find({ projectId }); + const customRoleSlugsCanalCreate = customRolesWithMemberCreate + .filter((role) => { + try { + const permissions = JSON.parse(role.permissions as string) as Array<{ + action: string; + subject: string; + }>; + return permissions.some( + (perm) => + perm.action === ProjectPermissionMemberActions.Create && perm.subject === ProjectPermissionSub.Member + ); + } catch { + return false; + } + }) + .map((role) => role.slug); + + if (customRoleSlugsCanalCreate.length > 0) { + const usersWithCustomCreateMemberRole = projectMembers + .filter((member) => + member.roles.some((role) => role.customRoleSlug && customRoleSlugsCanalCreate.includes(role.customRoleSlug)) + ) + .map((el) => el.user.email!) + .filter(Boolean); + + if (usersWithCustomCreateMemberRole.length > 0) { + filteredProjectMembers = usersWithCustomCreateMemberRole; + } + } + + if (filteredProjectMembers.length === 0) { + throw new BadRequestError({ + message: + "No users in this project have permission to grant access. Please contact an organization administrator to assign appropriate permissions to project members." + }); + } + const org = await orgDAL.findOne({ id: permission.orgId }); const project = await projectDAL.findById(projectId); const userDetails = await userDAL.findById(permission.id); From 58222105118a11e12693c71cbf01ecc322aeb2a4 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 5 Sep 2025 18:02:50 -0300 Subject: [PATCH 2/3] Small improvement on requestProjectAccess --- .../src/services/project/project-service.ts | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 901064e25..591c3f3f1 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -1857,35 +1857,39 @@ export const projectServiceFactory = ({ let filteredProjectMembers = projectMembers .filter((member) => member.roles.some((role) => role.role === ProjectMembershipRole.Admin)) .map((el) => el.user.email!); + if (filteredProjectMembers.length === 0) { + const customRolesWithMemberCreate = await projectRoleDAL.find({ projectId }); + const customRoleSlugsCanalCreate = customRolesWithMemberCreate + .filter((role) => { + try { + const permissions = ( + typeof role.permissions === "string" ? JSON.parse(role.permissions) : role.permissions + ) as Array<[string, string, object?, number?]>; - const customRolesWithMemberCreate = await projectRoleDAL.find({ projectId }); - const customRoleSlugsCanalCreate = customRolesWithMemberCreate - .filter((role) => { - try { - const permissions = JSON.parse(role.permissions as string) as Array<{ - action: string; - subject: string; - }>; - return permissions.some( - (perm) => - perm.action === ProjectPermissionMemberActions.Create && perm.subject === ProjectPermissionSub.Member - ); - } catch { - return false; + return permissions.some(([permissionActions, permissionSubject]) => { + if (permissionSubject === ProjectPermissionSub.Member) { + const actionsList = permissionActions.split(",").map((action) => action.trim()); + return actionsList.includes(ProjectPermissionMemberActions.Create); + } + return false; + }); + } catch { + return false; + } + }) + .map((role) => role.slug); + + if (customRoleSlugsCanalCreate.length > 0) { + const usersWithCustomCreateMemberRole = projectMembers + .filter((member) => + member.roles.some((role) => role.customRoleSlug && customRoleSlugsCanalCreate.includes(role.customRoleSlug)) + ) + .map((el) => el.user.email!) + .filter(Boolean); + + if (usersWithCustomCreateMemberRole.length > 0) { + filteredProjectMembers = usersWithCustomCreateMemberRole; } - }) - .map((role) => role.slug); - - if (customRoleSlugsCanalCreate.length > 0) { - const usersWithCustomCreateMemberRole = projectMembers - .filter((member) => - member.roles.some((role) => role.customRoleSlug && customRoleSlugsCanalCreate.includes(role.customRoleSlug)) - ) - .map((el) => el.user.email!) - .filter(Boolean); - - if (usersWithCustomCreateMemberRole.length > 0) { - filteredProjectMembers = usersWithCustomCreateMemberRole; } } From b56af1cbdc7039cc087917cc61185d2652d66c0d Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 5 Sep 2025 23:06:22 -0300 Subject: [PATCH 3/3] Address PR suggestions --- .../src/services/project/project-service.ts | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 591c3f3f1..d59f20bc6 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -1,4 +1,5 @@ -import { ForbiddenError, subject } from "@casl/ability"; +import { createMongoAbility, ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"; +import { PackRule, unpackRules } from "@casl/ability/extra"; import slugify from "@sindresorhus/slugify"; import { @@ -20,6 +21,7 @@ import { ProjectPermissionPkiSubscriberActions, ProjectPermissionPkiTemplateActions, ProjectPermissionSecretActions, + ProjectPermissionSet, ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; @@ -1859,30 +1861,29 @@ export const projectServiceFactory = ({ .map((el) => el.user.email!); if (filteredProjectMembers.length === 0) { const customRolesWithMemberCreate = await projectRoleDAL.find({ projectId }); - const customRoleSlugsCanalCreate = customRolesWithMemberCreate + const customRoleSlugsCanCreate = customRolesWithMemberCreate .filter((role) => { try { const permissions = ( - typeof role.permissions === "string" ? JSON.parse(role.permissions) : role.permissions - ) as Array<[string, string, object?, number?]>; + typeof role.permissions === "string" + ? (JSON.parse(role.permissions) as PackRule>>[]) + : role.permissions + ) as PackRule>>[]; - return permissions.some(([permissionActions, permissionSubject]) => { - if (permissionSubject === ProjectPermissionSub.Member) { - const actionsList = permissionActions.split(",").map((action) => action.trim()); - return actionsList.includes(ProjectPermissionMemberActions.Create); - } - return false; - }); + const ability = createMongoAbility>( + unpackRules>>(permissions) + ); + return ability.can(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member); } catch { return false; } }) .map((role) => role.slug); - if (customRoleSlugsCanalCreate.length > 0) { + if (customRoleSlugsCanCreate.length > 0) { const usersWithCustomCreateMemberRole = projectMembers .filter((member) => - member.roles.some((role) => role.customRoleSlug && customRoleSlugsCanalCreate.includes(role.customRoleSlug)) + member.roles.some((role) => role.customRoleSlug && customRoleSlugsCanCreate.includes(role.customRoleSlug)) ) .map((el) => el.user.email!) .filter(Boolean); @@ -1896,7 +1897,7 @@ export const projectServiceFactory = ({ if (filteredProjectMembers.length === 0) { throw new BadRequestError({ message: - "No users in this project have permission to grant access. Please contact an organization administrator to assign appropriate permissions to project members." + "No users in this project have permission to grant you access. Please contact an organization administrator to assign the necessary permissions." }); }