From e232282beac5f3c6e1e104b808205be6afe8ce09 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 30 Sep 2025 10:23:28 +0530 Subject: [PATCH] feat: completed all role routers --- backend/src/@types/fastify.d.ts | 13 ++ .../v1/deprecated-project-role-router.ts | 179 ++++++++---------- backend/src/ee/routes/v1/org-role-router.ts | 126 +++++++----- .../src/ee/routes/v1/project-role-router.ts | 119 ++++++------ .../v2/deprecated-project-role-router.ts | 97 +++++----- .../src/ee/services/group/group-service.ts | 5 +- .../permission/permission-service-types.ts | 4 +- backend/src/server/routes/index.ts | 94 ++++++++- backend/src/server/routes/sanitizedSchemas.ts | 4 +- .../routes/v1/project-membership-router.ts | 62 +++--- .../services/convertor/convertor-service.ts | 88 +++++++++ .../src/services/convertor/convertor-types.ts | 0 .../group-project/group-project-service.ts | 5 +- .../project-membership-identity-factory.ts | 38 ++-- .../membership-user/membership-user-dal.ts | 62 +++++- .../membership-user-service.ts | 2 +- .../membership-user/membership-user-types.ts | 5 +- .../project-membership-service.ts | 7 +- backend/src/services/role/role-service.ts | 13 +- backend/src/services/role/role-types.ts | 4 +- frontend/src/hooks/api/roles/mutation.tsx | 1 + 21 files changed, 581 insertions(+), 347 deletions(-) create mode 100644 backend/src/services/convertor/convertor-service.ts create mode 100644 backend/src/services/convertor/convertor-types.ts diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 088d99ee9..ce18b6bdf 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -53,6 +53,7 @@ import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-servi import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-types"; import { TAuthMode } from "@app/server/plugins/auth/inject-identity"; +import { TAdditionalPrivilegeServiceFactory } from "@app/services/additional-privilege/additional-privilege-service"; import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; import { TAuthLoginFactory } from "@app/services/auth/auth-login-service"; @@ -88,6 +89,9 @@ import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-a import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; import { TIntegrationServiceFactory } from "@app/services/integration/integration-service"; import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; +import { TMembershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service"; +import { TMembershipIdentityServiceFactory } from "@app/services/membership-identity/membership-identity-service"; +import { TMembershipUserServiceFactory } from "@app/services/membership-user/membership-user-service"; import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service"; import { TNotificationServiceFactory } from "@app/services/notification/notification-service"; import { TOfflineUsageReportServiceFactory } from "@app/services/offline-usage-report/offline-usage-report-service"; @@ -106,6 +110,7 @@ import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { TReminderServiceFactory } from "@app/services/reminder/reminder-types"; +import { TRoleServiceFactory } from "@app/services/role/role-service"; import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; @@ -125,6 +130,7 @@ import { TUserServiceFactory } from "@app/services/user/user-service"; import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service"; import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service"; import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service"; +import { TConvertorServiceFactory } from "@app/services/convertor/convertor-service"; declare module "@fastify/request-context" { interface RequestContextData { @@ -324,6 +330,13 @@ declare module "fastify" { pamAccount: TPamAccountServiceFactory; pamSession: TPamSessionServiceFactory; upgradePath: TUpgradePathService; + + membershipUser: TMembershipUserServiceFactory; + membershipIdentity: TMembershipIdentityServiceFactory; + membershipGroup: TMembershipGroupServiceFactory; + role: TRoleServiceFactory; + additionalPrivilege: TAdditionalPrivilegeServiceFactory; + convertor: TConvertorServiceFactory; }; // 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/deprecated-project-role-router.ts b/backend/src/ee/routes/v1/deprecated-project-role-router.ts index dc361626d..f8f73579e 100644 --- a/backend/src/ee/routes/v1/deprecated-project-role-router.ts +++ b/backend/src/ee/routes/v1/deprecated-project-role-router.ts @@ -1,7 +1,7 @@ import { packRules } from "@casl/ability/extra"; import { z } from "zod"; -import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas"; +import { AccessScope, ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { backfillPermissionV1SchemaToV2Schema, @@ -13,7 +13,6 @@ import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { SanitizedRoleSchemaV1 } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; -import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types"; export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ @@ -55,14 +54,16 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true)) ); - const role = await server.services.projectRole.createRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.SLUG, - projectSlug: req.params.projectSlug + const { id: projectId } = await server.services.convertor.projectSlugToId({ + slug: req.params.projectSlug, + orgId: req.permission.orgId + }); + const role = await server.services.role.createRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId }, data: { ...req.body, @@ -73,7 +74,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId, event: { type: EventType.CREATE_PROJECT_ROLE, metadata: { @@ -86,7 +87,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -131,12 +132,21 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv ? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true))) : undefined; - const role = await server.services.projectRole.updateRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - roleId: req.params.roleId, + const { id: projectId } = await server.services.convertor.projectSlugToId({ + slug: req.params.projectSlug, + orgId: req.permission.orgId + }); + + const role = await server.services.role.updateRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId + }, + selector: { + id: req.params.roleId + }, data: { ...req.body, permissions: stringifiedPermissions @@ -146,7 +156,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId: role.projectId as string, event: { type: EventType.UPDATE_PROJECT_ROLE, metadata: { @@ -159,7 +169,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -188,18 +198,27 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const role = await server.services.projectRole.deleteRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - roleId: req.params.roleId + const { id: projectId } = await server.services.convertor.projectSlugToId({ + slug: req.params.projectSlug, + orgId: req.permission.orgId + }); + + const role = await server.services.role.deleteRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId + }, + selector: { + id: req.params.roleId + } }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId: role.projectId as string, event: { type: EventType.DELETE_PROJECT_ROLE, metadata: { @@ -210,7 +229,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -238,17 +257,21 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const roles = await server.services.projectRole.listRoles({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.SLUG, - projectSlug: req.params.projectSlug - } + const { id: projectId } = await server.services.convertor.projectSlugToId({ + slug: req.params.projectSlug, + orgId: req.permission.orgId }); - return { roles }; + + const { roles } = await server.services.role.listRoles({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId + }, + data: {} + }); + return { roles: roles.map((el) => ({ ...el, projectId: el.projectId as string })) }; } }); @@ -265,78 +288,30 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv }), response: { 200: z.object({ - role: SanitizedRoleSchemaV1.omit({ version: true }) + role: SanitizedRoleSchemaV1 }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const role = await server.services.projectRole.getRoleBySlug({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.SLUG, - projectSlug: req.params.projectSlug - }, - roleSlug: req.params.slug + const { id: projectId } = await server.services.convertor.projectSlugToId({ + slug: req.params.projectSlug, + orgId: req.permission.orgId }); - return { role }; - } - }); - - server.route({ - method: "GET", - url: "/:projectId/permissions", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - projectId: z.string().trim() - }), - response: { - 200: z.object({ - data: z.object({ - membership: z.object({ - id: z.string(), - roles: z - .object({ - role: z.string() - }) - .array() - }), - assumedPrivilegeDetails: z - .object({ - actorId: z.string(), - actorType: z.string(), - actorName: z.string(), - actorEmail: z.string().optional() - }) - .optional(), - permissions: z.any().array() - }) - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission( - req.permission.id, - req.params.projectId, - req.permission.authMethod, - req.permission.orgId - ); - - return { - data: { - permissions, - membership, - assumedPrivilegeDetails + const role = await server.services.role.getRoleBySlug({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId + }, + selector: { + slug: req.params.slug } - }; + }); + + return { role: { ...role, projectId: role.projectId as string } }; } }); }; diff --git a/backend/src/ee/routes/v1/org-role-router.ts b/backend/src/ee/routes/v1/org-role-router.ts index 070462d47..942ace9fa 100644 --- a/backend/src/ee/routes/v1/org-role-router.ts +++ b/backend/src/ee/routes/v1/org-role-router.ts @@ -1,7 +1,9 @@ +import { packRules } from "@casl/ability/extra"; import { z } from "zod"; -import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas"; +import { AccessScope, OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { OrgPermissionSchema } from "@app/ee/services/permission/org-permission"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -25,8 +27,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { ), name: z.string().trim(), description: z.string().trim().nullish(), - // TODO(scott): once UI refactored permissions: OrgPermissionSchema.array() - permissions: z.any().array() + permissions: OrgPermissionSchema.array() }), response: { 200: z.object({ @@ -36,13 +37,18 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const role = await server.services.orgRole.createRole( - req.permission.id, - req.params.organizationId, - req.body, - req.permission.authMethod, - req.permission.orgId - ); + const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions)); + const role = await server.services.role.createRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.params.organizationId + }, + data: { + ...req.body, + permissions: stringifiedPermissions + } + }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, @@ -59,7 +65,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { } }); - return { role }; + return { role: { ...role, orgId: role.orgId as string } }; } }); @@ -82,14 +88,17 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const role = await server.services.orgRole.getRole( - req.permission.id, - req.params.organizationId, - req.params.roleId, - req.permission.authMethod, - req.permission.orgId - ); - return { role }; + const role = await server.services.role.getRoleById({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.params.organizationId + }, + selector: { + id: req.params.roleId + } + }); + return { role: { ...role, orgId: role.orgId as string } }; } }); @@ -114,8 +123,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { .optional(), name: z.string().trim().optional(), description: z.string().trim().nullish(), - // TODO(scott): once UI refactored permissions: OrgPermissionSchema.array().optional() - permissions: z.any().array().optional() + permissions: OrgPermissionSchema.array().optional() }), response: { 200: z.object({ @@ -125,14 +133,21 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const role = await server.services.orgRole.updateRole( - req.permission.id, - req.params.organizationId, - req.params.roleId, - req.body, - req.permission.authMethod, - req.permission.orgId - ); + const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined; + const role = await server.services.role.updateRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.params.organizationId + }, + selector: { + id: req.params.roleId + }, + data: { + ...req.body, + permissions: stringifiedPermissions + } + }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, @@ -149,7 +164,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { } }); - return { role }; + return { role: { ...role, orgId: role.orgId as string } }; } }); @@ -172,13 +187,16 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const role = await server.services.orgRole.deleteRole( - req.permission.id, - req.params.organizationId, - req.params.roleId, - req.permission.authMethod, - req.permission.orgId - ); + const role = await server.services.role.deleteRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.params.organizationId + }, + selector: { + id: req.params.roleId + } + }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, @@ -189,10 +207,11 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { } }); - return { role }; + return { role: { ...role, orgId: role.orgId as string } }; } }); + // TODO(simp): switch to top level roles server.route({ method: "GET", url: "/:organizationId/roles", @@ -206,25 +225,30 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { response: { 200: z.object({ data: z.object({ - roles: OrgRolesSchema.omit({ permissions: true }) - .merge(z.object({ permissions: z.unknown() })) - .array() + roles: OrgRolesSchema.omit({ permissions: true }).array() }) }) } }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const roles = await server.services.orgRole.listRoles( - req.permission.id, - req.params.organizationId, - req.permission.authMethod, - req.permission.orgId - ); - return { data: { roles } }; + const { roles } = await server.services.role.listRoles({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.permission.orgId + }, + data: {} + }); + return { + data: { + roles: roles.map((el) => ({ ...el, orgId: el.orgId as string })) + } + }; } }); + // TODO(simp): get this fixed server.route({ method: "GET", url: "/:organizationId/permissions", @@ -237,20 +261,20 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - membership: OrgMembershipsSchema, + memberships: OrgMembershipsSchema.array(), permissions: z.any().array() }) } }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const { permissions, membership } = await server.services.orgRole.getUserPermission( + const { permissions, memberships } = await server.services.orgRole.getUserPermission( req.permission.id, req.params.organizationId, req.permission.authMethod, req.permission.orgId ); - return { permissions, membership }; + return { permissions, memberships }; } }); }; diff --git a/backend/src/ee/routes/v1/project-role-router.ts b/backend/src/ee/routes/v1/project-role-router.ts index 5a20ad893..c46629ec7 100644 --- a/backend/src/ee/routes/v1/project-role-router.ts +++ b/backend/src/ee/routes/v1/project-role-router.ts @@ -1,7 +1,7 @@ import { packRules } from "@casl/ability/extra"; import { z } from "zod"; -import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas"; +import { ProjectMembershipRole, ProjectRolesSchema, AccessScope } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; @@ -11,7 +11,6 @@ import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; -import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types"; export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ @@ -55,13 +54,11 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { handler: async (req) => { const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions)); - const role = await server.services.projectRole.createRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.ID, + const role = await server.services.role.createRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, projectId: req.params.projectId }, data: { @@ -73,7 +70,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId: role.projectId as string, event: { type: EventType.CREATE_PROJECT_ROLE, metadata: { @@ -86,7 +83,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -133,12 +130,16 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined; - const role = await server.services.projectRole.updateRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - roleId: req.params.roleId, + const role = await server.services.role.updateRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + selector: { + id: req.params.roleId + }, data: { ...req.body, permissions: stringifiedPermissions @@ -148,7 +149,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId: role.projectId as string, event: { type: EventType.UPDATE_PROJECT_ROLE, metadata: { @@ -161,7 +162,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -192,18 +193,22 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const role = await server.services.projectRole.deleteRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - roleId: req.params.roleId + const role = await server.services.role.deleteRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + selector: { + id: req.params.roleId + } }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId: role.projectId as string, event: { type: EventType.DELETE_PROJECT_ROLE, metadata: { @@ -214,7 +219,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -244,17 +249,16 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const roles = await server.services.projectRole.listRoles({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.ID, + const { roles } = await server.services.role.listRoles({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, projectId: req.params.projectId - } + }, + data: {} }); - return { roles }; + return { roles: roles.map((el) => ({ ...el, projectId: el.projectId as string })) }; } }); @@ -273,24 +277,25 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { }), response: { 200: z.object({ - role: SanitizedRoleSchema.omit({ version: true }) + role: SanitizedRoleSchema }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const role = await server.services.projectRole.getRoleBySlug({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.ID, + const role = await server.services.role.getRoleBySlug({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, projectId: req.params.projectId }, - roleSlug: req.params.roleSlug + selector: { + slug: req.params.roleSlug + } }); - return { role }; + + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -307,14 +312,16 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { response: { 200: z.object({ data: z.object({ - membership: z.object({ - id: z.string(), - roles: z - .object({ - role: z.string() - }) - .array() - }), + memberships: z + .object({ + id: z.string(), + roles: z + .object({ + role: z.string() + }) + .array() + }) + .array(), assumedPrivilegeDetails: z .object({ actorId: z.string(), @@ -330,7 +337,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission( + const { permissions, memberships, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission( req.permission.id, req.params.projectId, req.permission.authMethod, @@ -340,7 +347,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { return { data: { permissions, - membership, + memberships, assumedPrivilegeDetails } }; diff --git a/backend/src/ee/routes/v2/deprecated-project-role-router.ts b/backend/src/ee/routes/v2/deprecated-project-role-router.ts index 326bda06a..0a44d4d21 100644 --- a/backend/src/ee/routes/v2/deprecated-project-role-router.ts +++ b/backend/src/ee/routes/v2/deprecated-project-role-router.ts @@ -1,7 +1,7 @@ import { packRules } from "@casl/ability/extra"; import { z } from "zod"; -import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas"; +import { AccessScope, ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; @@ -11,7 +11,6 @@ import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; -import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types"; export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ @@ -55,13 +54,11 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv handler: async (req) => { const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions)); - const role = await server.services.projectRole.createRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.ID, + const role = await server.services.role.createRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, projectId: req.params.projectId }, data: { @@ -73,7 +70,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId: req.params.projectId, event: { type: EventType.CREATE_PROJECT_ROLE, metadata: { @@ -86,7 +83,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -133,12 +130,16 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined; - const role = await server.services.projectRole.updateRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - roleId: req.params.roleId, + const role = await server.services.role.updateRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + selector: { + id: req.params.roleId + }, data: { ...req.body, permissions: stringifiedPermissions @@ -148,7 +149,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId: role.projectId as string, event: { type: EventType.UPDATE_PROJECT_ROLE, metadata: { @@ -161,7 +162,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -192,18 +193,22 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const role = await server.services.projectRole.deleteRole({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - roleId: req.params.roleId + const role = await server.services.role.deleteRole({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + selector: { + id: req.params.roleId + } }); await server.services.auditLog.createAuditLog({ ...req.auditLogInfo, orgId: req.permission.orgId, - projectId: role.projectId, + projectId: role.projectId as string, event: { type: EventType.DELETE_PROJECT_ROLE, metadata: { @@ -214,7 +219,7 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv } }); - return { role }; + return { role: { ...role, projectId: role.projectId as string } }; } }); @@ -244,17 +249,16 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const roles = await server.services.projectRole.listRoles({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.ID, + const { roles } = await server.services.role.listRoles({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, projectId: req.params.projectId - } + }, + data: {} }); - return { roles }; + return { roles: roles.map((el) => ({ ...el, projectId: el.projectId as string })) }; } }); @@ -273,24 +277,25 @@ export const registerDeprecatedProjectRoleRouter = async (server: FastifyZodProv }), response: { 200: z.object({ - role: SanitizedRoleSchema.omit({ version: true }) + role: SanitizedRoleSchema }) } }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const role = await server.services.projectRole.getRoleBySlug({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - filter: { - type: ProjectRoleServiceIdentifierType.ID, + const role = await server.services.role.getRoleBySlug({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, projectId: req.params.projectId }, - roleSlug: req.params.roleSlug + selector: { + slug: req.params.roleSlug + } }); - return { role }; + + return { role: { ...role, projectId: role.projectId as string } }; } }); }; diff --git a/backend/src/ee/services/group/group-service.ts b/backend/src/ee/services/group/group-service.ts index d4d30b5ae..7bb5be866 100644 --- a/backend/src/ee/services/group/group-service.ts +++ b/backend/src/ee/services/group/group-service.ts @@ -44,10 +44,7 @@ type TGroupServiceFactoryDep = { projectDAL: Pick; projectBotDAL: Pick; projectKeyDAL: Pick; - permissionService: Pick< - TPermissionServiceFactory, - "getOrgPermission" | "getOrgPermissionByRole" | "invalidateProjectPermissionCache" - >; + permissionService: Pick; licenseService: Pick; oidcConfigDAL: Pick; }; diff --git a/backend/src/ee/services/permission/permission-service-types.ts b/backend/src/ee/services/permission/permission-service-types.ts index 3b65c09cb..a3d2327c8 100644 --- a/backend/src/ee/services/permission/permission-service-types.ts +++ b/backend/src/ee/services/permission/permission-service-types.ts @@ -66,12 +66,12 @@ export type TPermissionServiceFactory = { actorOrgId: string | undefined ) => Promise<{ permission: MongoAbility; - memberships: TMemberships[]; + memberships: Array; hasRole: (role: string) => boolean; }>; getProjectPermission: (arg: TGetProjectPermissionArg) => Promise<{ permission: MongoAbility; - memberships: TMemberships[]; + memberships: Array; hasRole: (role: string) => boolean; }>; // TODO(simp): check what is this doing diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 406daa63d..d9634f110 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -147,6 +147,8 @@ import { TQueueServiceFactory } from "@app/queue"; import { readLimit } from "@app/server/config/rateLimiter"; import { registerSecretScanningV2Webhooks } from "@app/server/plugins/secret-scanner-v2"; import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue"; +import { additionalPrivilegeDALFactory } from "@app/services/additional-privilege/additional-privilege-dal"; +import { additionalPrivilegeServiceFactory } from "@app/services/additional-privilege/additional-privilege-service"; import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal"; import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { appConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; @@ -231,6 +233,14 @@ import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { kmsServiceFactory } from "@app/services/kms/kms-service"; +import { membershipDALFactory } from "@app/services/membership/membership-dal"; +import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; +import { membershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal"; +import { membershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service"; +import { membershipIdentityDALFactory } from "@app/services/membership-identity/membership-identity-dal"; +import { membershipIdentityServiceFactory } from "@app/services/membership-identity/membership-identity-service"; +import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal"; +import { membershipUserServiceFactory } from "@app/services/membership-user/membership-user-service"; import { microsoftTeamsIntegrationDALFactory } from "@app/services/microsoft-teams/microsoft-teams-integration-dal"; import { microsoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service"; import { projectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal"; @@ -283,6 +293,8 @@ import { reminderServiceFactory } from "@app/services/reminder/reminder-service" import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; +import { roleDALFactory } from "@app/services/role/role-dal"; +import { roleServiceFactory } from "@app/services/role/role-service"; import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretQueueFactory } from "@app/services/secret/secret-queue"; import { secretServiceFactory } from "@app/services/secret/secret-service"; @@ -531,17 +543,27 @@ export const registerRoutes = async ( const secretScanningV2DAL = secretScanningV2DALFactory(db); const keyValueStoreDAL = keyValueStoreDALFactory(db); + const membershipDAL = membershipDALFactory(db); + const membershipUserDAL = membershipUserDALFactory(db); + const membershipIdentityDAL = membershipIdentityDALFactory(db); + const membershipGroupDAL = membershipGroupDALFactory(db); + const additionalPrivilegeDAL = additionalPrivilegeDALFactory(db); + const membershipRoleDAL = membershipRoleDALFactory(db); + const roleDAL = roleDALFactory(db); + const eventBusService = eventBusFactory(server.redis); const sseService = sseServiceFactory(eventBusService, server.redis); const permissionService = permissionServiceFactory({ permissionDAL, - orgRoleDAL, - projectRoleDAL, serviceTokenDAL, projectDAL, - keyStore + keyStore, + roleDAL, + userDAL, + identityDAL }); + const assumePrivilegeService = assumePrivilegeServiceFactory({ projectDAL, permissionService @@ -556,6 +578,41 @@ export const registerRoutes = async ( projectDAL }); + const membershipUserService = membershipUserServiceFactory({ + licenseService, + membershipRoleDAL, + membershipUserDAL, + orgDAL, + permissionService, + roleDAL, + userDAL + }); + + const membershipIdentityService = membershipIdentityServiceFactory({ + membershipIdentityDAL, + membershipRoleDAL, + orgDAL, + permissionService, + roleDAL + }); + + const membershipGroupService = membershipGroupServiceFactory({ + membershipGroupDAL, + membershipRoleDAL, + roleDAL + }); + + const roleService = roleServiceFactory({ + permissionService, + roleDAL + }); + const additionalPrivilegeService = additionalPrivilegeServiceFactory({ + additionalPrivilegeDAL, + membershipDAL, + orgDAL, + permissionService + }); + const hsmService = hsmServiceFactory({ hsmModule, envConfig @@ -1538,7 +1595,8 @@ export const registerRoutes = async ( identityProjectDAL, licenseService, identityMetadataDAL, - keyStore + keyStore, + orgDAL }); const identityAuthTemplateService = identityAuthTemplateServiceFactory({ @@ -1563,8 +1621,10 @@ export const registerRoutes = async ( identityProjectDAL, identityOrgMembershipDAL, identityProjectMembershipRoleDAL, - projectRoleDAL + projectRoleDAL, + orgDAL }); + const identityProjectAdditionalPrivilegeService = identityProjectAdditionalPrivilegeServiceFactory({ projectDAL, identityProjectAdditionalPrivilegeDAL, @@ -1584,7 +1644,8 @@ export const registerRoutes = async ( identityOrgMembershipDAL, identityAccessTokenDAL, permissionService, - licenseService + licenseService, + orgDAL }); const identityUaService = identityUaServiceFactory({ @@ -1594,7 +1655,8 @@ export const registerRoutes = async ( identityUaClientSecretDAL, identityUaDAL, licenseService, - keyStore + keyStore, + orgDAL }); const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({ @@ -1604,6 +1666,7 @@ export const registerRoutes = async ( permissionService, licenseService, gatewayService, + orgDAL, gatewayV2Service, gatewayV2DAL, gatewayDAL, @@ -1611,6 +1674,7 @@ export const registerRoutes = async ( }); const identityGcpAuthService = identityGcpAuthServiceFactory({ identityGcpAuthDAL, + orgDAL, identityOrgMembershipDAL, identityAccessTokenDAL, permissionService, @@ -1619,6 +1683,7 @@ export const registerRoutes = async ( const identityAliCloudAuthService = identityAliCloudAuthServiceFactory({ identityAccessTokenDAL, + orgDAL, identityAliCloudAuthDAL, identityOrgMembershipDAL, licenseService, @@ -1627,6 +1692,7 @@ export const registerRoutes = async ( const identityTlsCertAuthService = identityTlsCertAuthServiceFactory({ identityAccessTokenDAL, + orgDAL, identityTlsCertAuthDAL, identityOrgMembershipDAL, licenseService, @@ -1636,6 +1702,7 @@ export const registerRoutes = async ( const identityAwsAuthService = identityAwsAuthServiceFactory({ identityAccessTokenDAL, + orgDAL, identityAwsAuthDAL, identityOrgMembershipDAL, licenseService, @@ -1644,6 +1711,7 @@ export const registerRoutes = async ( const identityAzureAuthService = identityAzureAuthServiceFactory({ identityAzureAuthDAL, + orgDAL, identityOrgMembershipDAL, identityAccessTokenDAL, permissionService, @@ -1652,6 +1720,7 @@ export const registerRoutes = async ( const identityOciAuthService = identityOciAuthServiceFactory({ identityAccessTokenDAL, + orgDAL, identityOciAuthDAL, identityOrgMembershipDAL, licenseService, @@ -1674,6 +1743,7 @@ export const registerRoutes = async ( const identityOidcAuthService = identityOidcAuthServiceFactory({ identityOidcAuthDAL, + orgDAL, identityOrgMembershipDAL, identityAccessTokenDAL, permissionService, @@ -1683,6 +1753,7 @@ export const registerRoutes = async ( const identityJwtAuthService = identityJwtAuthServiceFactory({ identityJwtAuthDAL, + orgDAL, permissionService, identityAccessTokenDAL, identityOrgMembershipDAL, @@ -1692,6 +1763,7 @@ export const registerRoutes = async ( const identityLdapAuthService = identityLdapAuthServiceFactory({ identityLdapAuthDAL, + orgDAL, permissionService, kmsService, identityAccessTokenDAL, @@ -2300,7 +2372,13 @@ export const registerRoutes = async ( pamResource: pamResourceService, pamAccount: pamAccountService, pamSession: pamSessionService, - upgradePath: upgradePathService + upgradePath: upgradePathService, + + membershipUser: membershipUserService, + membershipIdentity: membershipIdentityService, + membershipGroup: membershipGroupService, + role: roleService, + additionalPrivilege: additionalPrivilegeService }); const cronJobs: CronJob[] = []; diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 344224008..47fbb0e07 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -209,11 +209,11 @@ export const SanitizedIdentityPrivilegeSchema = IdentityProjectAdditionalPrivile ) }); -export const SanitizedRoleSchema = ProjectRolesSchema.extend({ +export const SanitizedRoleSchema = ProjectRolesSchema.omit({ version: true }).extend({ permissions: UnpackedPermissionSchema.array() }); -export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({ +export const SanitizedRoleSchemaV1 = ProjectRolesSchema.omit({ version: true }).extend({ permissions: UnpackedPermissionSchema.array().transform((caslPermission) => // first map and remove other actions of folder permission caslPermission diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index f3cdfe701..730f9ebe2 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { - OrgMembershipRole, + AccessScope, ProjectMembershipRole, ProjectMembershipsSchema, ProjectUserMembershipRolesSchema, @@ -67,14 +67,23 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { - const memberships = await server.services.projectMembership.getProjectMemberships({ - actorId: req.permission.id, - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.projectId + const { data: memberships } = await server.services.membershipUser.listMemberships({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + data: {} }); - return { memberships }; + + return { + memberships: memberships.map((el) => ({ + ...el, + userId: el.actorUserId as string, + projectId: req.params.projectId + })) + }; } }); @@ -242,20 +251,17 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const usernamesAndEmails = [...req.body.emails, ...req.body.usernames]; - const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({ - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - actor: req.permission.type, - inviteeEmails: usernamesAndEmails, - orgId: req.permission.orgId, - organizationRoleSlug: OrgMembershipRole.NoAccess, - projects: [ - { - id: req.params.projectId, - projectRoleSlug: req.body.roleSlugs || [ProjectMembershipRole.Member] - } - ] + const { memberships } = await server.services.membershipUser.createMembership({ + permission: req.permission, + scopeData: { + scope: AccessScope.Project, + orgId: req.permission.orgId, + projectId: req.params.projectId + }, + data: { + roles: (req.body.roleSlugs || [ProjectMembershipRole.Member]).map((role) => ({ isTemporary: false, role })), + usernames: usernamesAndEmails + } }); await server.services.auditLog.createAuditLog({ @@ -263,15 +269,21 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider ...req.auditLogInfo, event: { type: EventType.ADD_BATCH_PROJECT_MEMBER, - metadata: memberships.map(({ userId, id }) => ({ - userId: userId || "", + metadata: memberships.map(({ actorUserId, id }) => ({ + userId: actorUserId || "", membershipId: id, email: "" })) } }); - return { memberships }; + return { + memberships: memberships.map((el) => ({ + ...el, + userId: el.actorUserId as string, + projectId: req.params.projectId + })) + }; } }); diff --git a/backend/src/services/convertor/convertor-service.ts b/backend/src/services/convertor/convertor-service.ts new file mode 100644 index 000000000..0bff1a6b5 --- /dev/null +++ b/backend/src/services/convertor/convertor-service.ts @@ -0,0 +1,88 @@ +import { NotFoundError } from "@app/lib/errors"; +import { TMembershipDALFactory } from "../membership/membership-dal"; +import { TProjectDALFactory } from "../project/project-dal"; +import { AccessScope, AccessScopeData } from "@app/db/schemas"; + +type TConvertorServiceFactoryDep = { + projectDAL: Pick; + membershipDAL: Pick; +}; + +export type TConvertorServiceFactory = ReturnType; + +export const convertorServiceFactory = ({ projectDAL, membershipDAL }: TConvertorServiceFactoryDep) => { + const $getScopeDatabaseFields = (scopeData: AccessScopeData) => { + if (scopeData.scope === AccessScope.Organization) { + return { scopeOrgId: scopeData.orgId }; + } + + if (scopeData.scope === AccessScope.Namespace) { + return { scopeNamespaceId: scopeData.namespaceId, scopeOrgId: scopeData.orgId }; + } + + return { scopeProjectId: scopeData.projectId, scopeOrgId: scopeData.orgId }; + }; + + const projectSlugToId = async (dto: { slug: string; orgId: string }) => { + const project = await projectDAL.findOne({ + orgId: dto.orgId, + slug: dto.slug + }); + if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` }); + return project; + }; + + const userMembershipIdToUserId = async (scopeData: AccessScopeData, membershipId: string) => { + const dbFields = $getScopeDatabaseFields(scopeData); + + const membership = await membershipDAL.findOne({ + scope: scopeData.scope, + id: membershipId, + ...dbFields, + scopeOrgId: scopeData.orgId + }); + if (!membership || !membership.actorUserId) { + throw new NotFoundError({ message: `Membership with id ${membershipId} not found` }); + } + return membership.actorUserId; + }; + + const groupMembershipIdToGroupId = async (scopeData: AccessScopeData, membershipId: string) => { + const dbFields = $getScopeDatabaseFields(scopeData); + + const membership = await membershipDAL.findOne({ + scope: scopeData.scope, + id: membershipId, + ...dbFields, + scopeOrgId: scopeData.orgId + }); + if (!membership || !membership.actorGroupId) { + throw new NotFoundError({ message: `Membership with id ${membershipId} not found` }); + } + + return membership.actorGroupId; + }; + + const identityMembershipIdToGroupId = async (scopeData: AccessScopeData, membershipId: string) => { + const dbFields = $getScopeDatabaseFields(scopeData); + + const membership = await membershipDAL.findOne({ + scope: scopeData.scope, + id: membershipId, + ...dbFields, + scopeOrgId: scopeData.orgId + }); + if (!membership || !membership.actorIdentityId) { + throw new NotFoundError({ message: `Membership with id ${membershipId} not found` }); + } + + return membership.actorIdentityId; + }; + + return { + projectSlugToId, + userMembershipIdToUserId, + groupMembershipIdToGroupId, + identityMembershipIdToGroupId + }; +}; diff --git a/backend/src/services/convertor/convertor-types.ts b/backend/src/services/convertor/convertor-types.ts new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/services/group-project/group-project-service.ts b/backend/src/services/group-project/group-project-service.ts index 913aca2f3..1e41c919c 100644 --- a/backend/src/services/group-project/group-project-service.ts +++ b/backend/src/services/group-project/group-project-service.ts @@ -43,10 +43,7 @@ type TGroupProjectServiceFactoryDep = { projectRoleDAL: Pick; projectBotDAL: TProjectBotDALFactory; groupDAL: Pick; - permissionService: Pick< - TPermissionServiceFactory, - "getProjectPermission" | "getProjectPermissionByRole" | "invalidateProjectPermissionCache" - >; + permissionService: Pick; }; export type TGroupProjectServiceFactory = ReturnType; diff --git a/backend/src/services/membership-identity/project/project-membership-identity-factory.ts b/backend/src/services/membership-identity/project/project-membership-identity-factory.ts index 6ab04437c..76be11085 100644 --- a/backend/src/services/membership-identity/project/project-membership-identity-factory.ts +++ b/backend/src/services/membership-identity/project/project-membership-identity-factory.ts @@ -115,29 +115,27 @@ export const newProjectMembershipIdentityFactory = ({ const { shouldUseNewPrivilegeSystem } = await orgDAL.findById(dto.permission.orgId); const permissionRoles = await permissionService.getProjectPermissionByRoles( - dto.data.roles.map((el) => el.role), + dto.data.roles.filter((el) => el.role !== ProjectMembershipRole.NoAccess).map((el) => el.role), scope.value ); for (const permissionRole of permissionRoles) { - if (permissionRole?.role?.name !== ProjectMembershipRole.NoAccess) { - const permissionBoundary = validatePrivilegeChangeOperation( - shouldUseNewPrivilegeSystem, - ProjectPermissionIdentityActions.GrantPrivileges, - ProjectPermissionSub.Identity, - permission, - permissionRole.permission - ); - if (!permissionBoundary.isValid) - throw new PermissionBoundaryError({ - message: constructPermissionErrorMessage( - "Failed to create identity project membership", - shouldUseNewPrivilegeSystem, - ProjectPermissionIdentityActions.GrantPrivileges, - ProjectPermissionSub.Identity - ), - details: { missingPermissions: permissionBoundary.missingPermissions } - }); - } + const permissionBoundary = validatePrivilegeChangeOperation( + shouldUseNewPrivilegeSystem, + ProjectPermissionIdentityActions.GrantPrivileges, + ProjectPermissionSub.Identity, + permission, + permissionRole.permission + ); + if (!permissionBoundary.isValid) + throw new PermissionBoundaryError({ + message: constructPermissionErrorMessage( + "Failed to create identity project membership", + shouldUseNewPrivilegeSystem, + ProjectPermissionIdentityActions.GrantPrivileges, + ProjectPermissionSub.Identity + ), + details: { missingPermissions: permissionBoundary.missingPermissions } + }); } }; diff --git a/backend/src/services/membership-user/membership-user-dal.ts b/backend/src/services/membership-user/membership-user-dal.ts index e7be88bb1..864d053b7 100644 --- a/backend/src/services/membership-user/membership-user-dal.ts +++ b/backend/src/services/membership-user/membership-user-dal.ts @@ -44,6 +44,7 @@ export const membershipUserDALFactory = (db: TDbClient) => { }) .where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId) .where(`${TableName.Membership}.actorUserId`, userId) + .where(`${TableName.Users}.isGhost`, false) .where((qb) => { if (scopeData.scope === AccessScope.Organization) { void qb.where(`${TableName.Membership}.scope`, AccessScope.Organization); @@ -60,7 +61,9 @@ export const membershipUserDALFactory = (db: TDbClient) => { }) .select(selectAllTableCols(TableName.Membership)) .select( - db.ref("slug").withSchema(TableName.Role).as("roleSlug"), + db.ref("slug").withSchema(TableName.Role).as("customRoleSlug"), + db.ref("name").withSchema(TableName.Role).as("customRoleName"), + db.ref("id").withSchema(TableName.Role).as("customRoleId"), db.ref("id").withSchema(TableName.MembershipRole).as("membershipRoleId"), db.ref("role").withSchema(TableName.MembershipRole).as("membershipRole"), db.ref("temporaryMode").withSchema(TableName.MembershipRole).as("membershipRoleTemporaryMode"), @@ -78,19 +81,35 @@ export const membershipUserDALFactory = (db: TDbClient) => { db.ref("updatedAt").withSchema(TableName.MembershipRole).as("membershipRoleUpdatedAt"), db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"), db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"), - db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue") + db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"), + db.ref("username").withSchema(TableName.Users).as("userUsername"), + db.ref("email").withSchema(TableName.Users).as("userEmail"), + db.ref("firstName").withSchema(TableName.Users).as("userFirstName"), + db.ref("lastName").withSchema(TableName.Users).as("userLastName"), + db.ref("id").withSchema(TableName.Users).as("userId") ); const data = sqlNestRelationships({ data: docs, key: "id", - parentMapper: (el) => MembershipsSchema.parse(el), + parentMapper: (el) => ({ + ...MembershipsSchema.parse(el), + user: { + username: el.userUsername, + email: el.userEmail, + firstName: el.userFirstName, + lastName: el.userLastName, + id: el.userId + } + }), childrenMapper: [ { key: "membershipRoleId", label: "roles" as const, mapper: ({ - roleSlug, + customRoleSlug, + customRoleName, + customRoleId, membershipRoleId, membershipRole, membershipRoleIsTemporary, @@ -103,7 +122,9 @@ export const membershipUserDALFactory = (db: TDbClient) => { }) => ({ id: membershipRoleId, role: membershipRole, - customRoleSlug: roleSlug, + customRoleSlug, + customRoleName, + customRoleId, temporaryRange: membershipRoleTemporaryRange, temporaryMode: membershipRoleTemporaryMode, temporaryAccessStartTime: membershipRoleTemporaryAccessStartTime, @@ -140,6 +161,7 @@ export const membershipUserDALFactory = (db: TDbClient) => { .leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`) .distinct(`${TableName.Membership}.id`) .where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId) + .where(`${TableName.Users}.isGhost`, false) .where((qb) => { if (scopeData.scope === AccessScope.Organization) { void qb.where(`${TableName.Membership}.scope`, AccessScope.Organization); @@ -188,7 +210,9 @@ export const membershipUserDALFactory = (db: TDbClient) => { .whereIn(`${TableName.Membership}.id`, paginatedUsers) .select(selectAllTableCols(TableName.Membership)) .select( - db.ref("slug").withSchema(TableName.Role).as("roleSlug"), + db.ref("slug").withSchema(TableName.Role).as("customRoleSlug"), + db.ref("name").withSchema(TableName.Role).as("customRoleName"), + db.ref("id").withSchema(TableName.Role).as("customRoleId"), db.ref("id").withSchema(TableName.MembershipRole).as("membershipRoleId"), db.ref("role").withSchema(TableName.MembershipRole).as("membershipRole"), db.ref("temporaryMode").withSchema(TableName.MembershipRole).as("membershipRoleTemporaryMode"), @@ -203,7 +227,12 @@ export const membershipUserDALFactory = (db: TDbClient) => { .withSchema(TableName.MembershipRole) .as("membershipRoleTemporaryAccessEndTime"), db.ref("createdAt").withSchema(TableName.MembershipRole).as("membershipRoleCreatedAt"), - db.ref("updatedAt").withSchema(TableName.MembershipRole).as("membershipRoleUpdatedAt") + db.ref("updatedAt").withSchema(TableName.MembershipRole).as("membershipRoleUpdatedAt"), + db.ref("username").withSchema(TableName.Users).as("userUsername"), + db.ref("email").withSchema(TableName.Users).as("userEmail"), + db.ref("firstName").withSchema(TableName.Users).as("userFirstName"), + db.ref("lastName").withSchema(TableName.Users).as("userLastName"), + db.ref("id").withSchema(TableName.Users).as("userId") ) .select( db.raw( @@ -214,13 +243,24 @@ export const membershipUserDALFactory = (db: TDbClient) => { const data = sqlNestRelationships({ data: docs, key: "id", - parentMapper: (el) => MembershipsSchema.parse(el), + parentMapper: (el) => ({ + ...MembershipsSchema.parse(el), + user: { + username: el.userUsername, + email: el.userEmail, + firstName: el.userFirstName, + lastName: el.userLastName, + id: el.userId + } + }), childrenMapper: [ { key: "membershipRoleId", label: "roles" as const, mapper: ({ - roleSlug, + customRoleSlug, + customRoleName, + customRoleId, membershipRoleId, membershipRole, membershipRoleIsTemporary, @@ -233,7 +273,9 @@ export const membershipUserDALFactory = (db: TDbClient) => { }) => ({ id: membershipRoleId, role: membershipRole, - customRoleSlug: roleSlug, + customRoleSlug, + customRoleName, + customRoleId, temporaryRange: membershipRoleTemporaryRange, temporaryMode: membershipRoleTemporaryMode, temporaryAccessStartTime: membershipRoleTemporaryAccessStartTime, diff --git a/backend/src/services/membership-user/membership-user-service.ts b/backend/src/services/membership-user/membership-user-service.ts index 9ce1c8f4a..b32606c70 100644 --- a/backend/src/services/membership-user/membership-user-service.ts +++ b/backend/src/services/membership-user/membership-user-service.ts @@ -386,7 +386,7 @@ export const membershipUserServiceFactory = ({ limit: dto.data.limit, offset: dto.data.offset, username: dto.data.username, - role: dto.data.roles.length + role: dto.data?.roles?.length ? { [SearchResourceOperators.$in]: dto.data.roles } diff --git a/backend/src/services/membership-user/membership-user-types.ts b/backend/src/services/membership-user/membership-user-types.ts index a368e5a19..c90627402 100644 --- a/backend/src/services/membership-user/membership-user-types.ts +++ b/backend/src/services/membership-user/membership-user-types.ts @@ -55,14 +55,11 @@ export type TUpdateMembershipUserDTO = { export type TListMembershipUserDTO = { permission: OrgServiceActor; scopeData: AccessScopeData; - selector: { - actorId: string; - }; data: { limit?: number; offset?: number; username?: string; - roles: string[]; + roles?: string[]; }; }; diff --git a/backend/src/services/project-membership/project-membership-service.ts b/backend/src/services/project-membership/project-membership-service.ts index 301ce1bc0..4871bcad8 100644 --- a/backend/src/services/project-membership/project-membership-service.ts +++ b/backend/src/services/project-membership/project-membership-service.ts @@ -43,10 +43,7 @@ import { import { TProjectUserMembershipRoleDALFactory } from "./project-user-membership-role-dal"; type TProjectMembershipServiceFactoryDep = { - permissionService: Pick< - TPermissionServiceFactory, - "getProjectPermission" | "getProjectPermissionByRole" | "invalidateProjectPermissionCache" - >; + permissionService: Pick; smtpService: TSmtpService; projectBotDAL: TProjectBotDALFactory; projectMembershipDAL: TProjectMembershipDALFactory; @@ -573,6 +570,8 @@ export const projectMembershipServiceFactory = ({ tx ); + // TODO(simp): likewise check in all services if i am missing anything + // Deletion should handle all cleanups like wise await secretReminderRecipientsDAL.delete( { projectId, diff --git a/backend/src/services/role/role-service.ts b/backend/src/services/role/role-service.ts index f9a066cb0..5c02c0420 100644 --- a/backend/src/services/role/role-service.ts +++ b/backend/src/services/role/role-service.ts @@ -2,6 +2,7 @@ import { AccessScope, TableName } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; +import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; import { newNamespaceRoleFactory } from "./namespace/namespace-role-factory"; import { newOrgRoleFactory } from "./org/org-role-factory"; @@ -63,7 +64,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF [scope.key]: scope.value }); - return { ...role, [scope.key]: scope.value }; + return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) }; }; const updateRole = async (dto: TUpdateRoleDTO) => { @@ -99,7 +100,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF permissions: data?.permissions }); - return { ...role, [scope.key]: scope.value }; + return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) }; }; const deleteRole = async (dto: TDeleteRoleDTO) => { @@ -119,7 +120,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF [scope.key]: scope.value }); - return { ...role, [scope.key]: scope.value }; + return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) }; }; const listRoles = async (dto: TListRoleDTO) => { @@ -136,7 +137,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF { limit: dto.data.limit, offset: dto.data.offset, sort: [[`${TableName.Role}.slug` as "slug", "asc"]] } ); - return { roles }; + return { roles: roles.map((el) => ({ ...el, permissions: unpackPermissions(el.permissions) })) }; }; const getRoleById = async (dto: TGetRoleByIdDTO) => { @@ -152,7 +153,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF }); if (!role) throw new NotFoundError({ message: `Role with id ${dto.selector.id} not found` }); - return { ...role, [scope.key]: scope.value }; + return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) }; }; const getRoleBySlug = async (dto: TGetRoleBySlugDTO) => { @@ -168,7 +169,7 @@ export const roleServiceFactory = ({ roleDAL, permissionService }: TRoleServiceF }); if (!role) throw new NotFoundError({ message: `Role with slug ${dto.selector.slug} not found` }); - return { ...role, [scope.key]: scope.value }; + return { ...role, [scope.key]: scope.value, permissions: unpackPermissions(role.permissions) }; }; return { diff --git a/backend/src/services/role/role-types.ts b/backend/src/services/role/role-types.ts index a82ce7c4c..fb35db43d 100644 --- a/backend/src/services/role/role-types.ts +++ b/backend/src/services/role/role-types.ts @@ -16,7 +16,7 @@ export type TCreateRoleDTO = { scopeData: AccessScopeData; data: { name: string; - description?: string; + description?: string | null; slug: string; permissions: unknown; }; @@ -30,7 +30,7 @@ export type TUpdateRoleDTO = { }; data: Partial<{ name: string; - description?: string; + description?: string | null; slug: string; permissions: unknown; }>; diff --git a/frontend/src/hooks/api/roles/mutation.tsx b/frontend/src/hooks/api/roles/mutation.tsx index e9caa68d5..f62f2a7c0 100644 --- a/frontend/src/hooks/api/roles/mutation.tsx +++ b/frontend/src/hooks/api/roles/mutation.tsx @@ -76,6 +76,7 @@ export const useCreateOrgRole = () => { data: { role } } = await apiRequest.post(`/api/v1/organization/${orgId}/roles`, { ...dto, + // TODO(simp): removing packing permissions: permissions.length ? packRules(permissions) : [] });