From a5039494cd4ee063d25bd952e81a2a19b654f09f Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Fri, 15 Mar 2024 15:54:32 +0530 Subject: [PATCH] feat(server): completed routes for user additional privilege --- backend/src/@types/fastify.d.ts | 2 + backend/src/ee/routes/v1/index.ts | 7 + .../v1/user-additional-privilege-router.ts | 142 ++++++++++++++++++ backend/src/server/routes/index.ts | 11 +- .../routes/v1/project-membership-router.ts | 14 ++ .../src/server/routes/v1/project-router.ts | 14 ++ .../project-membership-dal.ts | 50 +++++- 7 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 backend/src/ee/routes/v1/user-additional-privilege-router.ts diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 9ed72e7822..54e220abf7 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -8,6 +8,7 @@ import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secr import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; +import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service"; import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service"; import { TScimServiceFactory } from "@app/ee/services/scim/scim-service"; import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; @@ -121,6 +122,7 @@ declare module "fastify" { telemetry: TTelemetryServiceFactory; dynamicSecret: TDynamicSecretServiceFactory; dynamicSecretLease: TDynamicSecretLeaseServiceFactory; + projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory; }; // this is exclusive use for middlewares in which we need to inject data // everywhere else access using service layer diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index b3fafcfdf4..0d7b45dede 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -15,6 +15,7 @@ import { registerSecretScanningRouter } from "./secret-scanning-router"; import { registerSecretVersionRouter } from "./secret-version-router"; import { registerSnapshotRouter } from "./snapshot-router"; import { registerTrustedIpRouter } from "./trusted-ip-router"; +import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router"; export const registerV1EERoutes = async (server: FastifyZodProvider) => { // org role starts with organization @@ -51,4 +52,10 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => { await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" }); await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" }); await server.register(registerSecretVersionRouter, { prefix: "/secret" }); + await server.register( + async (privilegeRouter) => { + await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" }); + }, + { prefix: "/additional-privilege" } + ); }; diff --git a/backend/src/ee/routes/v1/user-additional-privilege-router.ts b/backend/src/ee/routes/v1/user-additional-privilege-router.ts new file mode 100644 index 0000000000..fe40ee2597 --- /dev/null +++ b/backend/src/ee/routes/v1/user-additional-privilege-router.ts @@ -0,0 +1,142 @@ +import ms from "ms"; +import { z } from "zod"; + +import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas"; +import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/", + method: "POST", + schema: { + body: z.union([ + z.object({ + projectMembershipId: z.string(), + slug: z.string().max(60).trim(), + name: z.string().trim(), + description: z.string().trim().optional(), + permissions: z.any().array(), + isTemporary: z.literal(false).default(false) + }), + z.object({ + projectMembershipId: z.string(), + slug: z.string().max(60).trim(), + name: z.string().trim(), + description: z.string().trim().optional(), + permissions: z.any().array(), + isTemporary: z.literal(true), + temporaryMode: z.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode), + temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"), + temporaryAccessStartTime: z.string().datetime() + }) + ]), + response: { + 200: z.object({ + privilege: ProjectUserAdditionalPrivilegeSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const privilege = await server.services.projectUserAdditionalPrivilege.create({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + ...req.body, + permissions: JSON.stringify(req.body.permissions) + }); + return { privilege }; + } + }); + + server.route({ + url: "/:privilegeId", + method: "PATCH", + schema: { + params: z.object({ + privilegeId: z.string() + }), + body: z + .object({ + slug: z.string().max(60).trim(), + name: z.string().trim(), + description: z.string().trim().optional(), + permissions: z.any().array(), + isTemporary: z.boolean(), + temporaryMode: z.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode), + temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"), + temporaryAccessStartTime: z.string().datetime() + }) + .partial(), + response: { + 200: z.object({ + privilege: ProjectUserAdditionalPrivilegeSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const privilege = await server.services.projectUserAdditionalPrivilege.updateById({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + ...req.body, + permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined, + privilegeId: req.params.privilegeId + }); + return { privilege }; + } + }); + + server.route({ + url: "/:privilegeId", + method: "DELETE", + schema: { + params: z.object({ + privilegeId: z.string() + }), + response: { + 200: z.object({ + privilege: ProjectUserAdditionalPrivilegeSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const privilege = await server.services.projectUserAdditionalPrivilege.deleteById({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + privilegeId: req.params.privilegeId + }); + return { privilege }; + } + }); + + server.route({ + url: "/:privilegeId", + method: "GET", + schema: { + params: z.object({ + privilegeId: z.string() + }), + response: { + 200: z.object({ + privilege: ProjectUserAdditionalPrivilegeSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const privilege = await server.services.projectUserAdditionalPrivilege.getPrivilegeDetailsById({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + privilegeId: req.params.privilegeId + }); + return { privilege }; + } + }); +}; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 940c414e8b..4599a732b9 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -17,6 +17,8 @@ import { licenseDALFactory } from "@app/ee/services/license/license-dal"; import { licenseServiceFactory } from "@app/ee/services/license/license-service"; import { permissionDALFactory } from "@app/ee/services/permission/permission-dal"; import { permissionServiceFactory } from "@app/ee/services/permission/permission-service"; +import { projectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal"; +import { projectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service"; import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal"; import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service"; import { scimDALFactory } from "@app/ee/services/scim/scim-dal"; @@ -149,6 +151,7 @@ export const registerRoutes = async ( const projectDAL = projectDALFactory(db); const projectMembershipDAL = projectMembershipDALFactory(db); + const projectUserAdditionalPrivilegeDAL = projectUserAdditionalPrivilegeDALFactory(db); const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(db); const projectRoleDAL = projectRoleDALFactory(db); const projectEnvDAL = projectEnvDALFactory(db); @@ -345,6 +348,11 @@ export const registerRoutes = async ( projectRoleDAL, licenseService }); + const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({ + permissionService, + projectMembershipDAL, + projectUserAdditionalPrivilegeDAL + }); const projectKeyService = projectKeyServiceFactory({ permissionService, projectKeyDAL, @@ -638,7 +646,8 @@ export const registerRoutes = async ( trustedIp: trustedIpService, scim: scimService, secretBlindIndex: secretBlindIndexService, - telemetry: telemetryService + telemetry: telemetryService, + projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService }); server.decorate("store", { diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index 9e2f5bd22c..ea05594632 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -40,6 +40,20 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider lastName: true, id: true }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + additionalPrivileges: z.array( + z.object({ + id: z.string(), + name: z.string(), + slug: z.string(), + description: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional(), + createdAt: z.date() + }) + ), roles: z.array( z.object({ id: z.string(), diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 55f1fdda9e..95619a5100 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -73,6 +73,20 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { lastName: true, id: true }).merge(UserEncryptionKeysSchema.pick({ publicKey: true })), + additionalPrivileges: z.array( + z.object({ + id: z.string(), + name: z.string(), + slug: z.string(), + description: z.string().optional().nullable(), + isTemporary: z.boolean(), + temporaryMode: z.string().optional().nullable(), + temporaryRange: z.string().nullable().optional(), + temporaryAccessStartTime: z.date().nullable().optional(), + temporaryAccessEndTime: z.date().nullable().optional(), + createdAt: z.date() + }) + ), roles: z.array( z.object({ id: z.string(), diff --git a/backend/src/services/project-membership/project-membership-dal.ts b/backend/src/services/project-membership/project-membership-dal.ts index 8faab487e8..fae6c26ba9 100644 --- a/backend/src/services/project-membership/project-membership-dal.ts +++ b/backend/src/services/project-membership/project-membership-dal.ts @@ -29,6 +29,11 @@ export const projectMembershipDALFactory = (db: TDbClient) => { `${TableName.ProjectUserMembershipRole}.customRoleId`, `${TableName.ProjectRoles}.id` ) + .leftJoin( + TableName.ProjectUserAdditionalPrivilege, + `${TableName.ProjectMembership}.id`, + `${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId` + ) .select( db.ref("id").withSchema(TableName.ProjectMembership), db.ref("isGhost").withSchema(TableName.Users), @@ -47,7 +52,23 @@ export const projectMembershipDALFactory = (db: TDbClient) => { db.ref("isTemporary").withSchema(TableName.ProjectUserMembershipRole), db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole), db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole), - db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole) + db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole), + db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApId"), + db.ref("name").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApName"), + db.ref("description").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApDescription"), + db.ref("slug").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApSlug"), + db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"), + db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"), + db.ref("createdAt").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApCreatedAt"), + db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryRange"), + db + .ref("temporaryAccessStartTime") + .withSchema(TableName.ProjectUserAdditionalPrivilege) + .as("userApTemporaryAccessStartTime"), + db + .ref("temporaryAccessEndTime") + .withSchema(TableName.ProjectUserAdditionalPrivilege) + .as("userApTemporaryAccessEndTime") ) .where({ isGhost: false }); @@ -87,6 +108,33 @@ export const projectMembershipDALFactory = (db: TDbClient) => { temporaryAccessStartTime, isTemporary }) + }, + { + label: "additionalPrivileges" as const, + key: "userApId", + mapper: ({ + userApId, + userApDescription, + userApName, + userApSlug, + userApIsTemporary, + userApTemporaryMode, + userApTemporaryRange, + userApTemporaryAccessEndTime, + userApTemporaryAccessStartTime, + userApCreatedAt + }) => ({ + id: userApId, + name: userApName, + description: userApDescription, + slug: userApSlug, + temporaryRange: userApTemporaryRange, + temporaryMode: userApTemporaryMode, + temporaryAccessEndTime: userApTemporaryAccessEndTime, + temporaryAccessStartTime: userApTemporaryAccessStartTime, + isTemporary: userApIsTemporary, + createdAt: userApCreatedAt + }) } ] });