diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index f23fcb7f4..fa5cd7a11 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -1583,6 +1583,7 @@ export const registerRoutes = async ( }); const identityService = identityServiceFactory({ + additionalPrivilegeDAL, permissionService, identityDAL, identityOrgMembershipDAL, diff --git a/backend/src/server/routes/v1/identity-router.ts b/backend/src/server/routes/v1/identity-router.ts index d6a42c4a2..b1798692d 100644 --- a/backend/src/server/routes/v1/identity-router.ts +++ b/backend/src/server/routes/v1/identity-router.ts @@ -249,7 +249,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => { permissions: true, description: true }).optional(), - identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true }).extend({ + identity: IdentitiesSchema.pick({ name: true, id: true, hasDeleteProtection: true, orgId: true }).extend({ authMethods: z.array(z.string()), activeLockoutAuthMethods: z.array(z.string()) }) diff --git a/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts b/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts index 274703c60..3f3c6cdf6 100644 --- a/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts +++ b/backend/src/services/identity-alicloud-auth/identity-alicloud-auth-service.ts @@ -13,7 +13,13 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; import { crypto } from "@app/lib/crypto"; -import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { + BadRequestError, + ForbiddenRequestError, + NotFoundError, + PermissionBoundaryError, + UnauthorizedError +} from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { logger } from "@app/lib/logger"; @@ -162,6 +168,9 @@ export const identityAliCloudAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) { throw new BadRequestError({ @@ -239,6 +248,9 @@ export const identityAliCloudAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) { throw new NotFoundError({ @@ -306,6 +318,9 @@ export const identityAliCloudAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) { throw new BadRequestError({ @@ -342,6 +357,9 @@ export const identityAliCloudAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.ALICLOUD_AUTH)) { throw new BadRequestError({ message: "The identity does not have Alibaba Cloud auth" diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts index 74912635e..59f1b3d5b 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts @@ -13,7 +13,13 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { + BadRequestError, + ForbiddenRequestError, + NotFoundError, + PermissionBoundaryError, + UnauthorizedError +} from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; @@ -240,6 +246,9 @@ export const identityAwsAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { throw new BadRequestError({ @@ -321,6 +330,9 @@ export const identityAwsAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { throw new NotFoundError({ @@ -389,6 +401,9 @@ export const identityAwsAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { throw new BadRequestError({ @@ -425,6 +440,9 @@ export const identityAwsAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AWS_AUTH)) { throw new BadRequestError({ message: "The identity does not have aws auth" diff --git a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts index 98e7b14a4..c85fabc8f 100644 --- a/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts +++ b/backend/src/services/identity-azure-auth/identity-azure-auth-service.ts @@ -10,7 +10,13 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { + BadRequestError, + ForbiddenRequestError, + NotFoundError, + PermissionBoundaryError, + UnauthorizedError +} from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; @@ -153,6 +159,9 @@ export const identityAzureAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { throw new BadRequestError({ @@ -233,6 +242,9 @@ export const identityAzureAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { throw new BadRequestError({ message: "Failed to update Azure Auth" @@ -303,6 +315,9 @@ export const identityAzureAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { throw new BadRequestError({ message: "The identity does not have Azure Auth attached" @@ -339,6 +354,9 @@ export const identityAzureAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.AZURE_AUTH)) { throw new BadRequestError({ message: "The identity does not have azure auth" diff --git a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts index 05a2c94f8..83d407fa7 100644 --- a/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts +++ b/backend/src/services/identity-gcp-auth/identity-gcp-auth-service.ts @@ -10,7 +10,13 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { + BadRequestError, + ForbiddenRequestError, + NotFoundError, + PermissionBoundaryError, + UnauthorizedError +} from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; @@ -193,6 +199,9 @@ export const identityGcpAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { throw new BadRequestError({ @@ -275,6 +284,9 @@ export const identityGcpAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { throw new BadRequestError({ @@ -347,6 +359,9 @@ export const identityGcpAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { throw new BadRequestError({ @@ -384,6 +399,9 @@ export const identityGcpAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.GCP_AUTH)) { throw new BadRequestError({ diff --git a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts index b50655286..87b5e8ea7 100644 --- a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts +++ b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts @@ -284,6 +284,9 @@ export const identityJwtAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) { throw new BadRequestError({ message: "Failed to add JWT Auth to already configured identity" @@ -388,6 +391,9 @@ export const identityJwtAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) { throw new BadRequestError({ @@ -493,6 +499,9 @@ export const identityJwtAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) { throw new BadRequestError({ @@ -542,6 +551,9 @@ export const identityJwtAuthServiceFactory = ({ if (!identityMembershipOrg) { throw new NotFoundError({ message: "Failed to find identity" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.JWT_AUTH)) { throw new BadRequestError({ diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index d2eefcda1..bdb6ecd67 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -26,7 +26,13 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { + BadRequestError, + ForbiddenRequestError, + NotFoundError, + PermissionBoundaryError, + UnauthorizedError +} from "@app/lib/errors"; import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway"; import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -513,6 +519,9 @@ export const identityKubernetesAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { throw new BadRequestError({ @@ -640,6 +649,9 @@ export const identityKubernetesAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { throw new BadRequestError({ @@ -788,6 +800,9 @@ export const identityKubernetesAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId }); if (!identityKubernetesAuth) { @@ -851,6 +866,9 @@ export const identityKubernetesAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.KUBERNETES_AUTH)) { throw new BadRequestError({ diff --git a/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts b/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts index f925ff245..ad76a60a1 100644 --- a/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts +++ b/backend/src/services/identity-ldap-auth/identity-ldap-auth-service.ts @@ -21,6 +21,7 @@ import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; import { BadRequestError, + ForbiddenRequestError, NotFoundError, PermissionBoundaryError, RateLimitError, @@ -254,6 +255,9 @@ export const identityLdapAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) { throw new BadRequestError({ @@ -426,6 +430,9 @@ export const identityLdapAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) { throw new NotFoundError({ @@ -590,6 +597,9 @@ export const identityLdapAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) { throw new BadRequestError({ @@ -638,6 +648,9 @@ export const identityLdapAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) { throw new BadRequestError({ message: "The identity does not have LDAP Auth attached" diff --git a/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts b/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts index b8991477d..2c5d59e2b 100644 --- a/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts +++ b/backend/src/services/identity-oci-auth/identity-oci-auth-service.ts @@ -14,7 +14,13 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; import { crypto } from "@app/lib/crypto"; -import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { + BadRequestError, + ForbiddenRequestError, + NotFoundError, + PermissionBoundaryError, + UnauthorizedError +} from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { logger } from "@app/lib/logger"; @@ -168,6 +174,9 @@ export const identityOciAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) { throw new BadRequestError({ @@ -247,6 +256,9 @@ export const identityOciAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) { throw new NotFoundError({ @@ -314,6 +326,9 @@ export const identityOciAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) { throw new BadRequestError({ @@ -350,6 +365,9 @@ export const identityOciAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OCI_AUTH)) { throw new BadRequestError({ message: "The identity does not have OCI auth" diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index 9372c940c..f3d17eb71 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -259,6 +259,9 @@ export const identityOidcAuthServiceFactory = ({ if (!identityMembershipOrg) { throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { throw new BadRequestError({ message: "Failed to add OIDC Auth to already configured identity" @@ -352,6 +355,9 @@ export const identityOidcAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { throw new BadRequestError({ @@ -442,6 +448,9 @@ export const identityOidcAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { throw new BadRequestError({ @@ -484,6 +493,9 @@ export const identityOidcAuthServiceFactory = ({ if (!identityMembershipOrg) { throw new NotFoundError({ message: "Failed to find identity" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.OIDC_AUTH)) { throw new BadRequestError({ diff --git a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts index 9d59ababf..d547f7449 100644 --- a/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts +++ b/backend/src/services/identity-tls-cert-auth/identity-tls-cert-auth-service.ts @@ -11,7 +11,13 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; -import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; +import { + BadRequestError, + ForbiddenRequestError, + NotFoundError, + PermissionBoundaryError, + UnauthorizedError +} from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; @@ -189,6 +195,9 @@ export const identityTlsCertAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { throw new BadRequestError({ @@ -272,6 +281,9 @@ export const identityTlsCertAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { throw new NotFoundError({ @@ -352,6 +364,9 @@ export const identityTlsCertAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { throw new BadRequestError({ @@ -397,6 +412,9 @@ export const identityTlsCertAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TLS_CERT_AUTH)) { throw new BadRequestError({ message: "The identity does not have TLS Certificate auth" diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 87ae18fd5..e3c7a486e 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -10,7 +10,7 @@ import { import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto"; -import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { ActorType, AuthTokenType } from "../auth/auth-type"; @@ -79,6 +79,9 @@ export const identityTokenAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ @@ -156,6 +159,9 @@ export const identityTokenAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ @@ -225,6 +231,9 @@ export const identityTokenAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ @@ -265,6 +274,9 @@ export const identityTokenAuthServiceFactory = ({ identityId }); if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { throw new BadRequestError({ diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index f63c8a6e5..0de2503ff 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -13,6 +13,7 @@ import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, + ForbiddenRequestError, NotFoundError, PermissionBoundaryError, RateLimitError, @@ -315,6 +316,13 @@ export const identityUaServiceFactory = ({ message: "Failed to add universal auth to already configured identity" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } + + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) { throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" }); @@ -425,6 +433,10 @@ export const identityUaServiceFactory = ({ }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } + if ( (accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) > 0 && (accessTokenTTL || uaIdentityAuth.accessTokenMaxTTL) > (accessTokenMaxTTL || uaIdentityAuth.accessTokenMaxTTL) @@ -515,6 +527,9 @@ export const identityUaServiceFactory = ({ message: "The identity does not have universal auth" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } const { permission } = await permissionService.getOrgPermission({ scope: OrganizationActionScope.Any, @@ -549,6 +564,9 @@ export const identityUaServiceFactory = ({ message: "The identity does not have universal auth" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } const { permission } = await permissionService.getOrgPermission({ scope: OrganizationActionScope.Any, actor, @@ -617,6 +635,9 @@ export const identityUaServiceFactory = ({ message: "The identity does not have universal auth" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } const { permission } = await permissionService.getOrgPermission({ scope: OrganizationActionScope.Any, @@ -700,6 +721,10 @@ export const identityUaServiceFactory = ({ message: "The identity does not have universal auth" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } + const { permission } = await permissionService.getOrgPermission({ scope: OrganizationActionScope.Any, actor, @@ -770,6 +795,9 @@ export const identityUaServiceFactory = ({ message: "The identity does not have universal auth" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } const identityUa = await identityUaDAL.findOne({ identityId }); if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); @@ -839,6 +867,9 @@ export const identityUaServiceFactory = ({ message: "The identity does not have universal auth" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } const identityUa = await identityUaDAL.findOne({ identityId }); if (!identityUa) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` }); @@ -913,6 +944,9 @@ export const identityUaServiceFactory = ({ message: "The identity does not have universal auth" }); } + if (identityMembershipOrg.identity.identityOrgId !== actorOrgId) { + throw new ForbiddenRequestError({ message: "Sub organization not authorized to access this identity" }); + } const { permission } = await permissionService.getOrgPermission({ scope: OrganizationActionScope.Any, diff --git a/backend/src/services/identity/identity-org-dal.ts b/backend/src/services/identity/identity-org-dal.ts index 65aee561c..3d1004608 100644 --- a/backend/src/services/identity/identity-org-dal.ts +++ b/backend/src/services/identity/identity-org-dal.ts @@ -163,7 +163,8 @@ export const identityOrgDALFactory = (db: TDbClient) => { .select( selectAllTableCols(TableName.Membership), db.ref("name").withSchema(TableName.Identity).as("identityName"), - db.ref("hasDeleteProtection").withSchema(TableName.Identity) + db.ref("hasDeleteProtection").withSchema(TableName.Identity), + db.ref("orgId").withSchema(TableName.Identity) ) .where(filter) .as("paginatedIdentity"); @@ -257,6 +258,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"), db.ref("scopeOrgId").withSchema("paginatedIdentity").as("orgId"), db.ref("lastLoginAuthMethod").withSchema("paginatedIdentity"), + db.ref("orgId").withSchema("paginatedIdentity").as("identityOrgId"), db.ref("lastLoginTime").withSchema("paginatedIdentity"), db.ref("createdAt").withSchema("paginatedIdentity"), db.ref("updatedAt").withSchema("paginatedIdentity"), @@ -309,6 +311,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { roleId, id, orgId, + identityOrgId, uaId, alicloudId, awsId, @@ -348,6 +351,7 @@ export const identityOrgDALFactory = (db: TDbClient) => { id: identityId as string, name: identityName, hasDeleteProtection, + orgId: identityOrgId, authMethods: buildAuthMethods({ uaId, alicloudId, diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index f35c80032..2183ec826 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -28,6 +28,7 @@ import { TSearchOrgIdentitiesByOrgIdDTO, TUpdateIdentityDTO } from "./identity-types"; +import { TAdditionalPrivilegeDALFactory } from "../additional-privilege/additional-privilege-dal"; type TIdentityServiceFactoryDep = { identityDAL: TIdentityDALFactory; @@ -40,6 +41,7 @@ type TIdentityServiceFactoryDep = { licenseService: Pick; keyStore: Pick; orgDAL: Pick; + additionalPrivilegeDAL: Pick; }; export type TIdentityServiceFactory = ReturnType; @@ -54,7 +56,8 @@ export const identityServiceFactory = ({ keyStore, orgDAL, membershipIdentityDAL, - membershipRoleDAL + membershipRoleDAL, + additionalPrivilegeDAL }: TIdentityServiceFactoryDep) => { const createIdentity = async ({ name, @@ -337,10 +340,35 @@ export const identityServiceFactory = ({ if (identityOrgMembership.identity.hasDeleteProtection) throw new BadRequestError({ message: "Identity has delete protection" }); - const deletedIdentity = await identityDAL.deleteById(id); + if (identityOrgMembership.identity.identityOrgId === actorOrgId) { + const deletedIdentity = await identityDAL.deleteById(id); + await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.scopeOrgId); + return { ...deletedIdentity, orgId: identityOrgMembership.scopeOrgId }; + } - await licenseService.updateSubscriptionOrgMemberCount(identityOrgMembership.scopeOrgId); + await membershipIdentityDAL.transaction(async (tx) => { + const identityProjectMembership = await membershipIdentityDAL.find( + { + actorIdentityId: id, + scope: AccessScope.Project, + scopeOrgId: actorOrgId + }, + { tx } + ); + await additionalPrivilegeDAL.delete( + { + actorIdentityId: id, + $in: { + projectId: identityProjectMembership.map((el) => el.scopeProjectId) + } + }, + tx + ); + const doc = await membershipIdentityDAL.delete({ actorIdentityId: id, scopeOrgId: actorOrgId }, tx); + return doc; + }); + const deletedIdentity = await identityDAL.findById(id); return { ...deletedIdentity, orgId: identityOrgMembership.scopeOrgId }; }; diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts index 99e377143..a0eb828e8 100644 --- a/frontend/src/hooks/api/identities/types.ts +++ b/frontend/src/hooks/api/identities/types.ts @@ -20,6 +20,7 @@ export type Identity = { createdAt: string; updatedAt: string; isInstanceAdmin?: boolean; + orgId: string; }; export type IdentityAccessToken = { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx index 224af9d5e..6b4d5c232 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx @@ -53,6 +53,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { const orgId = currentOrg?.id || ""; const { data: roles } = useGetOrgRoles(orgId); + const isOrgIdentity = orgId === popUp?.identity?.data?.orgId; const { mutateAsync: createMutateAsync } = useCreateIdentity(); const { mutateAsync: updateMutateAsync } = useUpdateIdentity(); @@ -113,6 +114,7 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { name: string; role: string; hasDeleteProtection: boolean; + orgId: string; }; if (identity) { @@ -196,16 +198,23 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { title={`${popUp?.identity?.data ? "Update" : "Create"} Identity`} >
- ( - - - - )} - /> + {isOrgIdentity && ( + ( + + + + )} + /> + )} { label={`${popUp?.identity?.data ? "Update" : ""} Role`} errorText={error?.message} isError={Boolean(error)} - className="mt-4" > { )} /> - ( - - -

Delete Protection {value ? "Enabled" : "Disabled"}

-
-
- )} - /> + {isOrgIdentity && ( + ( + + +

Delete Protection {value ? "Enabled" : "Disabled"}

+
+
+ )} + /> + )}
diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index 985c8d27d..0a853070b 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -33,6 +33,7 @@ const Page = () => { const orgId = currentOrg?.id || ""; const { data } = useGetIdentityById(identityId); const { mutateAsync: deleteIdentity } = useDeleteIdentity(); + const isAuthHidden = orgId !== data?.identity?.orgId; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "identity", @@ -91,10 +92,12 @@ const Page = () => {
- + {!isAuthHidden && ( + + )}
diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityDetailsSection.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityDetailsSection.tsx index 8e0c1228c..c27fcb96c 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityDetailsSection.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityDetailsSection.tsx @@ -28,13 +28,14 @@ import { UsePopUpState } from "@app/hooks/usePopUp"; type Props = { identityId: string; + isOrgIdentity?: boolean; handlePopUpOpen: ( popUpName: keyof UsePopUpState<["identity", "identityAuthMethod", "deleteIdentity"]>, data?: object ) => void; }; -export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) => { +export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdentity }: Props) => { const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset({ initialState: "Copy ID to clipboard" }); @@ -75,6 +76,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) = handlePopUpOpen("identity", { identityId, name: data.identity.name, + orgId: data.identity.orgId, hasDeleteProtection: data.identity.hasDeleteProtection, role: data.role, customRole: data.customRole, @@ -140,24 +142,30 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen }: Props) =

Name

{data.identity.name}

-
-

Last Login Auth Method

-

- {data.lastLoginAuthMethod ? identityAuthToNameMap[data.lastLoginAuthMethod] : "-"} -

-
-
-

Last Login Time

-

- {data.lastLoginTime ? format(data.lastLoginTime, "PPpp") : "-"} -

-
-
-

Delete Protection

-

- {data.identity.hasDeleteProtection ? "On" : "Off"} -

-
+ {isOrgIdentity && ( +
+

Last Login Auth Method

+

+ {data.lastLoginAuthMethod ? identityAuthToNameMap[data.lastLoginAuthMethod] : "-"} +

+
+ )} + {isOrgIdentity && ( +
+

Last Login Time

+

+ {data.lastLoginTime ? format(data.lastLoginTime, "PPpp") : "-"} +

+
+ )} + {isOrgIdentity && ( +
+

Delete Protection

+

+ {data.identity.hasDeleteProtection ? "On" : "Off"} +

+
+ )}

Organization Role

{data.role}