diff --git a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts index 884738fca..63ba40ffc 100644 --- a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts +++ b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts @@ -20,7 +20,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F rateLimit: writeLimit }, schema: { - description: "Create an additional privilege for identity.", + description: "Add an additional privilege for identity.", security: [ { bearerAuth: [] @@ -91,7 +91,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F rateLimit: writeLimit }, schema: { - description: "Update a specific privilege of an identity.", + description: "Update a specific identity privilege.", security: [ { bearerAuth: [] @@ -167,7 +167,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F rateLimit: writeLimit }, schema: { - description: "Delete a specific privilege of an identity.", + description: "Delete the specified identity privilege.", security: [ { bearerAuth: [] @@ -202,7 +202,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F rateLimit: readLimit }, schema: { - description: "Retrieve details of a specific privilege by privilege id.", + description: "Retrieve details of a specific privilege by id.", security: [ { bearerAuth: [] @@ -237,7 +237,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F rateLimit: readLimit }, schema: { - description: "Retrieve details of a specific privilege by privilege slug.", + description: "Retrieve details of a specific privilege by slug.", security: [ { bearerAuth: [] @@ -277,7 +277,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F rateLimit: readLimit }, schema: { - description: "List of a specific privilege of an identity in a project.", + description: "List privileges for the specified identity by project.", security: [ { bearerAuth: [] diff --git a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts index 383532c17..2469903bd 100644 --- a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts @@ -76,7 +76,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ slug, projectMembershipId: identityProjectMembership.id }); - if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" }); + if (existingSlug) throw new BadRequestError({ message: "Additional privilege with provided slug already exists" }); if (!dto.isTemporary) { const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({ @@ -117,7 +117,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorAuthMethod }: TUpdateIdentityPrivilegeByIdDTO) => { const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id); - if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" }); + if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` }); const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId }); if (!identityProjectMembership) @@ -150,7 +150,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ projectMembershipId: identityProjectMembership.id }); if (existingSlug && existingSlug.id !== identityPrivilege.id) - throw new BadRequestError({ message: "Additional privilege of provided slug exist" }); + throw new BadRequestError({ message: "Additional privilege with provided slug already exists" }); } const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary; @@ -189,7 +189,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ const deleteById = async ({ actorId, id, actor, actorOrgId, actorAuthMethod }: TDeleteIdentityPrivilegeByIdDTO) => { const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id); - if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" }); + if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` }); const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId }); if (!identityProjectMembership) @@ -231,7 +231,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorAuthMethod }: TGetIdentityPrivilegeDetailsByIdDTO) => { const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id); - if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" }); + if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` }); const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId }); if (!identityProjectMembership) @@ -264,7 +264,7 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({ actorAuthMethod }: TGetIdentityPrivilegeDetailsBySlugDTO) => { const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); - if (!project) throw new NotFoundError({ message: "Project not found" }); + if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` }); const projectId = project.id; const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId }); diff --git a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts index a2cd7909d..4c5bd36eb 100644 --- a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts +++ b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts @@ -2,8 +2,10 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability"; import { PackRule, unpackRules } from "@casl/ability/extra"; import ms from "ms"; -import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { isAtLeastAsPrivileged } from "@app/lib/casl"; +import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { ActorType } from "@app/services/auth/auth-type"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TPermissionServiceFactory } from "../permission/permission-service"; @@ -50,7 +52,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ }: TCreateUserPrivilegeDTO) => { const projectMembership = await projectMembershipDAL.findById(projectMembershipId); if (!projectMembership) - throw new NotFoundError({ message: `Project membership with ID '${projectMembershipId}' not found` }); + throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` }); const { permission } = await permissionService.getProjectPermission( actor, @@ -60,13 +62,24 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); + const { permission: entityPermission } = await permissionService.getProjectPermission( + ActorType.USER, + projectMembership.userId, + projectMembership.projectId, + actorAuthMethod, + actorOrgId + ); + const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, entityPermission); + if (!hasRequiredPriviledges) + throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" }); const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({ slug, projectId: projectMembership.projectId, userId: projectMembership.userId }); - if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" }); + if (existingSlug) + throw new BadRequestError({ message: `Additional privilege with provided slug ${slug} already exists` }); if (!dto.isTemporary) { const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({ @@ -109,7 +122,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ }: TUpdateUserPrivilegeDTO) => { const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId); if (!userPrivilege) - throw new NotFoundError({ message: `User additional privilege with ID '${privilegeId}' not found` }); + throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` }); const projectMembership = await projectMembershipDAL.findOne({ userId: userPrivilege.userId, @@ -129,6 +142,16 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); + const { permission: entityPermission } = await permissionService.getProjectPermission( + ActorType.USER, + projectMembership.userId, + projectMembership.projectId, + actorAuthMethod, + actorOrgId + ); + const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, entityPermission); + if (!hasRequiredPriviledges) + throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" }); if (dto?.slug) { const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({ @@ -137,7 +160,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ projectId: projectMembership.projectId }); if (existingSlug && existingSlug.id !== userPrivilege.id) - throw new BadRequestError({ message: "Additional privilege of provided slug exist" }); + throw new BadRequestError({ message: `Additional privilege with provided slug ${dto.slug} already exists` }); } const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary; @@ -178,7 +201,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => { const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId); if (!userPrivilege) - throw new NotFoundError({ message: `User additional privilege with ID '${privilegeId}' not found` }); + throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` }); const projectMembership = await projectMembershipDAL.findOne({ userId: userPrivilege.userId, @@ -214,7 +237,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ }: TGetUserPrivilegeDetailsDTO) => { const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId); if (!userPrivilege) - throw new NotFoundError({ message: `User additional privilege with ID '${privilegeId}' not found` }); + throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` }); const projectMembership = await projectMembershipDAL.findOne({ userId: userPrivilege.userId, @@ -249,7 +272,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({ }: TListUserPrivilegesDTO) => { const projectMembership = await projectMembershipDAL.findById(projectMembershipId); if (!projectMembership) - throw new NotFoundError({ message: `Project membership with ID '${projectMembershipId}' not found` }); + throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` }); const { permission } = await permissionService.getProjectPermission( actor, diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index eeee7aaac..e878dfec0 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -974,27 +974,27 @@ export const PROJECT_USER_ADDITIONAL_PRIVILEGE = { export const IDENTITY_ADDITIONAL_PRIVILEGE_V2 = { CREATE: { - identityId: "The ID of the identity to create.", + identityId: "The ID of the identity to create the privilege for", projectId: "The ID of the project of the identity in.", slug: "The slug of the privilege to create.", permission: "The permission for the privilege.", - isTemporary: "Whether the privilege is temporary.", + isTemporary: "Whether the privilege is temporary or permanent.", temporaryMode: "Type of temporary access given. Types: relative", - temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d", - temporaryAccessStartTime: "ISO time for which temporary access should begin." + temporaryRange: "The TTL for the temporary access given", + temporaryAccessStartTime: "The start time in ISO format when the temporary access should begin." }, UPDATE: { - id: "The id of the privilege of the identity.", + id: "The ID of the identity privilege.", identityId: "The ID of the identity to update.", slug: "The slug of the privilege to update.", privilegePermission: "The permission for the privilege.", isTemporary: "Whether the privilege is temporary.", temporaryMode: "Type of temporary access given. Types: relative", - temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d", - temporaryAccessStartTime: "ISO time for which temporary access should begin." + temporaryRange: "The TTL for the temporary access given", + temporaryAccessStartTime: "The start time in ISO format when the temporary access should begin." }, DELETE: { - id: "the id of the privilege of the identity.", + id: "The ID of the identity privilege.", identityId: "The ID of the identity to delete.", slug: "The slug of the privilege to delete." }, @@ -1004,10 +1004,10 @@ export const IDENTITY_ADDITIONAL_PRIVILEGE_V2 = { slug: "The slug of the privilege." }, GET_BY_ID: { - id: "The id of the privilege of the identity." + id: "The ID of the identity privilege." }, LIST: { - projectId: "The ID of the project of the identity in.", + projectId: "The ID of the project that the identity is in.", identityId: "The ID of the identity to list." } }; diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index ee763e89f..ca7df196c 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -118,7 +118,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider }) } }, - onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const membership = await server.services.projectMembership.getProjectMembershipById({ actorId: req.permission.id, diff --git a/frontend/src/hooks/api/roles/queries.tsx b/frontend/src/hooks/api/roles/queries.tsx index 00fd037cc..7333cb4af 100644 --- a/frontend/src/hooks/api/roles/queries.tsx +++ b/frontend/src/hooks/api/roles/queries.tsx @@ -38,7 +38,7 @@ const glob: JsInterpreter> = (node, object, context) => { const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob }); export const roleQueryKeys = { - getProjectRoles: (projectId: string) => ["roles", { projectSlug: projectId }] as const, + getProjectRoles: (projectId: string) => ["roles", { projectId }] as const, getProjectRoleBySlug: (projectId: string, roleSlug: string) => ["roles", { projectId, roleSlug }] as const, getOrgRoles: (orgId: string) => ["org-roles", { orgId }] as const, diff --git a/frontend/src/pages/project/[id]/identities/[identityId]/index.tsx b/frontend/src/pages/project/[id]/identities/[identityId]/index.tsx index 9161d4d47..ae8b2716a 100644 --- a/frontend/src/pages/project/[id]/identities/[identityId]/index.tsx +++ b/frontend/src/pages/project/[id]/identities/[identityId]/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { useTranslation } from "react-i18next"; import Head from "next/head"; diff --git a/frontend/src/pages/project/[id]/members/[membershipId]/index.tsx b/frontend/src/pages/project/[id]/members/[membershipId]/index.tsx index 2ec4b482c..033aa2b15 100644 --- a/frontend/src/pages/project/[id]/members/[membershipId]/index.tsx +++ b/frontend/src/pages/project/[id]/members/[membershipId]/index.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { useTranslation } from "react-i18next"; import Head from "next/head"; diff --git a/frontend/src/views/Project/IdentityDetailsPage/IdentityDetailPage.tsx b/frontend/src/views/Project/IdentityDetailsPage/IdentityDetailPage.tsx index 6743a89de..d924c5230 100644 --- a/frontend/src/views/Project/IdentityDetailsPage/IdentityDetailPage.tsx +++ b/frontend/src/views/Project/IdentityDetailsPage/IdentityDetailPage.tsx @@ -56,10 +56,10 @@ export const IdentityDetailsPage = withProjectPermission(
{(isAllowed) => (
{isDirty && ( diff --git a/frontend/src/views/Project/IdentityDetailsPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx b/frontend/src/views/Project/IdentityDetailsPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx index b2fecab9e..30ae0b540 100644 --- a/frontend/src/views/Project/IdentityDetailsPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx +++ b/frontend/src/views/Project/IdentityDetailsPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx @@ -98,7 +98,7 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe >

- Project Additional Privilege + Project Additional Privileges

handlePopUpOpen("modifyPrivilege", privilegeDetails)} > - {privilegeDetails.slug} + {privilegeDetails.slug} {(isAllowed) => ( Project User Access
{(isAllowed) => (