Merge branch 'main' into feat/point-in-time-revamp

This commit is contained in:
carlosmonastyrski
2025-06-05 18:31:27 -03:00
757 changed files with 31611 additions and 4112 deletions
@@ -87,7 +87,7 @@ export const GroupsSection = () => {
<GroupTable handlePopUpOpen={handlePopUpOpen} />
<DeleteActionModal
isOpen={popUp.deleteGroup.isOpen}
title={`Are you sure want to remove the group ${
title={`Are you sure you want to remove the group ${
(popUp?.deleteGroup?.data as { name: string })?.name || ""
} from the project?`}
onChange={(isOpen) => handlePopUpToggle("deleteGroup", isOpen)}
@@ -432,7 +432,7 @@ export const IdentityTab = withProjectPermission(
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure want to remove ${
title={`Are you sure you want to remove ${
(popUp?.deleteIdentity?.data as { name: string })?.name || ""
} from the project?`}
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
@@ -163,6 +163,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {
text: "Failed to add user to project",
type: "error"
});
return;
}
handlePopUpToggle("addMember", false);
reset();
@@ -1,11 +1,15 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import { useMemo } from "react";
import { Controller, useForm } from "react-hook-form";
import { faEye, faTrashCan } from "@fortawesome/free-regular-svg-icons";
import {
faArrowRotateLeft,
faCaretDown,
faCheck,
faClock,
faLockOpen,
faPencil,
faPlus,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -205,7 +209,7 @@ export const SpecificPrivilegeSecretForm = ({
if (!data.secretPath) {
createNotification({
type: "error",
text: "Please select a secret path",
text: "Please select a secret path...",
title: "Error"
});
return;
@@ -264,18 +268,18 @@ export const SpecificPrivilegeSecretForm = ({
};
return (
<div className="mt-4 w-full">
<div className="w-full">
<form onSubmit={privilegeForm.handleSubmit(handleSubmit)}>
<div className={twMerge("flex items-start gap-4", !privilege && "flex-wrap")}>
<div className={twMerge("flex items-start", !privilege && "flex-col")}>
<Controller
control={privilegeForm.control}
name="environmentSlug"
render={({ field: { onChange, ...field } }) => (
<FormControl label="Environment">
<FormControl label="Environment" className="w-full">
<Select
{...field}
isDisabled={isMemberEditDisabled}
className="bg-mineshaft-600 hover:bg-mineshaft-500"
className="w-full bg-mineshaft-900 hover:bg-mineshaft-800"
onValueChange={(e) => onChange(e)}
>
{currentWorkspace?.environments?.map(({ slug, id, name }) => (
@@ -297,12 +301,13 @@ export const SpecificPrivilegeSecretForm = ({
isDisabled={!!selectablePaths.length}
content="The selected environment doesn't have any policies."
>
<div>
<div className="w-full">
<FormControl label="Secret Path">
<Select
{...field}
isDisabled={isMemberEditDisabled || !selectablePaths.length}
className="w-48"
className="w-full hover:bg-mineshaft-800"
placeholder="Select a secret path"
onValueChange={(e) => field.onChange(e)}
>
{selectablePaths.map((path) => (
@@ -328,222 +333,287 @@ export const SpecificPrivilegeSecretForm = ({
);
}}
/>
<div className="flex flex-grow justify-between">
<Controller
control={privilegeForm.control}
name="read"
render={({ field }) => (
<div className="flex flex-col items-center">
<FormLabel label="View" className="mb-4" />
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-read"
className="h-5 w-5"
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
</div>
)}
/>
<Controller
control={privilegeForm.control}
name="create"
render={({ field }) => (
<div className="flex flex-col items-center">
<FormLabel label="Create" className="mb-4" />
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-create"
className="h-5 w-5"
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
</div>
)}
/>
<Controller
control={privilegeForm.control}
name="edit"
render={({ field }) => (
<div className="flex flex-col items-center">
<FormLabel label="Modify" className="mb-4" />
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-modify"
className="h-5 w-5"
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
</div>
)}
/>
<Controller
control={privilegeForm.control}
name="delete"
render={({ field }) => (
<div className="flex flex-col items-center">
<FormLabel label="Delete" className="mb-4" />
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-delete"
className="h-5 w-5"
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
</div>
)}
/>
</div>
<div className="mt-6 flex items-center space-x-2">
<Popover>
<PopoverTrigger disabled={isMemberEditDisabled}>
<div>
<Tooltip content={getAccessLabel(true)}>
<Button
variant="outline_bg"
leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined}
rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />}
isDisabled={isMemberEditDisabled}
className={twMerge(
"border-none bg-mineshaft-600 py-2.5 text-xs capitalize hover:bg-mineshaft-500",
isTemporary && "text-primary",
isExpired && "text-red-600"
)}
<FormControl label="Permissions" className="w-full">
<div className="flex w-full flex-col justify-between">
<div className="flex w-full flex-row gap-2">
<Controller
control={privilegeForm.control}
name="read"
render={({ field }) => (
<label
className={`group my-1 flex w-full cursor-pointer flex-row items-center justify-start gap-2 rounded-md border border-mineshaft-600 bg-mineshaft-900 p-3 duration-100 hover:border-primary/30 hover:bg-primary/10 ${field.value ? "border-primary/50 bg-primary/10 hover:border-primary/50" : ""}`}
htmlFor="secret-read"
>
{getAccessLabel(false)}
</Button>
</Tooltip>
</div>
</PopoverTrigger>
<PopoverContent
arrowClassName="fill-gray-600"
side="right"
sideOffset={12}
hideCloseBtn
className="border border-gray-600 pt-4"
>
<div className="flex flex-col space-y-4">
<div className="border-b border-b-gray-700 pb-2 text-sm text-mineshaft-300">
Configure timed access
</div>
{isExpired && <Tag colorSchema="red">Expired</Tag>}
<Controller
control={privilegeForm.control}
defaultValue="1h"
name="temporaryAccess.temporaryRange"
render={({ field, fieldState: { error } }) => (
<FormControl
label={<TtlFormLabel label="Validity" />}
isError={Boolean(error?.message)}
errorText={error?.message}
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-read"
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
<div className="pointer-events-none ml-1 flex select-none flex-col text-mineshaft-300">
<div className="flex flex-row items-center gap-1">
<FontAwesomeIcon
icon={faEye}
className={`text-sm ${field.value ? "text-primary-200" : ""}`}
/>
<FormLabel
label="View"
className={`my-0 ml-0.5 text-mineshaft-300 ${field.value ? "text-primary-200" : ""}`}
/>
</div>
<p className="text-xs text-mineshaft-400">Read secret values</p>
</div>
</label>
)}
/>
<Controller
control={privilegeForm.control}
name="create"
render={({ field }) => (
<label
className={`group my-1 flex w-full cursor-pointer flex-row items-center justify-start gap-2 rounded-md border border-mineshaft-600 bg-mineshaft-900 p-3 duration-100 hover:border-primary/30 hover:bg-primary/10 ${field.value ? "border-primary/50 bg-primary/10 hover:border-primary/50" : ""}`}
htmlFor="secret-change"
>
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-change"
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
<div className="pointer-events-none ml-1 flex select-none flex-col text-mineshaft-300">
<div className="flex flex-row items-center gap-1">
<FontAwesomeIcon
icon={faPlus}
className={`text-sm ${field.value ? "text-primary-200" : ""}`}
/>
<FormLabel
label="Create"
className={`my-0 ml-0.5 text-mineshaft-300 ${field.value ? "text-primary-200" : ""}`}
/>
</div>
<p className="text-xs text-mineshaft-400">Create new secrets</p>
</div>
</label>
)}
/>
</div>
<div className="flex w-full flex-row gap-2">
<Controller
control={privilegeForm.control}
name="edit"
render={({ field }) => (
<label
className={`group my-1 flex w-full cursor-pointer flex-row items-center justify-start gap-2 rounded-md border border-mineshaft-600 bg-mineshaft-900 p-3 duration-100 hover:border-primary/30 hover:bg-primary/10 ${field.value ? "border-primary/50 bg-primary/10 hover:border-primary/50" : ""}`}
htmlFor="secret-modify"
>
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-modify"
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
<div className="pointer-events-none ml-1 flex select-none flex-col text-mineshaft-300">
<div className="flex flex-row items-center gap-1">
<FontAwesomeIcon
icon={faPencil}
className={`text-sm ${field.value ? "text-primary-200" : ""}`}
/>
<FormLabel
label="Modify"
className={`my-0 ml-0.5 text-mineshaft-300 ${field.value ? "text-primary-200" : ""}`}
/>
</div>
<p className="text-xs text-mineshaft-400">Update existing secrets</p>
</div>
</label>
)}
/>
<Controller
control={privilegeForm.control}
name="delete"
render={({ field }) => (
<label
className={`group my-1 flex w-full cursor-pointer flex-row items-center justify-start gap-2 rounded-md border border-mineshaft-600 bg-mineshaft-900 p-3 duration-100 hover:border-primary/30 hover:bg-primary/10 ${field.value ? "border-primary/50 bg-primary/10 hover:border-primary/50" : ""}`}
htmlFor="secret-delete"
>
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-delete"
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
<div className="pointer-events-none ml-1 flex select-none flex-col text-mineshaft-300">
<div className="flex flex-row items-center gap-1">
<FontAwesomeIcon
icon={faTrashCan}
className={`text-sm ${field.value ? "text-primary-200" : ""}`}
/>
<FormLabel
label="Delete"
className={`my-0 ml-0.5 text-mineshaft-300 ${field.value ? "text-primary-200" : ""}`}
/>
</div>
<p className="text-xs text-mineshaft-400">Delete existing secrets</p>
</div>
</label>
)}
/>
</div>
</div>
</FormControl>
<FormControl label="Time Period" className="w-full">
<div className="mt-1 flex w-full items-center space-x-2">
<Popover>
<PopoverTrigger disabled={isMemberEditDisabled}>
<div className="w-full">
<Tooltip content={getAccessLabel(true)}>
<Button
variant="outline_bg"
leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined}
rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-4" />}
isDisabled={isMemberEditDisabled}
className={twMerge(
"w-full border-mineshaft-600 bg-mineshaft-900 py-2.5 text-sm capitalize text-mineshaft-300 hover:border-mineshaft-600 hover:bg-mineshaft-800",
isExpired && "text-red-600"
)}
>
<Input {...field} />
</FormControl>
)}
/>
<div className="flex items-center space-x-2">
<Button
size="xs"
onClick={() => {
const temporaryRange = privilegeForm.getValues(
"temporaryAccess.temporaryRange"
);
if (!temporaryRange) {
privilegeForm.setError(
"temporaryAccess.temporaryRange",
{ type: "required", message: "Required" },
{ shouldFocus: true }
);
return;
}
privilegeForm.clearErrors("temporaryAccess.temporaryRange");
privilegeForm.setValue(
"temporaryAccess",
{
isTemporary: true,
temporaryAccessStartTime: new Date().toISOString(),
temporaryRange,
temporaryAccessEndTime: new Date(
new Date().getTime() + ms(temporaryRange)
).toISOString()
},
{ shouldDirty: true }
);
}}
>
{temporaryAccessField.isTemporary && !policies ? "Restart" : "Grant"}
</Button>
{temporaryAccessField.isTemporary && (
{getAccessLabel(false)}
</Button>
</Tooltip>
</div>
</PopoverTrigger>
<PopoverContent
arrowClassName="fill-mineshaft-600"
side="right"
sideOffset={12}
hideCloseBtn
className="border border-mineshaft-600 bg-mineshaft-800 pt-4"
>
<div className="flex flex-col space-y-4">
<div className="text-sm text-mineshaft-300">Configure timed access</div>
{isExpired && <Tag colorSchema="red">Expired</Tag>}
<Controller
control={privilegeForm.control}
defaultValue="1h"
name="temporaryAccess.temporaryRange"
render={({ field, fieldState: { error } }) => (
<FormControl
label={<TtlFormLabel label="Validity" />}
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<div className="flex items-center space-x-2">
<Button
size="xs"
variant="outline_bg"
colorSchema="danger"
onClick={() => {
privilegeForm.setValue("temporaryAccess", {
isTemporary: false
});
const temporaryRange = privilegeForm.getValues(
"temporaryAccess.temporaryRange"
);
if (!temporaryRange) {
privilegeForm.setError(
"temporaryAccess.temporaryRange",
{ type: "required", message: "Required" },
{ shouldFocus: true }
);
return;
}
privilegeForm.clearErrors("temporaryAccess.temporaryRange");
privilegeForm.setValue(
"temporaryAccess",
{
isTemporary: true,
temporaryAccessStartTime: new Date().toISOString(),
temporaryRange,
temporaryAccessEndTime: new Date(
new Date().getTime() + ms(temporaryRange)
).toISOString()
},
{ shouldDirty: true }
);
}}
>
Cancel
{temporaryAccessField.isTemporary && !policies ? "Restart" : "Grant"}
</Button>
)}
{temporaryAccessField.isTemporary && (
<Button
size="xs"
variant="outline_bg"
colorSchema="danger"
onClick={() => {
privilegeForm.setValue("temporaryAccess", {
isTemporary: false
});
}}
>
Cancel
</Button>
)}
</div>
</div>
</div>
</PopoverContent>
</Popover>
{/* eslint-disable-next-line no-nested-ternary */}
{privilegeForm.formState.isDirty && privilege ? (
<>
<Tooltip content="Cancel" className="mr-4">
<IconButton
variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 py-2.5 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-privilege"
isDisabled={privilegeForm.formState.isSubmitting}
onClick={() => privilegeForm.reset()}
</PopoverContent>
</Popover>
{/* eslint-disable-next-line no-nested-ternary */}
{privilegeForm.formState.isDirty && privilege ? (
<>
<Tooltip content="Cancel" className="mr-4">
<IconButton
variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 py-2.5 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-privilege"
isDisabled={privilegeForm.formState.isSubmitting}
onClick={() => privilegeForm.reset()}
>
<FontAwesomeIcon icon={faArrowRotateLeft} className="py-0.5" />
</IconButton>
</Tooltip>
<Tooltip
content={isMemberEditDisabled ? "Access restricted" : "Save"}
className="mr-4"
>
<FontAwesomeIcon icon={faArrowRotateLeft} className="py-0.5" />
</IconButton>
</Tooltip>
<IconButton
isDisabled={isMemberEditDisabled}
className="border-none py-3"
ariaLabel="save-privilege"
type="submit"
>
{privilegeForm.formState.isSubmitting ? (
<Spinner size="xs" className="m-0 h-3 w-3 text-slate-500" />
) : (
<FontAwesomeIcon icon={faCheck} className="px-0.5" />
)}
</IconButton>
</Tooltip>
</>
) : // eslint-disable-next-line no-nested-ternary
privilege ? (
<Tooltip
content={isMemberEditDisabled ? "Access restricted" : "Save"}
content={isMemberEditDisabled ? "Access restricted" : "Delete"}
className="mr-4"
>
<IconButton
isDisabled={isMemberEditDisabled}
className="border-none py-3"
ariaLabel="save-privilege"
type="submit"
variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 py-3 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-privilege"
onClick={() => handlePopUpOpen("deletePrivilege")}
>
{privilegeForm.formState.isSubmitting ? (
<Spinner size="xs" className="m-0 h-3 w-3 text-slate-500" />
) : (
<FontAwesomeIcon icon={faCheck} className="px-0.5" />
)}
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Tooltip>
</>
) : // eslint-disable-next-line no-nested-ternary
privilege ? (
<Tooltip
content={isMemberEditDisabled ? "Access restricted" : "Delete"}
className="mr-4"
>
<IconButton
isDisabled={isMemberEditDisabled}
variant="outline_bg"
className="border border-mineshaft-500 bg-mineshaft-600 py-3 hover:border-red/70 hover:bg-red/20"
ariaLabel="delete-privilege"
onClick={() => handlePopUpOpen("deletePrivilege")}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Tooltip>
) : (
<div />
)}
</div>
) : (
<div />
)}
</div>
</FormControl>
</div>
<div className="mb-4 flex w-full">
<Controller
@@ -552,7 +622,13 @@ export const SpecificPrivilegeSecretForm = ({
render={({ field }) => (
<div className="w-full">
<FormLabel label="Note" className="mb-2" />
<Input {...field} isDisabled={isMemberEditDisabled} maxLength={255} />
<Input
{...field}
isDisabled={isMemberEditDisabled}
maxLength={255}
placeholder="Add the reason for this access request..."
className="text-mineshaft-300"
/>
</div>
)}
/>
@@ -195,7 +195,7 @@ export const ProjectRoleList = () => {
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteRole.isOpen}
title={`Are you sure want to delete ${
title={`Are you sure you want to delete ${
(popUp?.deleteRole?.data as TProjectRole)?.name || " "
} role?`}
deleteKey={(popUp?.deleteRole?.data as TProjectRole)?.slug || ""}
@@ -0,0 +1,38 @@
import { createFileRoute, linkOptions, stripSearchParams } from "@tanstack/react-router";
import { zodValidator } from "@tanstack/zod-adapter";
import { z } from "zod";
import { ProjectAccessControlTabs } from "@app/types/project";
import { AccessControlPage } from "./AccessControlPage";
const AccessControlPageQuerySchema = z.object({
selectedTab: z.nativeEnum(ProjectAccessControlTabs).catch(ProjectAccessControlTabs.Member),
requesterEmail: z.string().catch("")
});
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/access-management"
)({
component: AccessControlPage,
validateSearch: zodValidator(AccessControlPageQuerySchema),
search: {
middlewares: [stripSearchParams({ requesterEmail: "" })]
},
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/secret-scanning/$projectId/access-management",
params: {
projectId: params.projectId
}
})
}
]
};
}
});
@@ -184,7 +184,7 @@ const Page = () => {
/>
<DeleteActionModal
isOpen={popUp.deleteIdentity.isOpen}
title={`Are you sure want to remove ${identityMembershipDetails?.identity?.name} from the project?`}
title={`Are you sure you want to remove ${identityMembershipDetails?.identity?.name} from the project?`}
onChange={(isOpen) => handlePopUpToggle("deleteIdentity", isOpen)}
deleteKey="remove"
onDeleteApproved={() => onRemoveIdentitySubmit()}
@@ -0,0 +1,28 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { IdentityDetailsByIDPage } from "./IdentityDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/identities/$identityId"
)({
component: IdentityDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/secret-scanning/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Identities"
}
]
};
}
});
@@ -28,6 +28,7 @@ import {
useGetWorkspaceUserDetails
} from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { MemberProjectAdditionalPrivilegeSection } from "./components/MemberProjectAdditionalPrivilegeSection";
import { MemberRoleDetailsSection } from "./components/MemberRoleDetailsSection";
@@ -70,7 +71,21 @@ export const Page = () => {
type: "success",
text: "User privilege assumption has started"
});
window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/overview`;
let page: string;
switch (currentWorkspace.type) {
case ProjectType.SecretScanning:
page = "data-sources";
break;
case ProjectType.CertificateManager:
page = "subscribers";
break;
default:
page = "overview";
}
window.location.href = `/${currentWorkspace.type}/${currentWorkspace.id}/${page}`;
}
}
);
@@ -0,0 +1,28 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { MemberDetailsByIDPage } from "./MemberDetailsByIDPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/members/$membershipId"
)({
component: MemberDetailsByIDPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/secret-scanning/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Users"
}
]
};
}
});
@@ -170,7 +170,7 @@ const Page = () => {
<RoleModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<DeleteActionModal
isOpen={popUp.deleteRole.isOpen}
title={`Are you sure want to delete the project role ${data?.name ?? ""}?`}
title={`Are you sure you want to delete the project role ${data?.name ?? ""}?`}
onChange={(isOpen) => handlePopUpToggle("deleteRole", isOpen)}
deleteKey="confirm"
onDeleteApproved={() => onDeleteRoleSubmit()}
@@ -0,0 +1,173 @@
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { faInfoCircle, faPlus, faTrash, faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
FormControl,
IconButton,
Input,
Select,
SelectItem,
Tooltip
} from "@app/components/v2";
import {
PermissionConditionOperators,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { getConditionOperatorHelperInfo } from "./PermissionConditionHelpers";
import { TFormSchema } from "./ProjectRoleModifySection.utils";
type Props = {
position?: number;
isDisabled?: boolean;
};
export const PkiTemplatePermissionConditions = ({ position = 0, isDisabled }: Props) => {
const {
control,
watch,
formState: { errors }
} = useFormContext<TFormSchema>();
const permissionSubject = ProjectPermissionSub.CertificateTemplates;
const items = useFieldArray({
control,
name: `permissions.${permissionSubject}.${position}.conditions`
});
return (
<div className="mt-6 border-t border-t-mineshaft-600 bg-mineshaft-800 pt-2">
<p className="mt-2 text-gray-300">Conditions</p>
<p className="text-sm text-mineshaft-400">
Conditions determine when a policy will be applied (always if no conditions are present).
</p>
<p className="mb-3 text-sm leading-4 text-mineshaft-400">
All conditions must evaluate to true for the policy to take effect.
</p>
<div className="mt-2 flex flex-col space-y-2">
{items.fields.map((el, index) => {
const condition =
(watch(`permissions.${permissionSubject}.${position}.conditions.${index}`) as {
lhs: string;
rhs: string;
operator: string;
}) || {};
return (
<div
key={el.id}
className="flex gap-2 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md"
>
<div className="w-1/4">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.lhs`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => field.onChange(e)}
className="w-full"
>
<SelectItem value="name">Name</SelectItem>
</Select>
</FormControl>
)}
/>
</div>
<div className="flex w-36 items-center space-x-2">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.operator`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0 flex-grow"
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => field.onChange(e)}
className="w-full"
>
<SelectItem value={PermissionConditionOperators.$EQ}>Equals</SelectItem>
<SelectItem value={PermissionConditionOperators.$GLOB}>Glob</SelectItem>
<SelectItem value={PermissionConditionOperators.$IN}>In</SelectItem>
</Select>
</FormControl>
)}
/>
<Tooltip
asChild
content={getConditionOperatorHelperInfo(
condition?.operator as PermissionConditionOperators
)}
className="max-w-xs"
>
<FontAwesomeIcon icon={faInfoCircle} size="xs" className="text-gray-400" />
</Tooltip>
</div>
<div className="flex-grow">
<Controller
control={control}
name={`permissions.${permissionSubject}.${position}.conditions.${index}.rhs`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0 flex-grow"
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div>
<IconButton
ariaLabel="delete"
variant="outline_bg"
className="p-2.5"
onClick={() => items.remove(index)}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
</div>
);
})}
</div>
{errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message && (
<div className="flex items-center space-x-2 py-2 text-sm text-gray-400">
<FontAwesomeIcon icon={faWarning} className="text-red" />
<span>{errors?.permissions?.[permissionSubject]?.[position]?.conditions?.message}</span>
</div>
)}
<div>
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
variant="star"
size="xs"
className="mt-3"
isDisabled={isDisabled}
onClick={() =>
items.append({
lhs: "name",
operator: PermissionConditionOperators.$EQ,
rhs: ""
})
}
>
Add Condition
</Button>
</div>
</div>
);
};
@@ -12,7 +12,6 @@ import {
} from "@app/context";
import {
PermissionConditionOperators,
ProjectPermissionApprovalActions,
ProjectPermissionCommitsActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionGroupActions,
@@ -20,8 +19,12 @@ import {
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionPkiTemplateActions,
ProjectPermissionSecretActions,
ProjectPermissionSecretRotationActions,
ProjectPermissionSecretScanningConfigActions,
ProjectPermissionSecretScanningDataSourceActions,
ProjectPermissionSecretScanningFindingActions,
ProjectPermissionSecretSyncActions,
ProjectPermissionSshHostActions,
TPermissionCondition,
@@ -55,12 +58,10 @@ const SecretPolicyActionSchema = z.object({
});
const ApprovalPolicyActionSchema = z.object({
[ProjectPermissionApprovalActions.Read]: z.boolean().optional(),
[ProjectPermissionApprovalActions.Edit]: z.boolean().optional(),
[ProjectPermissionApprovalActions.Delete]: z.boolean().optional(),
[ProjectPermissionApprovalActions.Create]: z.boolean().optional(),
[ProjectPermissionApprovalActions.AllowChangeBypass]: z.boolean().optional(),
[ProjectPermissionApprovalActions.AllowAccessBypass]: z.boolean().optional()
[ProjectPermissionActions.Read]: z.boolean().optional(),
[ProjectPermissionActions.Edit]: z.boolean().optional(),
[ProjectPermissionActions.Delete]: z.boolean().optional(),
[ProjectPermissionActions.Create]: z.boolean().optional()
});
const CmekPolicyActionSchema = z.object({
@@ -106,6 +107,26 @@ const SecretRotationPolicyActionSchema = z.object({
[ProjectPermissionSecretRotationActions.RotateSecrets]: z.boolean().optional()
});
const SecretScanningDataSourcePolicyActionSchema = z.object({
[ProjectPermissionSecretScanningDataSourceActions.Read]: z.boolean().optional(),
[ProjectPermissionSecretScanningDataSourceActions.Create]: z.boolean().optional(),
[ProjectPermissionSecretScanningDataSourceActions.Edit]: z.boolean().optional(),
[ProjectPermissionSecretScanningDataSourceActions.Delete]: z.boolean().optional(),
[ProjectPermissionSecretScanningDataSourceActions.ReadScans]: z.boolean().optional(),
[ProjectPermissionSecretScanningDataSourceActions.ReadResources]: z.boolean().optional(),
[ProjectPermissionSecretScanningDataSourceActions.TriggerScans]: z.boolean().optional()
});
const SecretScanningFindingPolicyActionSchema = z.object({
[ProjectPermissionSecretScanningFindingActions.Read]: z.boolean().optional(),
[ProjectPermissionSecretScanningFindingActions.Update]: z.boolean().optional()
});
const SecretScanningConfigPolicyActionSchema = z.object({
[ProjectPermissionSecretScanningConfigActions.Read]: z.boolean().optional(),
[ProjectPermissionSecretScanningConfigActions.Update]: z.boolean().optional()
});
const KmipPolicyActionSchema = z.object({
[ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(),
[ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(),
@@ -157,6 +178,15 @@ const PkiSubscriberPolicyActionSchema = z.object({
[ProjectPermissionPkiSubscriberActions.ListCerts]: z.boolean().optional()
});
const PkiTemplatePolicyActionSchema = z.object({
[ProjectPermissionPkiTemplateActions.Read]: z.boolean().optional(),
[ProjectPermissionPkiTemplateActions.Create]: z.boolean().optional(),
[ProjectPermissionPkiTemplateActions.Edit]: z.boolean().optional(),
[ProjectPermissionPkiTemplateActions.Delete]: z.boolean().optional(),
[ProjectPermissionPkiTemplateActions.IssueCert]: z.boolean().optional(),
[ProjectPermissionPkiTemplateActions.ListCerts]: z.boolean().optional()
});
const SecretRollbackPolicyActionSchema = z.object({
read: z.boolean().optional(),
create: z.boolean().optional()
@@ -202,6 +232,23 @@ const ConditionSchema = z
: el.rhs.trim().startsWith("/")
),
{ message: "Invalid Secret Path. Must start with '/'" }
)
.refine(
(val) =>
val
.filter((el) => el.operator === PermissionConditionOperators.$EQ)
.every((el) => !el.rhs.includes(",")),
{ message: '"Equal" checks cannot contain comma separated values. Use "IN" operator instead.' }
)
.refine(
(val) =>
val
.filter((el) => el.operator === PermissionConditionOperators.$NEQ)
.every((el) => !el.rhs.includes(",")),
{
message:
'"Not Equal" checks cannot contain comma separated values. Use "IN" operator with "Forbid" instead.'
}
);
export const projectRoleFormSchema = z.object({
@@ -265,7 +312,12 @@ export const projectRoleFormSchema = z.object({
.default([]),
[ProjectPermissionSub.PkiAlerts]: GeneralPolicyActionSchema.array().default([]),
[ProjectPermissionSub.PkiCollections]: GeneralPolicyActionSchema.array().default([]),
[ProjectPermissionSub.CertificateTemplates]: GeneralPolicyActionSchema.array().default([]),
[ProjectPermissionSub.CertificateTemplates]: PkiTemplatePolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
.array()
.default([]),
[ProjectPermissionSub.SshCertificateAuthorities]: GeneralPolicyActionSchema.array().default(
[]
),
@@ -291,7 +343,13 @@ export const projectRoleFormSchema = z.object({
[ProjectPermissionSub.Kms]: GeneralPolicyActionSchema.array().default([]),
[ProjectPermissionSub.Cmek]: CmekPolicyActionSchema.array().default([]),
[ProjectPermissionSub.SecretSyncs]: SecretSyncPolicyActionSchema.array().default([]),
[ProjectPermissionSub.Kmip]: KmipPolicyActionSchema.array().default([])
[ProjectPermissionSub.Kmip]: KmipPolicyActionSchema.array().default([]),
[ProjectPermissionSub.SecretScanningDataSources]:
SecretScanningDataSourcePolicyActionSchema.array().default([]),
[ProjectPermissionSub.SecretScanningFindings]:
SecretScanningFindingPolicyActionSchema.array().default([]),
[ProjectPermissionSub.SecretScanningConfigs]:
SecretScanningConfigPolicyActionSchema.array().default([])
})
.partial()
.optional()
@@ -305,6 +363,7 @@ type TConditionalFields =
| ProjectPermissionSub.SecretImports
| ProjectPermissionSub.DynamicSecrets
| ProjectPermissionSub.PkiSubscribers
| ProjectPermissionSub.CertificateTemplates
| ProjectPermissionSub.SshHosts
| ProjectPermissionSub.SecretRotation
| ProjectPermissionSub.Identity;
@@ -319,7 +378,8 @@ export const isConditionalSubjects = (
subject === ProjectPermissionSub.Identity ||
subject === ProjectPermissionSub.SshHosts ||
subject === ProjectPermissionSub.SecretRotation ||
subject === ProjectPermissionSub.PkiSubscribers;
subject === ProjectPermissionSub.PkiSubscribers ||
subject === ProjectPermissionSub.CertificateTemplates;
const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => {
const formConditions: z.infer<typeof ConditionSchema> = [];
@@ -418,7 +478,6 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
ProjectPermissionSub.CertificateAuthorities,
ProjectPermissionSub.PkiAlerts,
ProjectPermissionSub.PkiCollections,
ProjectPermissionSub.CertificateTemplates,
ProjectPermissionSub.Tags,
ProjectPermissionSub.SecretRotation,
ProjectPermissionSub.Kms,
@@ -581,24 +640,18 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
}
if (subject === ProjectPermissionSub.SecretApproval) {
const canCreate = action.includes(ProjectPermissionApprovalActions.Create);
const canDelete = action.includes(ProjectPermissionApprovalActions.Delete);
const canEdit = action.includes(ProjectPermissionApprovalActions.Edit);
const canRead = action.includes(ProjectPermissionApprovalActions.Read);
const canChangeBypass = action.includes(ProjectPermissionApprovalActions.AllowChangeBypass);
const canAccessBypass = action.includes(ProjectPermissionApprovalActions.AllowAccessBypass);
const canCreate = action.includes(ProjectPermissionActions.Create);
const canDelete = action.includes(ProjectPermissionActions.Delete);
const canEdit = action.includes(ProjectPermissionActions.Edit);
const canRead = action.includes(ProjectPermissionActions.Read);
if (!formVal[subject]) formVal[subject] = [{}];
// Map actions to the keys defined in ApprovalPolicyActionSchema
if (canCreate) formVal[subject]![0][ProjectPermissionApprovalActions.Create] = true;
if (canDelete) formVal[subject]![0][ProjectPermissionApprovalActions.Delete] = true;
if (canEdit) formVal[subject]![0][ProjectPermissionApprovalActions.Edit] = true;
if (canRead) formVal[subject]![0][ProjectPermissionApprovalActions.Read] = true;
if (canChangeBypass)
formVal[subject]![0][ProjectPermissionApprovalActions.AllowChangeBypass] = true;
if (canAccessBypass)
formVal[subject]![0][ProjectPermissionApprovalActions.AllowAccessBypass] = true;
if (canCreate) formVal[subject]![0][ProjectPermissionActions.Create] = true;
if (canDelete) formVal[subject]![0][ProjectPermissionActions.Delete] = true;
if (canEdit) formVal[subject]![0][ProjectPermissionActions.Edit] = true;
if (canRead) formVal[subject]![0][ProjectPermissionActions.Read] = true;
return;
}
@@ -746,6 +799,71 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
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);
const canDelete = action.includes(ProjectPermissionSecretScanningDataSourceActions.Delete);
const canCreate = action.includes(ProjectPermissionSecretScanningDataSourceActions.Create);
const canReadScans = action.includes(
ProjectPermissionSecretScanningDataSourceActions.ReadScans
);
const canReadResources = action.includes(
ProjectPermissionSecretScanningDataSourceActions.ReadResources
);
const canTriggerScans = action.includes(
ProjectPermissionSecretScanningDataSourceActions.TriggerScans
);
if (!formVal[subject]) formVal[subject] = [{}];
// from above statement we are sure it won't be undefined
if (canRead)
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Read] = true;
if (canEdit)
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Edit] = true;
if (canCreate)
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Create] = true;
if (canDelete)
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.Delete] = true;
if (canReadScans)
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.ReadScans] = true;
if (canReadResources)
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.ReadResources] = true;
if (canTriggerScans)
formVal[subject]![0][ProjectPermissionSecretScanningDataSourceActions.TriggerScans] = true;
return;
}
if (subject === ProjectPermissionSub.SecretScanningFindings) {
const canRead = action.includes(ProjectPermissionSecretScanningFindingActions.Read);
const canUpdate = action.includes(ProjectPermissionSecretScanningFindingActions.Update);
if (!formVal[subject]) formVal[subject] = [{}];
// from above statement we are sure it won't be undefined
if (canRead) formVal[subject]![0][ProjectPermissionSecretScanningFindingActions.Read] = true;
if (canUpdate)
formVal[subject]![0][ProjectPermissionSecretScanningFindingActions.Update] = true;
return;
}
if (subject === ProjectPermissionSub.SecretScanningConfigs) {
const canRead = action.includes(ProjectPermissionSecretScanningConfigActions.Read);
const canUpdate = action.includes(ProjectPermissionSecretScanningConfigActions.Update);
if (!formVal[subject]) formVal[subject] = [{}];
// from above statement we are sure it won't be undefined
if (canRead) formVal[subject]![0][ProjectPermissionSecretScanningConfigActions.Read] = true;
if (canUpdate)
formVal[subject]![0][ProjectPermissionSecretScanningConfigActions.Update] = true;
return;
}
if (subject === ProjectPermissionSub.SshHosts) {
@@ -770,6 +888,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
return;
}
if (subject === ProjectPermissionSub.Commits) {
@@ -808,6 +928,34 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
return;
}
if (subject === ProjectPermissionSub.CertificateTemplates) {
if (!formVal[subject]) formVal[subject] = [];
formVal[subject]!.push({
[ProjectPermissionPkiTemplateActions.Edit]: action.includes(
ProjectPermissionPkiTemplateActions.Edit
),
[ProjectPermissionPkiTemplateActions.Delete]: action.includes(
ProjectPermissionPkiTemplateActions.Delete
),
[ProjectPermissionPkiTemplateActions.Create]: action.includes(
ProjectPermissionPkiTemplateActions.Create
),
[ProjectPermissionPkiTemplateActions.Read]: action.includes(
ProjectPermissionPkiTemplateActions.Read
),
[ProjectPermissionPkiTemplateActions.IssueCert]: action.includes(
ProjectPermissionPkiTemplateActions.IssueCert
),
[ProjectPermissionPkiTemplateActions.ListCerts]: action.includes(
ProjectPermissionPkiTemplateActions.ListCerts
),
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
}
});
@@ -1155,10 +1303,12 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
[ProjectPermissionSub.CertificateTemplates]: {
title: "Certificate Templates",
actions: [
{ label: "Read", value: "read" },
{ label: "Create", value: "create" },
{ label: "Modify", value: "edit" },
{ label: "Remove", value: "delete" }
{ label: "Read", value: ProjectPermissionPkiTemplateActions.Read },
{ label: "Create", value: ProjectPermissionPkiTemplateActions.Create },
{ label: "Modify", value: ProjectPermissionPkiTemplateActions.Edit },
{ label: "Remove", value: ProjectPermissionPkiTemplateActions.Delete },
{ label: "Issue Certificates", value: ProjectPermissionPkiTemplateActions.IssueCert },
{ label: "List Certificates", value: ProjectPermissionPkiTemplateActions.ListCerts }
]
},
[ProjectPermissionSub.SshCertificateAuthorities]: {
@@ -1239,12 +1389,10 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
[ProjectPermissionSub.SecretApproval]: {
title: "Secret Approval Policies",
actions: [
{ label: "Read", value: ProjectPermissionApprovalActions.Read },
{ label: "Create", value: ProjectPermissionApprovalActions.Create },
{ label: "Modify", value: ProjectPermissionApprovalActions.Edit },
{ label: "Remove", value: ProjectPermissionApprovalActions.Delete },
{ label: "Allow Change Bypass", value: ProjectPermissionApprovalActions.AllowChangeBypass },
{ label: "Allow Access Bypass", value: ProjectPermissionApprovalActions.AllowAccessBypass }
{ label: "Read", value: ProjectPermissionActions.Read },
{ label: "Create", value: ProjectPermissionActions.Create },
{ label: "Modify", value: ProjectPermissionActions.Edit },
{ label: "Remove", value: ProjectPermissionActions.Delete }
]
},
[ProjectPermissionSub.SecretRotation]: {
@@ -1310,6 +1458,65 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
value: ProjectPermissionKmipActions.GenerateClientCertificates
}
]
},
[ProjectPermissionSub.SecretScanningDataSources]: {
title: "Secret Scanning Data Sources",
actions: [
{
label: "Read Data Sources",
value: ProjectPermissionSecretScanningDataSourceActions.Read
},
{
label: "Create Data Sources",
value: ProjectPermissionSecretScanningDataSourceActions.Create
},
{
label: "Modify Data Sources",
value: ProjectPermissionSecretScanningDataSourceActions.Edit
},
{
label: "Delete Data Sources",
value: ProjectPermissionSecretScanningDataSourceActions.Delete
},
{
label: "Read Resources",
value: ProjectPermissionSecretScanningDataSourceActions.ReadResources
},
{
label: "Read Scans",
value: ProjectPermissionSecretScanningDataSourceActions.ReadScans
},
{
label: "Trigger Scans",
value: ProjectPermissionSecretScanningDataSourceActions.TriggerScans
}
]
},
[ProjectPermissionSub.SecretScanningFindings]: {
title: "Secret Scanning Findings",
actions: [
{
label: "Read Findings",
value: ProjectPermissionSecretScanningFindingActions.Read
},
{
label: "Update Findings",
value: ProjectPermissionSecretScanningFindingActions.Update
}
]
},
[ProjectPermissionSub.SecretScanningConfigs]: {
title: "Secret Scanning Config",
actions: [
{
label: "Read Config",
value: ProjectPermissionSecretScanningConfigActions.Read
},
{
label: "Update Config",
value: ProjectPermissionSecretScanningConfigActions.Update
}
]
}
};
@@ -1364,6 +1571,12 @@ const SshPermissionSubjects = (enabled = false) => ({
[ProjectPermissionSub.SshHostGroups]: enabled
});
const SecretScanningSubject = (enabled = false) => ({
[ProjectPermissionSub.SecretScanningDataSources]: enabled,
[ProjectPermissionSub.SecretScanningFindings]: enabled,
[ProjectPermissionSub.SecretScanningConfigs]: enabled
});
// scott: this structure ensures we don't forget to add project permissions to their relevant project type
export const ProjectTypePermissionSubjects: Record<
ProjectType,
@@ -1374,27 +1587,39 @@ export const ProjectTypePermissionSubjects: Record<
...SecretsManagerPermissionSubjects(true),
...KmsPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...SshPermissionSubjects()
...SshPermissionSubjects(),
...SecretScanningSubject()
},
[ProjectType.KMS]: {
...SharedPermissionSubjects,
...KmsPermissionSubjects(true),
...SecretsManagerPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...SshPermissionSubjects()
...SshPermissionSubjects(),
...SecretScanningSubject()
},
[ProjectType.CertificateManager]: {
...SharedPermissionSubjects,
...CertificateManagerPermissionSubjects(true),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects(),
...SshPermissionSubjects()
...SshPermissionSubjects(),
...SecretScanningSubject()
},
[ProjectType.SSH]: {
...SharedPermissionSubjects,
...SshPermissionSubjects(true),
...CertificateManagerPermissionSubjects(),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects(),
...SecretScanningSubject()
},
[ProjectType.SecretScanning]: {
...SharedPermissionSubjects,
...SecretScanningSubject(true),
...SshPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects()
}
};
@@ -1607,6 +1832,56 @@ export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
},
projectManagerTemplate()
],
[ProjectType.SecretScanning]: [
{
id: "scanning-viewer",
name: "Secret Scanning Viewing Policies",
description: "Grants read access to data sources and findings",
permissions: [
{
subject: ProjectPermissionSub.SecretScanningDataSources,
actions: [
ProjectPermissionSecretScanningDataSourceActions.Read,
ProjectPermissionSecretScanningDataSourceActions.ReadResources,
ProjectPermissionSecretScanningDataSourceActions.ReadScans
]
},
{
subject: ProjectPermissionSub.SecretScanningFindings,
actions: [ProjectPermissionSecretScanningFindingActions.Read]
},
{
subject: ProjectPermissionSub.SecretScanningConfigs,
actions: [ProjectPermissionSecretScanningConfigActions.Read]
}
]
},
{
id: "scanning-editor",
name: "Secret Scanning Editing Policies",
description: "Grants read and edit access to data sources and findings",
permissions: [
{
subject: ProjectPermissionSub.SecretScanningDataSources,
actions: Object.values(ProjectPermissionSecretScanningDataSourceActions)
},
{
subject: ProjectPermissionSub.SecretScanningFindings,
actions: Object.values(ProjectPermissionSecretScanningFindingActions)
},
{
subject: ProjectPermissionSub.SecretScanningConfigs,
actions: [ProjectPermissionSecretScanningConfigActions.Read]
}
]
},
projectManagerTemplate([
{
subject: ProjectPermissionSub.SecretScanningConfigs,
actions: Object.values(ProjectPermissionSecretScanningConfigActions)
}
])
],
[ProjectType.SecretManager]: [
{
id: "secret-viewer",
@@ -1730,7 +2005,7 @@ export const RoleTemplates: Record<ProjectType, RoleTemplate[]> = {
},
{
subject: ProjectPermissionSub.SecretApproval,
actions: Object.values(ProjectPermissionApprovalActions)
actions: Object.values(ProjectPermissionActions)
},
{
subject: ProjectPermissionSub.ServiceTokens,
@@ -23,6 +23,7 @@ import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies";
import { IdentityManagementPermissionConditions } from "./IdentityManagementPermissionConditions";
import { PermissionEmptyState } from "./PermissionEmptyState";
import { PkiSubscriberPermissionConditions } from "./PkiSubscriberPermissionConditions";
import { PkiTemplatePermissionConditions } from "./PkiTemplatePermissionConditions";
import {
EXCLUDED_PERMISSION_SUBS,
formRolePermission2API,
@@ -64,6 +65,10 @@ export const renderConditionalComponents = (
return <PkiSubscriberPermissionConditions isDisabled={isDisabled} />;
}
if (subject === ProjectPermissionSub.CertificateTemplates) {
return <PkiTemplatePermissionConditions isDisabled={isDisabled} />;
}
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
}
@@ -154,8 +159,8 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
variant="outline_bg"
type="submit"
className={twMerge(
"mr-4 h-10 border border-primary",
isDirty && "bg-primary text-black"
"mr-4 h-10 border",
isDirty && "bg-primary text-black hover:bg-primary hover:opacity-80"
)}
isDisabled={isSubmitting || !isDirty}
isLoading={isSubmitting}
@@ -0,0 +1,28 @@
import { createFileRoute, linkOptions } from "@tanstack/react-router";
import { RoleDetailsBySlugPage } from "./RoleDetailsBySlugPage";
export const Route = createFileRoute(
"/_authenticate/_inject-org-details/_org-layout/secret-scanning/$projectId/_secret-scanning-layout/roles/$roleSlug"
)({
component: RoleDetailsBySlugPage,
beforeLoad: ({ context, params }) => {
return {
breadcrumbs: [
...context.breadcrumbs,
{
label: "Access Control",
link: linkOptions({
to: "/secret-scanning/$projectId/access-management",
params: {
projectId: params.projectId
}
})
},
{
label: "Roles"
}
]
};
}
});