mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
improvements: fix secret sync policy parsing, add read checks/filters and disable ui based of conditions
This commit is contained in:
@@ -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])
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 (
|
||||
<Tr
|
||||
onClick={() =>
|
||||
@@ -264,7 +273,7 @@ export const SecretSyncRow = ({
|
||||
</DropdownMenuItem>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.SyncSecrets}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
@@ -295,7 +304,7 @@ export const SecretSyncRow = ({
|
||||
{syncOption?.canImportSecrets && (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.ImportSecrets}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
@@ -326,7 +335,7 @@ export const SecretSyncRow = ({
|
||||
)}
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.RemoveSecrets}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
@@ -356,7 +365,7 @@ export const SecretSyncRow = ({
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
@@ -373,7 +382,7 @@ export const SecretSyncRow = ({
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.Delete}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback } from "react";
|
||||
import { subject } from "@casl/ability";
|
||||
import {
|
||||
faBan,
|
||||
faCheck,
|
||||
@@ -63,7 +64,7 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
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 (
|
||||
<>
|
||||
<div className="ml-auto mt-4 flex flex-wrap items-center justify-end gap-2">
|
||||
@@ -157,7 +166,7 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
<div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.SyncSecrets}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<Button
|
||||
@@ -194,7 +203,7 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
{syncOption?.canImportSecrets && (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.ImportSecrets}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
@@ -222,7 +231,7 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
)}
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.RemoveSecrets}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
@@ -249,7 +258,7 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
@@ -267,7 +276,7 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.Delete}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
a={permissionSubject}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReactNode } from "react";
|
||||
import { subject } from "@casl/ability";
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
@@ -34,7 +35,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: Props) => {
|
||||
const { destination, connection } = secretSync;
|
||||
const { destination, connection, folder, environment } = secretSync;
|
||||
|
||||
const app = APP_CONNECTION_MAP[connection.app].name;
|
||||
|
||||
@@ -97,14 +98,19 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
|
||||
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
||||
}
|
||||
|
||||
const permissionSubject =
|
||||
environment && folder
|
||||
? subject(ProjectPermissionSub.SecretSyncs, {
|
||||
environment: environment.slug,
|
||||
secretPath: folder.path
|
||||
})
|
||||
: ProjectPermissionSub.SecretSyncs;
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="font-semibold text-mineshaft-100">Destination Configuration</h3>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
<ProjectPermissionCan I={ProjectPermissionSecretSyncActions.Edit} a={permissionSubject}>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import { subject } from "@casl/ability";
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
@@ -16,7 +17,8 @@ type Props = {
|
||||
};
|
||||
|
||||
export const SecretSyncDetailsSection = ({ secretSync, onEditDetails }: Props) => {
|
||||
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description } = secretSync;
|
||||
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description, environment, folder } =
|
||||
secretSync;
|
||||
|
||||
const failureMessage = useMemo(() => {
|
||||
if (syncStatus === SecretSyncStatus.Failed) {
|
||||
@@ -32,14 +34,19 @@ export const SecretSyncDetailsSection = ({ secretSync, onEditDetails }: Props) =
|
||||
return null;
|
||||
}, [syncStatus, lastSyncMessage]);
|
||||
|
||||
const permissionSubject =
|
||||
environment && folder
|
||||
? subject(ProjectPermissionSub.SecretSyncs, {
|
||||
environment: environment.slug,
|
||||
secretPath: folder.path
|
||||
})
|
||||
: ProjectPermissionSub.SecretSyncs;
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="font-semibold text-mineshaft-100">Details</h3>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
<ProjectPermissionCan I={ProjectPermissionSecretSyncActions.Edit} a={permissionSubject}>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReactNode } from "react";
|
||||
import { subject } from "@casl/ability";
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
@@ -21,7 +22,9 @@ type Props = {
|
||||
export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) => {
|
||||
const {
|
||||
destination,
|
||||
syncOptions: { initialSyncBehavior, disableSecretDeletion, keySchema }
|
||||
syncOptions: { initialSyncBehavior, disableSecretDeletion, keySchema },
|
||||
environment,
|
||||
folder
|
||||
} = secretSync;
|
||||
|
||||
let AdditionalSyncOptionsComponent: ReactNode;
|
||||
@@ -58,15 +61,20 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||
}
|
||||
|
||||
const permissionSubject =
|
||||
environment && folder
|
||||
? subject(ProjectPermissionSub.SecretSyncs, {
|
||||
environment: environment.slug,
|
||||
secretPath: folder.path
|
||||
})
|
||||
: ProjectPermissionSub.SecretSyncs;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="font-semibold text-mineshaft-100">Sync Options</h3>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
<ProjectPermissionCan I={ProjectPermissionSecretSyncActions.Edit} a={permissionSubject}>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { subject } from "@casl/ability";
|
||||
import { faEdit, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
@@ -16,6 +17,14 @@ type Props = {
|
||||
export const SecretSyncSourceSection = ({ secretSync, onEditSource }: Props) => {
|
||||
const { folder, environment } = secretSync;
|
||||
|
||||
const permissionSubject =
|
||||
environment && folder
|
||||
? subject(ProjectPermissionSub.SecretSyncs, {
|
||||
environment: environment.slug,
|
||||
secretPath: folder.path
|
||||
})
|
||||
: ProjectPermissionSub.SecretSyncs;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
@@ -35,10 +44,7 @@ export const SecretSyncSourceSection = ({ secretSync, onEditSource }: Props) =>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionSecretSyncActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
<ProjectPermissionCan I={ProjectPermissionSecretSyncActions.Edit} a={permissionSubject}>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
|
||||
Reference in New Issue
Block a user