diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts index c9f3b826e..ecc8b676e 100644 --- a/backend/src/ee/services/permission/permission-fns.ts +++ b/backend/src/ee/services/permission/permission-fns.ts @@ -14,7 +14,7 @@ import { SecretSubjectFields } from "./project-permission"; -export function CheckForbiddenErrorSecretsSubject( +export function throwIfMissingSecretReadValueOrDescribePermission( permission: MongoAbility | PureAbility, action: Extract< ProjectPermissionSecretActions, @@ -43,7 +43,7 @@ export function CheckForbiddenErrorSecretsSubject( } } -export function CheckCanSecretsSubject( +export function hasSecretReadValueOrDescribePermission( permission: MongoAbility, action: Extract< ProjectPermissionSecretActions, @@ -83,12 +83,10 @@ export function checkForInvalidPermissionCombination(permissions: z.infer { can( [ - // not adding DescribeAndReadValue, because it's already covered by DescribeSecret and ReadValue + ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSecretActions.ReadValue, ProjectPermissionSecretActions.Create, @@ -634,7 +634,7 @@ const buildMemberPermissionRules = () => { can( [ - // not adding DescribeAndReadValue, because it's already covered by DescribeSecret and ReadValue + ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSecretActions.ReadValue, ProjectPermissionSecretActions.Edit, @@ -811,7 +811,7 @@ export const projectMemberPermissions = buildMemberPermissionRules(); const buildViewerPermissionRules = () => { const { can, rules } = new AbilityBuilder>(createMongoAbility); - // not adding DescribeAndReadValue, because it's already covered by DescribeSecret and ReadValue + can(ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSub.Secrets); can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets); can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders); diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index 9f3ec7111..72d36c26b 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -77,7 +77,7 @@ import { TSecretApprovalDetailsDTO, TStatusChangeDTO } from "./secret-approval-request-types"; -import { CheckForbiddenErrorSecretsSubject } from "../permission/permission-fns"; +import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns"; type TSecretApprovalRequestServiceFactoryDep = { permissionService: Pick; @@ -919,7 +919,7 @@ export const secretApprovalRequestServiceFactory = ({ actionProjectType: ActionProjectType.SecretManager }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath }); diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 6afc6bca3..5fbb3f598 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -23,7 +23,10 @@ import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secre import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; import { TLicenseServiceFactory } from "../license/license-service"; -import { CheckCanSecretsSubject, CheckForbiddenErrorSecretsSubject } from "../permission/permission-fns"; +import { + hasSecretReadValueOrDescribePermission, + throwIfMissingSecretReadValueOrDescribePermission +} from "../permission/permission-fns"; import { TPermissionServiceFactory } from "../permission/permission-service"; import { ProjectPermissionActions, @@ -103,7 +106,7 @@ export const secretSnapshotServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); // We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder. - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment, secretPath: path }); @@ -140,7 +143,7 @@ export const secretSnapshotServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); // We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder. - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment, secretPath: path }); @@ -187,12 +190,16 @@ export const secretSnapshotServiceFactory = ({ snapshotDetails = { ...encryptedSnapshotDetails, secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => { - const canReadValue = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment: encryptedSnapshotDetails.environment.slug, - secretPath: fullFolderPath, - secretName: el.key, - secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined - }); + const canReadValue = hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment: encryptedSnapshotDetails.environment.slug, + secretPath: fullFolderPath, + secretName: el.key, + secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined + } + ); let secretValue = ""; if (canReadValue) { @@ -236,12 +243,16 @@ export const secretSnapshotServiceFactory = ({ key: botKey }); - const canReadValue = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment: encryptedSnapshotDetails.environment.slug, - secretPath: fullFolderPath, - secretName: secretKey, - secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined - }); + const canReadValue = hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment: encryptedSnapshotDetails.environment.slug, + secretPath: fullFolderPath, + secretName: secretKey, + secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined + } + ); let secretValue = ""; diff --git a/backend/src/services/integration/integration-service.ts b/backend/src/services/integration/integration-service.ts index 3f7c55896..ad08e89d1 100644 --- a/backend/src/services/integration/integration-service.ts +++ b/backend/src/services/integration/integration-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError } from "@casl/ability"; import { ActionProjectType } from "@app/db/schemas"; -import { CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns"; +import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, @@ -96,7 +96,7 @@ export const integrationServiceFactory = ({ }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: sourceEnvironment, secretPath }); @@ -176,7 +176,7 @@ export const integrationServiceFactory = ({ const newSecretPath = secretPath || integration.secretPath; if (environment || secretPath) { - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: newEnvironment, secretPath: newSecretPath }); diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index b21cc99fe..dbef33c10 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -10,7 +10,7 @@ import { } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; -import { CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns"; +import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, @@ -765,7 +765,7 @@ export const projectServiceFactory = ({ actorOrgId, actionProjectType: ActionProjectType.Any }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret); + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); const project = await projectDAL.findProjectById(projectId); diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index 27ed12c84..b8e8b2fa0 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -4,7 +4,10 @@ import { ForbiddenError, subject } from "@casl/ability"; import { ActionProjectType, TableName } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { CheckCanSecretsSubject, CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns"; +import { + hasSecretReadValueOrDescribePermission, + throwIfMissingSecretReadValueOrDescribePermission +} from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, @@ -94,7 +97,7 @@ export const secretImportServiceFactory = ({ ); // check if user has permission to import from target path - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment: data.environment, secretPath: data.path }); @@ -404,7 +407,7 @@ export const secretImportServiceFactory = ({ if (!secretImportDoc.isReplication) throw new BadRequestError({ message: "Import is not in replication mode" }); // check if user has permission to import from target path - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment: secretImportDoc.importEnv.slug, secretPath: secretImportDoc.importPath }); @@ -595,7 +598,7 @@ export const secretImportServiceFactory = ({ // so anything based on this order will also be in right position const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false }); const allowedImports = secretImports.filter((el) => - CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: el.importEnv.slug, secretPath: el.importPath }) @@ -645,7 +648,7 @@ export const secretImportServiceFactory = ({ secretImportDAL, decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""), hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => - CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: expandEnvironment, secretPath: expandSecretPath, secretName: expandSecretKey, @@ -663,7 +666,7 @@ export const secretImportServiceFactory = ({ }); const allowedImports = secretImports.filter((el) => - CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: el.importEnv.slug, secretPath: el.importPath }) diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index d74ea120e..5c4a7e850 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError } from "@casl/ability"; import { ActionProjectType } from "@app/db/schemas"; -import { CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns"; +import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionSecretActions, @@ -179,7 +179,7 @@ export const secretSyncServiceFactory = ({ ProjectPermissionSub.SecretSyncs ); - CheckForbiddenErrorSecretsSubject(projectPermission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(projectPermission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath }); @@ -267,7 +267,7 @@ export const secretSyncServiceFactory = ({ if (!updatedEnvironment || !updatedSecretPath) throw new BadRequestError({ message: "Must specify both source environment and secret path" }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: updatedEnvironment, secretPath: updatedSecretPath }); diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 3b3a147bd..45c6493d6 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -10,7 +10,10 @@ import { TableName, TSecretsV2 } from "@app/db/schemas"; -import { CheckCanSecretsSubject, CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns"; +import { + hasSecretReadValueOrDescribePermission, + throwIfMissingSecretReadValueOrDescribePermission +} from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, @@ -197,7 +200,7 @@ export const secretV2BridgeServiceFactory = ({ const referredSecretsGroupBySecretKey = groupBy(referredSecrets, (i) => i.key); references.forEach((el) => { - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: el.environment, secretPath: el.secretPath, secretName: el.secretKey, @@ -542,14 +545,18 @@ export const secretV2BridgeServiceFactory = ({ }); } - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment, - secretPath, - secretName: inputSecret.secretName, - ...(tagsToCheck.length && { - secretTags: tagsToCheck.map((el) => el.slug) - }) - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment, + secretPath, + secretName: inputSecret.secretName, + ...(tagsToCheck.length && { + secretTags: tagsToCheck.map((el) => el.slug) + }) + } + ); return reshapeBridgeSecret( projectId, @@ -650,12 +657,16 @@ export const secretV2BridgeServiceFactory = ({ projectId }); - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment, - secretPath, - secretName: secretToDelete.key, - secretTags: secretToDelete.tags?.map((el) => el.slug) - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment, + secretPath, + secretName: secretToDelete.key, + secretTags: secretToDelete.tags?.map((el) => el.slug) + } + ); return reshapeBridgeSecret( projectId, @@ -698,7 +709,7 @@ export const secretV2BridgeServiceFactory = ({ actorOrgId, actionProjectType: ActionProjectType.SecretManager }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret); + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); } const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path); @@ -744,7 +755,7 @@ export const secretV2BridgeServiceFactory = ({ actorOrgId, actionProjectType: ActionProjectType.SecretManager }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret); + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); const folder = await folderDAL.findBySecretPath(projectId, environment, path); if (!folder) return 0; @@ -779,37 +790,27 @@ export const secretV2BridgeServiceFactory = ({ }); const decryptedSecrets = secrets - .filter((el) => { - if ( - filterByAction === ProjectPermissionSecretActions.ReadValue || - filterByAction === ProjectPermissionSecretActions.DescribeSecret - ) { - return CheckCanSecretsSubject(projectPermission, filterByAction, { - environment: groupedFolderMappings[el.folderId][0].environment, - secretPath: groupedFolderMappings[el.folderId][0].path, - secretName: el.key, - secretTags: el.tags.map((i) => i.slug) - }); - } + .filter((el) => + hasSecretReadValueOrDescribePermission(projectPermission, filterByAction, { + environment: groupedFolderMappings[el.folderId][0].environment, + secretPath: groupedFolderMappings[el.folderId][0].path, + secretName: el.key, + secretTags: el.tags.map((i) => i.slug) + }) + ) - return projectPermission.can( - filterByAction, - subject(ProjectPermissionSub.Secrets, { - environment: groupedFolderMappings[el.folderId][0].environment, - secretPath: groupedFolderMappings[el.folderId][0].path, - secretName: el.key, - secretTags: el.tags.map((i) => i.slug) - }) - ); - }) .map((secret) => { // Note(Daniel): This is only relevant if the filterAction isn't set to ReadValue. This is needed for the frontend. - const secretValueHidden = !CheckCanSecretsSubject(projectPermission, ProjectPermissionSecretActions.ReadValue, { - environment: groupedFolderMappings[secret.folderId][0].environment, - secretPath: groupedFolderMappings[secret.folderId][0].path, - secretName: secret.key, - secretTags: secret.tags.map((i) => i.slug) - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + projectPermission, + ProjectPermissionSecretActions.ReadValue, + { + environment: groupedFolderMappings[secret.folderId][0].environment, + secretPath: groupedFolderMappings[secret.folderId][0].path, + secretName: secret.key, + secretTags: secret.tags.map((i) => i.slug) + } + ); return reshapeBridgeSecret( projectId, @@ -855,7 +856,7 @@ export const secretV2BridgeServiceFactory = ({ actionProjectType: ActionProjectType.SecretManager }); if (!isInternal) { - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret); + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret); } const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environments, path); @@ -907,7 +908,7 @@ export const secretV2BridgeServiceFactory = ({ actorOrgId, actionProjectType: ActionProjectType.SecretManager }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment, secretPath: path, secretTags: params.tagSlugs @@ -950,12 +951,16 @@ export const secretV2BridgeServiceFactory = ({ const decryptedSecrets = secrets .filter((el) => { - const canDescribeSecret = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { - environment, - secretPath: groupedPaths[el.folderId][0].path, - secretName: el.key, - secretTags: el.tags.map((i) => i.slug) - }); + const canDescribeSecret = hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.DescribeSecret, + { + environment, + secretPath: groupedPaths[el.folderId][0].path, + secretName: el.key, + secretTags: el.tags.map((i) => i.slug) + } + ); if (!canDescribeSecret) { return false; @@ -964,7 +969,7 @@ export const secretV2BridgeServiceFactory = ({ if (viewSecretValue) { // Recursive secret, should be filtered out if (groupedPaths[el.folderId][0].path !== path) { - const canReadRecursiveSecretValue = CheckCanSecretsSubject( + const canReadRecursiveSecretValue = hasSecretReadValueOrDescribePermission( permission, ProjectPermissionSecretActions.ReadValue, { @@ -981,7 +986,7 @@ export const secretV2BridgeServiceFactory = ({ } if (throwOnMissingReadValuePermission) { - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath: groupedPaths[el.folderId][0].path, secretName: el.key, @@ -999,7 +1004,7 @@ export const secretV2BridgeServiceFactory = ({ const secretValueHidden = !viewSecretValue || - !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + !hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath: groupedPaths[secret.folderId][0].path, secretName: secret.key, @@ -1029,7 +1034,7 @@ export const secretV2BridgeServiceFactory = ({ secretDAL, decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined), canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => - CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: expandEnvironment, secretPath: expandSecretPath, secretName: expandSecretKey, @@ -1074,19 +1079,27 @@ export const secretV2BridgeServiceFactory = ({ expandSecretReferences, decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""), hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => { - const canDescribe = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { - environment: expandEnvironment, - secretPath: expandSecretPath, - secretName: expandSecretKey, - secretTags: expandSecretTags - }); + const canDescribe = hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.DescribeSecret, + { + environment: expandEnvironment, + secretPath: expandSecretPath, + secretName: expandSecretKey, + secretTags: expandSecretTags + } + ); - const canReadValue = CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment: expandEnvironment, - secretPath: expandSecretPath, - secretName: expandSecretKey, - secretTags: expandSecretTags - }); + const canReadValue = hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment: expandEnvironment, + secretPath: expandSecretPath, + secretName: expandSecretKey, + secretTags: expandSecretTags + } + ); return viewSecretValue ? canDescribe && canReadValue : canDescribe; } @@ -1128,7 +1141,7 @@ export const secretV2BridgeServiceFactory = ({ actionProjectType: ActionProjectType.SecretManager }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: folderWithPath.environmentSlug, secretPath: folderWithPath.path, secretName: secret.key, @@ -1240,7 +1253,7 @@ export const secretV2BridgeServiceFactory = ({ }) )); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment, secretPath: path, secretName, @@ -1255,7 +1268,7 @@ export const secretV2BridgeServiceFactory = ({ secretDAL, decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined), canExpandValue: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => { - return CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + return hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: expandEnvironment, secretPath: expandSecretPath, secretName: expandSecretKey, @@ -1280,7 +1293,7 @@ export const secretV2BridgeServiceFactory = ({ decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""), expandSecretReferences: shouldExpandSecretReferences ? expandSecretReferences : undefined, hasSecretAccess: (expandEnvironment, expandSecretPath, expandSecretKey, expandSecretTags) => { - return CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + return hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment: expandEnvironment, secretPath: expandSecretPath, secretName: expandSecretKey, @@ -1297,7 +1310,7 @@ export const secretV2BridgeServiceFactory = ({ if (viewSecretValue) { if ( - !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + !hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: importedSecret.environment, secretPath: importedSecrets[i].secretPath, secretName: importedSecret.key, @@ -1350,7 +1363,7 @@ export const secretV2BridgeServiceFactory = ({ if (viewSecretValue) { if ( - !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + !hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath: path, secretName, @@ -1523,12 +1536,16 @@ export const secretV2BridgeServiceFactory = ({ }); return newSecrets.map((el) => { - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment, - secretPath, - secretName: el.key, - secretTags: el.tags?.map((i) => i.slug) - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment, + secretPath, + secretName: el.key, + secretTags: el.tags?.map((i) => i.slug) + } + ); return reshapeBridgeSecret( projectId, @@ -1857,12 +1874,16 @@ export const secretV2BridgeServiceFactory = ({ ); return updatedSecrets.map((el) => { - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment, - secretPath: el.secretPath, - secretName: el.key, - secretTags: el.tags.map((i) => i.slug) - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment, + secretPath: el.secretPath, + secretName: el.key, + secretTags: el.tags.map((i) => i.slug) + } + ); return { ...reshapeBridgeSecret( @@ -1987,7 +2008,7 @@ export const secretV2BridgeServiceFactory = ({ const secretValueHidden = !secretToDeleteMatch || - !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + !hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath, secretName: el.key, @@ -2049,14 +2070,18 @@ export const secretV2BridgeServiceFactory = ({ sort: [["createdAt", "desc"]] }); return secretVersions.map((el) => { - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment: folder.environment.envSlug, - secretPath: folderWithPath.path, - secretName: el.key, - ...(el.tags?.length && { - secretTags: el.tags.map((tag) => tag.slug) - }) - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment: folder.environment.envSlug, + secretPath: folderWithPath.path, + secretName: el.key, + ...(el.tags?.length && { + secretTags: el.tags.map((tag) => tag.slug) + }) + } + ); return reshapeBridgeSecret( folder.projectId, @@ -2178,7 +2203,7 @@ export const secretV2BridgeServiceFactory = ({ sourceAction === ProjectPermissionSecretActions.DescribeSecret || sourceAction === ProjectPermissionSecretActions.ReadValue ) { - CheckForbiddenErrorSecretsSubject(permission, sourceAction, { + throwIfMissingSecretReadValueOrDescribePermission(permission, sourceAction, { environment: sourceEnvironment, secretPath: sourceSecretPath, secretName: secret.key, @@ -2517,7 +2542,7 @@ export const secretV2BridgeServiceFactory = ({ actionProjectType: ActionProjectType.SecretManager }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment, secretPath }); @@ -2541,7 +2566,7 @@ export const secretV2BridgeServiceFactory = ({ type: SecretType.Shared }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.DescribeSecret, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, { environment, secretPath, secretName, @@ -2558,7 +2583,7 @@ export const secretV2BridgeServiceFactory = ({ secretDAL, decryptSecretValue: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined), canExpandValue: (expandEnvironment, expandSecretPath, expandSecretName, expandSecretTags) => - CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: expandEnvironment, secretPath: expandSecretPath, secretName: expandSecretName, @@ -2567,7 +2592,7 @@ export const secretV2BridgeServiceFactory = ({ }); if ( - !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + !hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath, secretName, diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts index 510181d0c..473d1ed83 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-types.ts @@ -349,5 +349,5 @@ export type TGetSecretsRawByFolderMappingsDTO = { folderMappings: { folderId: string; path: string; environment: string }[]; userId: string; filters: TFindSecretsByFolderIdsFilter; - filterByAction?: ProjectPermissionSecretActions; + filterByAction?: ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue; }; diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index ddaa054ab..aa8cec2bc 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -11,7 +11,7 @@ import { TSecretFolders, TSecrets } from "@app/db/schemas"; -import { CheckCanSecretsSubject } from "@app/ee/services/permission/permission-fns"; +import { hasSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; @@ -191,7 +191,7 @@ export const recursivelyGetSecretPaths = ({ // Filter out paths that the user does not have permission to access, and paths that are not in the current path const allowedPaths = paths.filter( (folder) => - CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath: folder.path }) && folder.path.startsWith(currentPath === "/" ? "" : currentPath) diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index e3eb75bda..3593adf88 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -13,7 +13,10 @@ import { SecretType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { CheckCanSecretsSubject, CheckForbiddenErrorSecretsSubject } from "@app/ee/services/permission/permission-fns"; +import { + hasSecretReadValueOrDescribePermission, + throwIfMissingSecretReadValueOrDescribePermission +} from "@app/ee/services/permission/permission-fns"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, @@ -453,10 +456,14 @@ export const secretServiceFactory = ({ }); } - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment, - secretPath: path - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment, + secretPath: path + } + ); return { ...updatedSecret[0], @@ -560,10 +567,14 @@ export const secretServiceFactory = ({ }); } - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment, - secretPath: path - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment, + secretPath: path + } + ); return { ...deletedSecret[0], @@ -620,7 +631,7 @@ export const secretServiceFactory = ({ paths = deepPaths.map(({ folderId, path: p }) => ({ folderId, path: p })); } else { - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath: path }); @@ -645,7 +656,7 @@ export const secretServiceFactory = ({ // if its service token allow full access over imported one actor === ActorType.SERVICE ? true - : CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + : hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: importEnv.slug, secretPath: importPath }) @@ -699,7 +710,7 @@ export const secretServiceFactory = ({ actorOrgId, actionProjectType: ActionProjectType.SecretManager }); - CheckForbiddenErrorSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment, secretPath: path }); @@ -750,7 +761,7 @@ export const secretServiceFactory = ({ // if its service token allow full access over imported one actor === ActorType.SERVICE ? true - : CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { + : hasSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { environment: importEnv.slug, secretPath: importPath }) @@ -969,10 +980,14 @@ export const secretServiceFactory = ({ secretVersionTagDAL }); - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment, - secretPath: path - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment, + secretPath: path + } + ); return updatedSecrets.map((secret) => ({ ...secret, @@ -1063,10 +1078,14 @@ export const secretServiceFactory = ({ }); } } - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment, - secretPath: path - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment, + secretPath: path + } + ); return secrets.map((secret) => ({ ...secret, @@ -1257,7 +1276,7 @@ export const secretServiceFactory = ({ action === ProjectPermissionSecretActions.DescribeSecret || action === ProjectPermissionSecretActions.ReadValue ) { - return CheckCanSecretsSubject(entityPermission.permission, action, { + return hasSecretReadValueOrDescribePermission(entityPermission.permission, action, { environment, secretPath, secretName, @@ -2429,14 +2448,18 @@ export const secretServiceFactory = ({ key: botKey }); - const secretValueHidden = !CheckCanSecretsSubject(permission, ProjectPermissionSecretActions.ReadValue, { - environment: folder.environment.envSlug, - secretPath: folderWithPath.path, - secretName: secretKey, - ...(el.tags?.length && { - secretTags: el.tags.map((tag) => tag.slug) - }) - }); + const secretValueHidden = !hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.ReadValue, + { + environment: folder.environment.envSlug, + secretPath: folderWithPath.path, + secretName: secretKey, + ...(el.tags?.length && { + secretTags: el.tags.map((tag) => tag.slug) + }) + } + ); return decryptSecretRaw( { @@ -2839,7 +2862,7 @@ export const secretServiceFactory = ({ sourceAction === ProjectPermissionSecretActions.ReadValue || sourceAction === ProjectPermissionSecretActions.DescribeSecret ) { - CheckForbiddenErrorSecretsSubject(permission, sourceAction, { + throwIfMissingSecretReadValueOrDescribePermission(permission, sourceAction, { environment: sourceEnvironment, secretPath: sourceSecretPath }); diff --git a/frontend/src/lib/fn/permission.ts b/frontend/src/lib/fn/permission.ts index f2077d76b..c3937a0ff 100644 --- a/frontend/src/lib/fn/permission.ts +++ b/frontend/src/lib/fn/permission.ts @@ -7,7 +7,7 @@ import { SecretSubjectFields } from "@app/context/ProjectPermissionContext/types"; -export function secretsPermissionCan( +export function hasSecretReadValueOrDescribePermission( permission: MongoAbility, action: Extract< ProjectPermissionSecretActions, diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index 9f13b53a8..a47a64614 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -29,11 +29,11 @@ const GeneralPolicyActionSchema = z.object({ const SecretPolicyActionSchema = z.object({ [ProjectPermissionSecretActions.DescribeAndReadValue]: z.boolean().optional(), // existing read, gives both describe and read value - [ProjectPermissionSecretActions.DescribeSecret]: z.boolean().optional(), // describe secret, cannot read value - [ProjectPermissionSecretActions.ReadValue]: z.boolean().optional(), // read value - [ProjectPermissionSecretActions.Edit]: z.boolean().optional(), // edit secret - [ProjectPermissionSecretActions.Delete]: z.boolean().optional(), // delete secret - [ProjectPermissionSecretActions.Create]: z.boolean().optional() // create secret + [ProjectPermissionSecretActions.DescribeSecret]: z.boolean().optional(), + [ProjectPermissionSecretActions.ReadValue]: z.boolean().optional(), + [ProjectPermissionSecretActions.Edit]: z.boolean().optional(), + [ProjectPermissionSecretActions.Delete]: z.boolean().optional(), + [ProjectPermissionSecretActions.Create]: z.boolean().optional() }); const CmekPolicyActionSchema = z.object({ diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx index 536f75542..ce2e43f67 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretRenameRow.tsx @@ -15,7 +15,7 @@ import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionCo import { useToggle } from "@app/hooks"; import { useUpdateSecretV3 } from "@app/hooks/api"; import { SecretType, SecretV3RawSanitized } from "@app/hooks/api/types"; -import { secretsPermissionCan } from "@app/lib/fn/permission"; +import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission"; enum SecretActionType { Created = "created", @@ -52,7 +52,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath } secretTags: (secretDetails?.tags || []).map((i) => i.slug) }); const isSecretInEnvReadOnly = - secretsPermissionCan( + hasSecretReadValueOrDescribePermission( permission, ProjectPermissionSecretActions.DescribeSecret, secretPermissionSubject diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index 4dba1c2a8..e6fc5eadf 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -38,7 +38,7 @@ import { useGetProjectSecretsDetails } from "@app/hooks/api/dashboard"; import { DashboardSecretsOrderBy } from "@app/hooks/api/dashboard/types"; import { OrderByDirection } from "@app/hooks/api/generic/types"; import { ProjectType } from "@app/hooks/api/workspace/types"; -import { secretsPermissionCan } from "@app/lib/fn/permission"; +import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission"; import { SecretTableResourceCount } from "../OverviewPage/components/SecretTableResourceCount"; import { SecretV2MigrationSection } from "../OverviewPage/components/SecretV2MigrationSection"; @@ -105,7 +105,7 @@ const Page = () => { const projectSlug = currentWorkspace?.slug || ""; const secretPath = (routerQueryParams.secretPath as string) || "/"; - const canReadSecret = secretsPermissionCan( + const canReadSecret = hasSecretReadValueOrDescribePermission( permission, ProjectPermissionSecretActions.DescribeSecret, { @@ -116,7 +116,7 @@ const Page = () => { } ); - const canReadSecretValue = secretsPermissionCan( + const canReadSecretValue = hasSecretReadValueOrDescribePermission( permission, ProjectPermissionSecretActions.ReadValue, { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx index 3ad7159d7..410d49e5f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretDetailSidebar.tsx @@ -57,7 +57,7 @@ import { ActorType } from "@app/hooks/api/auditLogs/enums"; import { useGetSecretAccessList } from "@app/hooks/api/secrets/queries"; import { SecretV3RawSanitized, WsTag } from "@app/hooks/api/types"; import { ProjectType } from "@app/hooks/api/workspace/types"; -import { secretsPermissionCan } from "@app/lib/fn/permission"; +import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission"; import { CreateReminderForm } from "./CreateReminderForm"; import { formSchema, SecretActionType, TFormSchema } from "./SecretListView.utils"; @@ -141,7 +141,7 @@ export const SecretDetailSidebar = ({ }) ); - const cannotReadSecretValue = !secretsPermissionCan( + const cannotReadSecretValue = !hasSecretReadValueOrDescribePermission( permission, ProjectPermissionSecretActions.ReadValue, { @@ -153,12 +153,16 @@ export const SecretDetailSidebar = ({ ); const isReadOnly = - secretsPermissionCan(permission, ProjectPermissionSecretActions.DescribeSecret, { - environment, - secretPath, - secretName: secretKey, - secretTags: selectTagSlugs - }) && + hasSecretReadValueOrDescribePermission( + permission, + ProjectPermissionSecretActions.DescribeSecret, + { + environment, + secretPath, + secretName: secretKey, + secretTags: selectTagSlugs + } + ) && cannotEditSecret && cannotReadSecretValue; @@ -361,11 +365,11 @@ export const SecretDetailSidebar = ({ >