From e8cb3f8b4a23f3d22b0ff01cc55cc48cfea4761d Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 17 Jun 2025 16:18:41 -0700 Subject: [PATCH] improvements: fix secret sync policy parsing, add read checks/filters and disable ui based of conditions --- .../secret-sync/secret-sync-service.ts | 36 ++++++++++-- .../ProjectRoleModifySection.utils.tsx | 55 ++++++++++--------- .../SecretSyncTable/SecretSyncRow.tsx | 19 +++++-- .../components/SecretSyncActionTriggers.tsx | 21 +++++-- .../SecretSyncDestinatonSection.tsx | 16 ++++-- .../components/SecretSyncDetailsSection.tsx | 17 ++++-- .../SecretSyncOptionsSection.tsx | 18 ++++-- .../components/SecretSyncSourceSection.tsx | 14 +++-- 8 files changed, 137 insertions(+), 59 deletions(-) diff --git a/backend/src/services/secret-sync/secret-sync-service.ts b/backend/src/services/secret-sync/secret-sync-service.ts index 1bb27c05a..3fdb7fea6 100644 --- a/backend/src/services/secret-sync/secret-sync-service.ts +++ b/backend/src/services/secret-sync/secret-sync-service.ts @@ -89,7 +89,17 @@ export const secretSyncServiceFactory = ({ projectId }); - return secretSyncs as TSecretSync[]; + return secretSyncs.filter((secretSync) => + permission.can( + ProjectPermissionSecretSyncActions.Read, + secretSync.environment && secretSync.folder + ? subject(ProjectPermissionSub.SecretSyncs, { + environment: secretSync.environment.slug, + secretPath: secretSync.folder.path + }) + : ProjectPermissionSub.SecretSyncs + ) + ) as TSecretSync[]; }; const listSecretSyncsBySecretPath = async ( @@ -105,7 +115,15 @@ export const secretSyncServiceFactory = ({ projectId }); - if (permission.cannot(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs)) { + if ( + permission.cannot( + ProjectPermissionSecretSyncActions.Read, + subject(ProjectPermissionSub.SecretSyncs, { + environment, + secretPath + }) + ) + ) { return []; } @@ -142,7 +160,12 @@ export const secretSyncServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretSyncActions.Read, - ProjectPermissionSub.SecretSyncs + secretSync.environment && secretSync.folder + ? subject(ProjectPermissionSub.SecretSyncs, { + environment: secretSync.environment.slug, + secretPath: secretSync.folder.path + }) + : ProjectPermissionSub.SecretSyncs ); if (secretSync.connection.app !== SECRET_SYNC_CONNECTION_MAP[destination]) @@ -179,7 +202,12 @@ export const secretSyncServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionSecretSyncActions.Read, - ProjectPermissionSub.SecretSyncs + secretSync.environment && secretSync.folder + ? subject(ProjectPermissionSub.SecretSyncs, { + environment: secretSync.environment.slug, + secretPath: secretSync.folder.path + }) + : ProjectPermissionSub.SecretSyncs ); if (secretSync.connection.app !== SECRET_SYNC_CONNECTION_MAP[destination]) diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index 6d95c403c..471246d04 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -524,6 +524,36 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { return; } + if (subject === ProjectPermissionSub.SecretSyncs) { + const canRead = action.includes(ProjectPermissionSecretSyncActions.Read); + const canEdit = action.includes(ProjectPermissionSecretSyncActions.Edit); + const canDelete = action.includes(ProjectPermissionSecretSyncActions.Delete); + const canCreate = action.includes(ProjectPermissionSecretSyncActions.Create); + const canSyncSecrets = action.includes(ProjectPermissionSecretSyncActions.SyncSecrets); + const canImportSecrets = action.includes( + ProjectPermissionSecretSyncActions.ImportSecrets + ); + const canRemoveSecrets = action.includes( + ProjectPermissionSecretSyncActions.RemoveSecrets + ); + + if (!formVal[subject]) formVal[subject] = [{ conditions: [], inverted: false }]; + + // from above statement we are sure it won't be undefined + formVal[subject]!.push({ + [ProjectPermissionSecretSyncActions.Read]: canRead, + [ProjectPermissionSecretSyncActions.Create]: canCreate, + [ProjectPermissionSecretSyncActions.Edit]: canEdit, + [ProjectPermissionSecretSyncActions.Delete]: canDelete, + [ProjectPermissionSecretSyncActions.SyncSecrets]: canSyncSecrets, + [ProjectPermissionSecretSyncActions.ImportSecrets]: canImportSecrets, + [ProjectPermissionSecretSyncActions.RemoveSecrets]: canRemoveSecrets, + conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [], + inverted + }); + return; + } + if (subject === ProjectPermissionSub.DynamicSecrets) { const canRead = action.includes(ProjectPermissionDynamicSecretActions.ReadRootCredential); const canEdit = action.includes(ProjectPermissionDynamicSecretActions.EditRootCredential); @@ -786,31 +816,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => { return; } - if (subject === ProjectPermissionSub.SecretSyncs) { - const canRead = action.includes(ProjectPermissionSecretSyncActions.Read); - const canEdit = action.includes(ProjectPermissionSecretSyncActions.Edit); - const canDelete = action.includes(ProjectPermissionSecretSyncActions.Delete); - const canCreate = action.includes(ProjectPermissionSecretSyncActions.Create); - const canSyncSecrets = action.includes(ProjectPermissionSecretSyncActions.SyncSecrets); - const canImportSecrets = action.includes(ProjectPermissionSecretSyncActions.ImportSecrets); - const canRemoveSecrets = action.includes(ProjectPermissionSecretSyncActions.RemoveSecrets); - - if (!formVal[subject]) formVal[subject] = [{ conditions: [], inverted: false }]; - - // from above statement we are sure it won't be undefined - if (canRead) formVal[subject]![0][ProjectPermissionSecretSyncActions.Read] = true; - if (canEdit) formVal[subject]![0][ProjectPermissionSecretSyncActions.Edit] = true; - if (canCreate) formVal[subject]![0][ProjectPermissionSecretSyncActions.Create] = true; - if (canDelete) formVal[subject]![0][ProjectPermissionSecretSyncActions.Delete] = true; - if (canSyncSecrets) - formVal[subject]![0][ProjectPermissionSecretSyncActions.SyncSecrets] = true; - if (canImportSecrets) - formVal[subject]![0][ProjectPermissionSecretSyncActions.ImportSecrets] = true; - if (canRemoveSecrets) - formVal[subject]![0][ProjectPermissionSecretSyncActions.RemoveSecrets] = true; - return; - } - if (subject === ProjectPermissionSub.SecretScanningDataSources) { const canRead = action.includes(ProjectPermissionSecretScanningDataSourceActions.Read); const canEdit = action.includes(ProjectPermissionSecretScanningDataSourceActions.Edit); diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncRow.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncRow.tsx index ddc74b128..3cec5694c 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncRow.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncRow.tsx @@ -1,4 +1,5 @@ import { useCallback, useMemo } from "react"; +import { subject } from "@casl/ability"; import { faBan, faCalendarCheck, @@ -117,6 +118,14 @@ export const SecretSyncRow = ({ const destinationDetails = SECRET_SYNC_MAP[destination]; + const permissionSubject = + environment && folder + ? subject(ProjectPermissionSub.SecretSyncs, { + environment: environment.slug, + secretPath: folder.path + }) + : ProjectPermissionSub.SecretSyncs; + return ( @@ -264,7 +273,7 @@ export const SecretSyncRow = ({ {(isAllowed: boolean) => ( {(isAllowed: boolean) => ( {(isAllowed: boolean) => ( {(isAllowed: boolean) => ( {(isAllowed: boolean) => ( { const triggerSyncSecrets = useTriggerSecretSyncSyncSecrets(); const updateSync = useUpdateSecretSync(); - const { destination } = secretSync; + const { destination, environment, folder } = secretSync; const destinationName = SECRET_SYNC_MAP[destination].name; const { syncOption } = useSecretSyncOption(destination); @@ -128,6 +129,14 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => { } }; + const permissionSubject = + environment && folder + ? subject(ProjectPermissionSub.SecretSyncs, { + environment: environment.slug, + secretPath: folder.path + }) + : ProjectPermissionSub.SecretSyncs; + return ( <>
@@ -157,7 +166,7 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
{(isAllowed: boolean) => (