From 596378208e1d37a15c55a0440a010c8d72025d4b Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 6 Mar 2025 23:39:39 +0800 Subject: [PATCH] misc: privilege management transition for org --- .../src/ee/services/group/group-service.ts | 51 +++++++++---- .../ee/services/permission/org-permission.ts | 60 ++++++++++++---- .../ee/services/permission/permission-fns.ts | 23 +++++- .../identity-aws-auth-service.ts | 21 ++++-- .../identity-azure-auth-service.ts | 22 ++++-- .../identity-gcp-auth-service.ts | 22 ++++-- .../identity-jwt-auth-service.ts | 22 ++++-- .../identity-kubernetes-auth-service.ts | 22 ++++-- .../identity-oidc-auth-service.ts | 21 ++++-- .../identity-token-auth-service.ts | 47 ++++++++---- .../identity-ua/identity-ua-service.ts | 72 ++++++++++++++----- .../src/services/identity/identity-service.ts | 53 ++++++-------- .../src/context/OrgPermissionContext/types.ts | 26 ++++++- 13 files changed, 327 insertions(+), 135 deletions(-) diff --git a/backend/src/ee/services/group/group-service.ts b/backend/src/ee/services/group/group-service.ts index 7de3f8f92..fdb582bbf 100644 --- a/backend/src/ee/services/group/group-service.ts +++ b/backend/src/ee/services/group/group-service.ts @@ -3,7 +3,6 @@ import slugify from "@sindresorhus/slugify"; import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas"; import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; @@ -14,7 +13,8 @@ import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal import { TUserDALFactory } from "@app/services/user/user-dal"; import { TLicenseServiceFactory } from "../license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; +import { OrgPermissionGroupActions, OrgPermissionSubjects } from "../permission/org-permission"; +import { validatePrivilegeChangeOperation } from "../permission/permission-fns"; import { TPermissionServiceFactory } from "../permission/permission-service"; import { TGroupDALFactory } from "./group-dal"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "./group-fns"; @@ -74,7 +74,7 @@ export const groupServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Groups); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Create, OrgPermissionSubjects.Groups); const plan = await licenseService.getPlan(actorOrgId); if (!plan.groups) @@ -87,7 +87,14 @@ export const groupServiceFactory = ({ actorOrgId ); const isCustomRole = Boolean(customRole); - const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission); + + const hasRequiredPriviledges = validatePrivilegeChangeOperation( + OrgPermissionGroupActions.ManagePrivileges, + OrgPermissionSubjects.Groups, + permission, + rolePermission + ); + if (!hasRequiredPriviledges) throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" }); @@ -135,7 +142,7 @@ export const groupServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); const plan = await licenseService.getPlan(actorOrgId); if (!plan.groups) @@ -156,7 +163,13 @@ export const groupServiceFactory = ({ ); const isCustomRole = Boolean(customOrgRole); - const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission); + const hasRequiredNewRolePermission = validatePrivilegeChangeOperation( + OrgPermissionGroupActions.ManagePrivileges, + OrgPermissionSubjects.Groups, + permission, + rolePermission + ); + if (!hasRequiredNewRolePermission) throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" }); if (isCustomRole) customRole = customOrgRole; @@ -206,7 +219,7 @@ export const groupServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Groups); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Delete, OrgPermissionSubjects.Groups); const plan = await licenseService.getPlan(actorOrgId); @@ -233,7 +246,7 @@ export const groupServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Groups); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); const group = await groupDAL.findById(id); if (!group) { @@ -266,7 +279,7 @@ export const groupServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Groups); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); const group = await groupDAL.findOne({ orgId: actorOrgId, @@ -301,7 +314,7 @@ export const groupServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); // check if group with slug exists const group = await groupDAL.findOne({ @@ -329,7 +342,13 @@ export const groupServiceFactory = ({ const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId); // check if user has broader or equal to privileges than group - const hasRequiredPrivileges = isAtLeastAsPrivileged(permission, groupRolePermission); + const hasRequiredPrivileges = validatePrivilegeChangeOperation( + OrgPermissionGroupActions.AddMembers, + OrgPermissionSubjects.Groups, + permission, + groupRolePermission + ); + if (!hasRequiredPrivileges) throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" }); @@ -368,7 +387,7 @@ export const groupServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); // check if group with slug exists const group = await groupDAL.findOne({ @@ -396,7 +415,13 @@ export const groupServiceFactory = ({ const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId); // check if user has broader or equal to privileges than group - const hasRequiredPrivileges = isAtLeastAsPrivileged(permission, groupRolePermission); + const hasRequiredPrivileges = validatePrivilegeChangeOperation( + OrgPermissionGroupActions.RemoveMembers, + OrgPermissionSubjects.Groups, + permission, + groupRolePermission + ); + if (!hasRequiredPrivileges) throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" }); diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts index dbabcc2c1..9d0f8c380 100644 --- a/backend/src/ee/services/permission/org-permission.ts +++ b/backend/src/ee/services/permission/org-permission.ts @@ -40,6 +40,28 @@ export enum OrgPermissionGatewayActions { DeleteGateways = "delete-gateways" } +export enum OrgPermissionIdentityActions { + Read = "read", + Create = "create", + Edit = "edit", + Delete = "delete", + ManagePrivileges = "manage-privileges", + RevokeAuth = "revoke-auth", + CreateToken = "create-token", + GetToken = "get-token", + DeleteToken = "delete-token" +} + +export enum OrgPermissionGroupActions { + Read = "read", + Create = "create", + Edit = "edit", + Delete = "delete", + ManagePrivileges = "manage-privileges", + AddMembers = "add-members", + RemoveMembers = "remove-members" +} + export enum OrgPermissionSubjects { Workspace = "workspace", Role = "role", @@ -75,10 +97,10 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.Sso] | [OrgPermissionActions, OrgPermissionSubjects.Scim] | [OrgPermissionActions, OrgPermissionSubjects.Ldap] - | [OrgPermissionActions, OrgPermissionSubjects.Groups] + | [OrgPermissionGroupActions, OrgPermissionSubjects.Groups] | [OrgPermissionActions, OrgPermissionSubjects.SecretScanning] | [OrgPermissionActions, OrgPermissionSubjects.Billing] - | [OrgPermissionActions, OrgPermissionSubjects.Identity] + | [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity] | [OrgPermissionActions, OrgPermissionSubjects.Kms] | [OrgPermissionActions, OrgPermissionSubjects.AuditLogs] | [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates] @@ -244,20 +266,28 @@ const buildAdminPermission = () => { can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap); can(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap); - can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups); - can(OrgPermissionActions.Create, OrgPermissionSubjects.Groups); - can(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups); - can(OrgPermissionActions.Delete, OrgPermissionSubjects.Groups); + can(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); + can(OrgPermissionGroupActions.Create, OrgPermissionSubjects.Groups); + can(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); + can(OrgPermissionGroupActions.Delete, OrgPermissionSubjects.Groups); + can(OrgPermissionGroupActions.ManagePrivileges, OrgPermissionSubjects.Groups); + can(OrgPermissionGroupActions.AddMembers, OrgPermissionSubjects.Groups); + can(OrgPermissionGroupActions.RemoveMembers, OrgPermissionSubjects.Groups); can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing); - can(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); - can(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); - can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); - can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.ManagePrivileges, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.RevokeAuth, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.CreateToken, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.GetToken, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.DeleteToken, OrgPermissionSubjects.Identity); can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms); can(OrgPermissionActions.Create, OrgPermissionSubjects.Kms); @@ -302,7 +332,7 @@ const buildMemberPermission = () => { can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); can(OrgPermissionActions.Read, OrgPermissionSubjects.Member); - can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups); + can(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); can(OrgPermissionActions.Read, OrgPermissionSubjects.Role); can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); @@ -313,10 +343,10 @@ const buildMemberPermission = () => { can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning); can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning); - can(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); - can(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); - can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); - can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); + can(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity); can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs); diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts index 80a58db0a..b7913888a 100644 --- a/backend/src/ee/services/permission/permission-fns.ts +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -1,7 +1,13 @@ +import { MongoAbility } from "@casl/ability"; + import { TOrganizations } from "@app/db/schemas"; +import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors"; import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type"; +import { OrgPermissionSet } from "./org-permission"; +import { ProjectPermissionSet } from "./project-permission"; + function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) { if (!actorAuthMethod) return false; @@ -43,4 +49,19 @@ const escapeHandlebarsMissingMetadata = (obj: Record) => { return new Proxy(obj, handler); }; -export { escapeHandlebarsMissingMetadata, isAuthMethodSaml, validateOrgSSO }; +// This function serves as a transition layer between the old and new privilege management system +// the old privilege management system is based on the actor having more privileges than the managed permission +// the new privilege management system is based on the actor having the appropriate permission to perform the privilege change, +// regardless of the actor's privilege level. +const validatePrivilegeChangeOperation = ( + action: OrgPermissionSet[0] | ProjectPermissionSet[0], + subject: OrgPermissionSet[1] | ProjectPermissionSet[1], + actorPermission: MongoAbility, + managedPermission: MongoAbility +) => { + // first we ensure if the actor has the permission to manage the privilege + // if not, we check if the actor is indeed more privileged than the managed permission + return actorPermission.can(action, subject) || isAtLeastAsPrivileged(actorPermission, managedPermission); +}; + +export { escapeHandlebarsMissingMetadata, isAuthMethodSaml, validateOrgSSO, validatePrivilegeChangeOperation }; 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 ff202f225..ad48223c0 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 @@ -5,9 +5,9 @@ import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -171,7 +171,7 @@ export const identityAwsAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => { @@ -250,7 +250,7 @@ export const identityAwsAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => { @@ -304,7 +304,7 @@ export const identityAwsAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); return { ...awsIdentityAuth, orgId: identityMembershipOrg.orgId }; }; @@ -329,7 +329,7 @@ export const identityAwsAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -339,7 +339,14 @@ export const identityAwsAuthServiceFactory = ({ actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) throw new ForbiddenRequestError({ message: "Failed to revoke aws auth of identity with more privileged role" }); 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 01d013734..f011654a5 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 @@ -3,9 +3,9 @@ import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -143,7 +143,7 @@ export const identityAzureAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => { @@ -221,7 +221,7 @@ export const identityAzureAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => { @@ -277,7 +277,7 @@ export const identityAzureAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); return { ...identityAzureAuth, orgId: identityMembershipOrg.orgId }; }; @@ -303,7 +303,7 @@ export const identityAzureAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -312,7 +312,15 @@ export const identityAzureAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) + + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) throw new ForbiddenRequestError({ message: "Failed to revoke azure auth of identity with more privileged role" }); 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 5e404ca20..9aea92dba 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 @@ -3,9 +3,9 @@ import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -184,7 +184,7 @@ export const identityGcpAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => { @@ -264,7 +264,7 @@ export const identityGcpAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => { @@ -322,7 +322,7 @@ export const identityGcpAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); return { ...identityGcpAuth, orgId: identityMembershipOrg.orgId }; }; @@ -349,7 +349,7 @@ export const identityGcpAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -358,7 +358,15 @@ export const identityGcpAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) + + const hasRequiredPrivileges = validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ); + + if (!hasRequiredPrivileges) throw new ForbiddenRequestError({ message: "Failed to revoke gcp auth of identity with more privileged role" }); 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 6757b0b84..011a22465 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 @@ -5,9 +5,9 @@ import { JwksClient } from "jwks-rsa"; import { IdentityAuthMethod, TIdentityJwtAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -264,7 +264,7 @@ export const identityJwtAuthServiceFactory = ({ actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => { @@ -367,7 +367,7 @@ export const identityJwtAuthServiceFactory = ({ actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => { @@ -456,7 +456,7 @@ export const identityJwtAuthServiceFactory = ({ actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); const identityJwtAuth = await identityJwtAuthDAL.findOne({ identityId }); @@ -498,7 +498,7 @@ export const identityJwtAuthServiceFactory = ({ actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -508,7 +508,15 @@ export const identityJwtAuthServiceFactory = ({ actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) { + // revoke auth identity - org permission + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) { throw new ForbiddenRequestError({ message: "Failed to revoke JWT auth of identity with more privileged role" }); 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 a5677894d..1be1eda3f 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 @@ -5,9 +5,9 @@ import jwt from "jsonwebtoken"; import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -249,7 +249,7 @@ export const identityKubernetesAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => { @@ -340,7 +340,7 @@ export const identityKubernetesAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => { @@ -434,7 +434,7 @@ export const identityKubernetesAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); const { decryptor } = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, @@ -478,7 +478,7 @@ export const identityKubernetesAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -487,7 +487,15 @@ export const identityKubernetesAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) + + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) throw new ForbiddenRequestError({ message: "Failed to revoke kubernetes auth of identity with more privileged role" }); 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 ff7256a9c..bfdaba6d7 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 @@ -6,9 +6,9 @@ import { JwksClient } from "jwks-rsa"; import { IdentityAuthMethod, TIdentityOidcAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -220,7 +220,7 @@ export const identityOidcAuthServiceFactory = ({ actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => { @@ -310,7 +310,7 @@ export const identityOidcAuthServiceFactory = ({ actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => { @@ -382,7 +382,7 @@ export const identityOidcAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); @@ -418,7 +418,7 @@ export const identityOidcAuthServiceFactory = ({ actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -428,7 +428,14 @@ export const identityOidcAuthServiceFactory = ({ actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) { + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) { throw new ForbiddenRequestError({ message: "Failed to revoke OIDC auth of identity with more privileged role" }); 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 bf38c5fa1..74a47e1a0 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 @@ -3,9 +3,9 @@ import jwt from "jsonwebtoken"; import { IdentityAuthMethod, TableName } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -81,7 +81,7 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => { @@ -154,7 +154,7 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => { @@ -208,7 +208,7 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); return { ...identityTokenAuth, orgId: identityMembershipOrg.orgId }; }; @@ -235,7 +235,7 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -245,7 +245,14 @@ export const identityTokenAuthServiceFactory = ({ actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) { + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) { throw new ForbiddenRequestError({ message: "Failed to revoke Token Auth of identity with more privileged role" }); @@ -286,7 +293,7 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -295,7 +302,14 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); + + const hasPriviledge = validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ); + if (!hasPriviledge) throw new ForbiddenRequestError({ message: "Failed to create token for identity with more privileged role" @@ -363,7 +377,7 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); const tokens = await identityAccessTokenDAL.find( { @@ -406,7 +420,7 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -415,7 +429,14 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); + + const hasPriviledge = validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ); + if (!hasPriviledge) throw new ForbiddenRequestError({ message: "Failed to update token for identity with more privileged role" @@ -467,7 +488,7 @@ export const identityTokenAuthServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const [revokedToken] = await identityAccessTokenDAL.update( { diff --git a/backend/src/services/identity-ua/identity-ua-service.ts b/backend/src/services/identity-ua/identity-ua-service.ts index b9837265a..9a57d7eda 100644 --- a/backend/src/services/identity-ua/identity-ua-service.ts +++ b/backend/src/services/identity-ua/identity-ua-service.ts @@ -6,9 +6,9 @@ import jwt from "jsonwebtoken"; import { IdentityAuthMethod } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from "@app/lib/ip"; @@ -172,7 +172,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedClientSecretTrustedIps = clientSecretTrustedIps.map((clientSecretTrustedIp) => { @@ -263,7 +263,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const plan = await licenseService.getPlan(identityMembershipOrg.orgId); const reformattedClientSecretTrustedIps = clientSecretTrustedIps?.map((clientSecretTrustedIp) => { @@ -332,7 +332,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId }; }; @@ -358,7 +358,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -367,7 +367,15 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) + + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.RevokeAuth, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) throw new ForbiddenRequestError({ message: "Failed to revoke universal auth of identity with more privileged role" }); @@ -405,7 +413,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -414,8 +422,15 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission); - if (!hasPriviledge) + + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) throw new ForbiddenRequestError({ message: "Failed to add identity to project with more privileged role" }); @@ -465,7 +480,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -475,9 +490,16 @@ export const identityUaServiceFactory = ({ actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.GetToken, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) throw new ForbiddenRequestError({ - message: "Failed to add identity to project with more privileged role" + message: "Failed to get identity client secret with more privileged role" }); const identityUniversalAuth = await identityUaDAL.findOne({ @@ -515,7 +537,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -524,7 +546,15 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) + + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.GetToken, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) throw new ForbiddenRequestError({ message: "Failed to read identity client secret of project with more privileged role" }); @@ -557,7 +587,7 @@ export const identityUaServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity); const { permission: rolePermission } = await permissionService.getOrgPermission( ActorType.IDENTITY, @@ -567,10 +597,18 @@ export const identityUaServiceFactory = ({ actorOrgId ); - if (!isAtLeastAsPrivileged(permission, rolePermission)) + if ( + !validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.DeleteToken, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ) + ) { throw new ForbiddenRequestError({ message: "Failed to revoke identity client secret with more privileged role" }); + } const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, { isClientSecretRevoked: true diff --git a/backend/src/services/identity/identity-service.ts b/backend/src/services/identity/identity-service.ts index fffcbacc2..d17d6476a 100644 --- a/backend/src/services/identity/identity-service.ts +++ b/backend/src/services/identity/identity-service.ts @@ -2,13 +2,12 @@ import { ForbiddenError } from "@casl/ability"; import { OrgMembershipRole, TableName, TOrgRoles } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; +import { validatePrivilegeChangeOperation } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; -import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; -import { ActorType } from "../auth/auth-type"; import { TIdentityDALFactory } from "./identity-dal"; import { TIdentityMetadataDALFactory } from "./identity-metadata-dal"; import { TIdentityOrgDALFactory } from "./identity-org-dal"; @@ -51,14 +50,19 @@ export const identityServiceFactory = ({ metadata }: TCreateIdentityDTO) => { const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity); const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole( role, orgId ); const isCustomRole = Boolean(customRole); - const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission); + const hasRequiredPriviledges = validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.ManagePrivileges, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ); if (!hasRequiredPriviledges) throw new ForbiddenRequestError({ message: "Failed to create a more privileged identity" }); @@ -120,18 +124,7 @@ export const identityServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity); - - const { permission: identityRolePermission } = await permissionService.getOrgPermission( - ActorType.IDENTITY, - id, - identityOrgMembership.orgId, - actorAuthMethod, - actorOrgId - ); - const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); - if (!hasRequiredPriviledges) - throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" }); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity); let customRole: TOrgRoles | undefined; if (role) { @@ -141,7 +134,13 @@ export const identityServiceFactory = ({ ); const isCustomRole = Boolean(customOrgRole); - const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission); + const hasRequiredNewRolePermission = validatePrivilegeChangeOperation( + OrgPermissionIdentityActions.ManagePrivileges, + OrgPermissionSubjects.Identity, + permission, + rolePermission + ); + if (!hasRequiredNewRolePermission) throw new ForbiddenRequestError({ message: "Failed to create a more privileged identity" }); if (isCustomRole) customRole = customOrgRole; @@ -193,7 +192,7 @@ export const identityServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); return identity; }; @@ -208,17 +207,7 @@ export const identityServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity); - const { permission: identityRolePermission } = await permissionService.getOrgPermission( - ActorType.IDENTITY, - id, - identityOrgMembership.orgId, - actorAuthMethod, - actorOrgId - ); - const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission); - if (!hasRequiredPriviledges) - throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" }); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Delete, OrgPermissionSubjects.Identity); const deletedIdentity = await identityDAL.deleteById(id); @@ -240,7 +229,7 @@ export const identityServiceFactory = ({ search }: TListOrgIdentitiesByOrgIdDTO) => { const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); const identityMemberships = await identityOrgMembershipDAL.find({ [`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId, @@ -276,7 +265,7 @@ export const identityServiceFactory = ({ actorAuthMethod, actorOrgId ); - ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); + ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity); const identityMemberships = await identityProjectDAL.findByIdentityId(identityId); return identityMemberships; diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index 2a31ed6a8..854230eb0 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -54,6 +54,27 @@ export enum OrgPermissionKmipActions { Setup = "setup" } +export enum OrgPermissionIdentityActions { + Read = "read", + Create = "create", + Edit = "edit", + Delete = "delete", + ManagePrivileges = "manage-privileges", + RevokeAuth = "revoke-auth", + CreateToken = "create-token", + GetToken = "get-token" +} + +export enum OrgPermissionGroupActions { + Read = "read", + Create = "create", + Edit = "edit", + Delete = "delete", + ManagePrivileges = "manage-privileges", + AddMembers = "add-members", + RemoveMembers = "remove-members" +} + export type AppConnectionSubjectFields = { connectionId: string; }; @@ -68,17 +89,18 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.Scim] | [OrgPermissionActions, OrgPermissionSubjects.Sso] | [OrgPermissionActions, OrgPermissionSubjects.Ldap] - | [OrgPermissionActions, OrgPermissionSubjects.Groups] + | [OrgPermissionGroupActions, OrgPermissionSubjects.Groups] | [OrgPermissionActions, OrgPermissionSubjects.SecretScanning] | [OrgPermissionActions, OrgPermissionSubjects.Billing] - | [OrgPermissionActions, OrgPermissionSubjects.Identity] | [OrgPermissionActions, OrgPermissionSubjects.Kms] | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole] | [OrgPermissionActions, OrgPermissionSubjects.AuditLogs] | [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates] | [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections] + | [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity] | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip] | [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]; + // TODO(scott): add back once org UI refactored // | [ // OrgPermissionAppConnectionActions,