diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e7f57e85c..121dcd094 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -78,7 +78,7 @@ "react-day-picker": "^9.4.3", "react-dom": "^18.3.1", "react-helmet": "^6.1.0", - "react-hook-form": "^7.54.0", + "react-hook-form": "^7.56.3", "react-i18next": "^15.2.0", "react-icons": "^5.4.0", "react-markdown": "^10.0.1", @@ -11484,9 +11484,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.54.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.54.0.tgz", - "integrity": "sha512-PS05+UQy/IdSbJNojBypxAo9wllhHgGmyr8/dyGQcPoiMf3e7Dfb9PWYVRco55bLbxH9S+1yDDJeTdlYCSxO3A==", + "version": "7.56.3", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.3.tgz", + "integrity": "sha512-IK18V6GVbab4TAo1/cz3kqajxbDPGofdF0w7VHdCo0Nt8PrPlOZcuuDq9YYIV1BtjcX78x0XsldbQRQnQXWXmw==", "license": "MIT", "engines": { "node": ">=18.0.0" diff --git a/frontend/package.json b/frontend/package.json index 6225b78f0..7cd636343 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -82,7 +82,7 @@ "react-day-picker": "^9.4.3", "react-dom": "^18.3.1", "react-helmet": "^6.1.0", - "react-hook-form": "^7.54.0", + "react-hook-form": "^7.56.3", "react-i18next": "^15.2.0", "react-icons": "^5.4.0", "react-markdown": "^10.0.1", diff --git a/frontend/src/components/projects/ProjectSettings/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx b/frontend/src/components/projects/ProjectSettings/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx index 7431b41fa..a4bc3a73a 100644 --- a/frontend/src/components/projects/ProjectSettings/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx +++ b/frontend/src/components/projects/ProjectSettings/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx @@ -1,5 +1,5 @@ import { Controller, FormProvider, useForm } from "react-hook-form"; -import { faChevronLeft, faPlus, faSave } from "@fortawesome/free-solid-svg-icons"; +import { faChevronLeft, faSave } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { twMerge } from "tailwind-merge"; @@ -9,12 +9,11 @@ import { createNotification } from "@app/components/notifications"; import { Button, FormControl, Input } from "@app/components/v2"; import { ProjectPermissionSub } from "@app/context"; import { isCustomProjectRole } from "@app/helpers/roles"; -import { usePopUp } from "@app/hooks"; import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates"; import { slugSchema } from "@app/lib/schemas"; +import { AddPoliciesButton } from "@app/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton"; import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies"; import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState"; -import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal"; import { formRolePermission2API, PROJECT_PERMISSION_OBJECT, @@ -44,8 +43,6 @@ export const ProjectTemplateEditRoleForm = ({ role, isDisabled }: Props) => { - const { popUp, handlePopUpToggle } = usePopUp(["addPolicy"] as const); - const formMethods = useForm({ values: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : undefined, resolver: zodResolver(formSchema) @@ -120,7 +117,7 @@ export const ProjectTemplateEditRoleForm = ({ variant="outline_bg" type="submit" className={twMerge( - "h-10 rounded-r-none border border-primary", + "mr-4 h-10 border border-primary", isDirty && "bg-primary text-black" )} isDisabled={isSubmitting || !isDirty || isDisabled} @@ -129,19 +126,7 @@ export const ProjectTemplateEditRoleForm = ({ > Save - - handlePopUpToggle("addPolicy", isOpen)} - /> + )} diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx index 50d392748..14eac860d 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx @@ -1,12 +1,6 @@ import { Controller, FormProvider, useForm } from "react-hook-form"; import { subject } from "@casl/ability"; -import { - faCaretDown, - faChevronLeft, - faClock, - faPlus, - faSave -} from "@fortawesome/free-solid-svg-icons"; +import { faCaretDown, faChevronLeft, faClock, faSave } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { format, formatDistance } from "date-fns"; @@ -33,16 +27,15 @@ import { useProjectPermission, useWorkspace } from "@app/context"; -import { usePopUp } from "@app/hooks"; import { useCreateIdentityProjectAdditionalPrivilege, useGetIdentityProjectPrivilegeDetails, useUpdateIdentityProjectAdditionalPrivilege } from "@app/hooks/api"; import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/identityProjectAdditionalPrivilege/types"; +import { AddPoliciesButton } from "@app/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton"; import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies"; import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState"; -import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal"; import { formRolePermission2API, PROJECT_PERMISSION_OBJECT, @@ -97,7 +90,6 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({ ProjectPermissionIdentityActions.Edit, subject(ProjectPermissionSub.Identity, { identityId }) ); - const { popUp, handlePopUpToggle } = usePopUp(["addPolicy"] as const); const form = useForm({ values: privilegeDetails @@ -224,7 +216,7 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({ variant="outline_bg" type="submit" className={twMerge( - "h-10 rounded-r-none border border-primary", + "mr-4 h-10 border border-primary", isDirty && "bg-primary text-black" )} isDisabled={isSubmitting || !isDirty || isDisabled} @@ -233,15 +225,7 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({ > Save - + @@ -382,10 +366,6 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({ ) )} - handlePopUpToggle("addPolicy", isOpen)} - /> ); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx index 5fee21f9c..fb12f8381 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx @@ -1,11 +1,5 @@ import { Controller, FormProvider, useForm } from "react-hook-form"; -import { - faCaretDown, - faChevronLeft, - faClock, - faPlus, - faSave -} from "@fortawesome/free-solid-svg-icons"; +import { faCaretDown, faChevronLeft, faClock, faSave } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { format, formatDistance } from "date-fns"; @@ -32,16 +26,15 @@ import { useProjectPermission, useWorkspace } from "@app/context"; -import { usePopUp } from "@app/hooks"; import { useCreateProjectUserAdditionalPrivilege, useGetProjectUserPrivilegeDetails, useUpdateProjectUserAdditionalPrivilege } from "@app/hooks/api"; import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/projectUserAdditionalPrivilege/types"; +import { AddPoliciesButton } from "@app/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton"; import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies"; import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState"; -import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal"; import { formRolePermission2API, PROJECT_PERMISSION_OBJECT, @@ -83,8 +76,6 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({ projectMembershipId, isDisabled }: Props) => { - const { popUp, handlePopUpToggle } = usePopUp(["addPolicy"] as const); - const isCreate = !privilegeId; const { currentWorkspace } = useWorkspace(); const projectId = currentWorkspace?.id || ""; @@ -221,7 +212,7 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({ variant="outline_bg" type="submit" className={twMerge( - "h-10 rounded-r-none border border-primary", + "mr-4 h-10 border border-primary", isDirty && "bg-primary text-black" )} isDisabled={isSubmitting || !isDirty || isDisabled} @@ -230,15 +221,7 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({ > Save - + @@ -377,10 +360,6 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({ ))} - handlePopUpToggle("addPolicy", isOpen)} - /> ); diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx new file mode 100644 index 000000000..aaf58d0e2 --- /dev/null +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton.tsx @@ -0,0 +1,78 @@ +import { faAngleDown, faLayerGroup, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, + IconButton +} from "@app/components/v2"; +import { usePopUp } from "@app/hooks"; +import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal"; +import { PolicyTemplateModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal"; + +type Props = { + isDisabled?: boolean; +}; + +export const AddPoliciesButton = ({ isDisabled }: Props) => { + const { popUp, handlePopUpToggle, handlePopUpOpen, handlePopUpClose } = usePopUp([ + "addPolicy", + "addPolicyOptions", + "applyTemplate" + ] as const); + + return ( + <> + + handlePopUpToggle("addPolicyOptions", isOpen)} + > + + + + + + +
+ +
+
+
+ handlePopUpToggle("addPolicy", isOpen)} + /> + handlePopUpToggle("applyTemplate", isOpen)} + /> + + ); +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx index 6773f0658..c46c4fce8 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx @@ -1,5 +1,5 @@ -import { cloneElement, useState } from "react"; -import { Controller, useFieldArray, useFormContext } from "react-hook-form"; +import { cloneElement, ReactNode, useState } from "react"; +import { Controller, useFieldArray, useFormContext, useWatch } from "react-hook-form"; import { faChevronDown, faChevronRight, @@ -29,6 +29,39 @@ type Props = { isDisabled?: boolean; }; +type ActionProps = { + value: string; + subject: ProjectPermissionSub; + rootIndex: number; + label: ReactNode; + isDisabled?: boolean; + control: any; +}; + +const ActionCheckbox = ({ value, subject, isDisabled, rootIndex, label, control }: ActionProps) => { + // scott: using Controller caused discrepency between field value and actual value, this is a hacky fix + const fieldValue = useWatch({ control, name: `permissions.${subject}.${rootIndex}.${value}` }); + const { setValue } = useFormContext(); + + return ( +
+ + setValue(`permissions.${subject}.${rootIndex}.${value}`, isChecked, { + shouldDirty: true, + shouldTouch: true + }) + } + id={`permissions.${subject}.${rootIndex}.${String(value)}`} + > + {label} + +
+ ); +}; + export const GeneralPermissionPolicies = >({ subject, actions, @@ -41,11 +74,18 @@ export const GeneralPermissionPolicies = (null); const [dragOverItem, setDragOverItem] = useState(null); - if (!fields.length) return
; + if (!watchFields?.length) return
; const handleDragStart = (_: React.DragEvent, index: number) => { setDraggedItem(index); @@ -194,25 +234,14 @@ export const GeneralPermissionPolicies = { - return ( -
- - {label} - -
- ); - }} + subject={subject} + isDisabled={isDisabled} /> ); })} diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx new file mode 100644 index 000000000..5f729983c --- /dev/null +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal.tsx @@ -0,0 +1,202 @@ +import { useState } from "react"; +import { useFormContext } from "react-hook-form"; + +import { createNotification } from "@app/components/notifications"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Button, + Modal, + ModalClose, + ModalContent +} from "@app/components/v2"; +import { ProjectPermissionSub } from "@app/context"; +import { useGetProjectTypeFromRoute } from "@app/hooks"; +import { ProjectType } from "@app/hooks/api/workspace/types"; + +import { + PROJECT_PERMISSION_OBJECT, + RoleTemplate, + RoleTemplates, + TFormSchema +} from "./ProjectRoleModifySection.utils"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +type ContentProps = { + onClose: () => void; +}; + +const Content = ({ onClose }: ContentProps) => { + const rootForm = useFormContext(); + const projectType = useGetProjectTypeFromRoute(); + + const [selectedTemplate, setSelectedTemplate] = useState(); + const [conflictingSubjects, setConflictingSubjects] = useState([]); + const [showConflictingSubjects, setShowConflictingSubjects] = useState(false); + + const templates = RoleTemplates[projectType ?? ProjectType.SecretManager]; + + const onSubmit = (skipConflicting = false) => { + if (!selectedTemplate) { + createNotification({ type: "error", text: "Please select a template" }); + return; + } + + selectedTemplate.permissions.forEach(({ subject, actions }) => { + if (skipConflicting && conflictingSubjects.includes(subject)) return; + + rootForm.setValue( + `permissions.${subject}`, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore-error akhilmhdh: this is because of ts collision with both + [Object.fromEntries(actions.map((action) => [action, true]))], + { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true + } + ); + }); + + onClose(); + }; + + const onApply = () => { + if (!selectedTemplate) { + createNotification({ type: "error", text: "Please select a template" }); + return; + } + + const conflictingPolicies: ProjectPermissionSub[] = []; + + selectedTemplate.permissions.forEach(({ subject }) => { + const rootPolicyValue = rootForm.getValues("permissions")?.[subject]; + + if (rootPolicyValue?.length) { + conflictingPolicies.push(subject); + } + }); + + if (conflictingPolicies.length) { + setConflictingSubjects(conflictingPolicies); + setShowConflictingSubjects(true); + return; + } + + onSubmit(); + }; + + return ( + <> + + +
+ {conflictingSubjects.map((subject) => ( +
+ + {PROJECT_PERMISSION_OBJECT[subject].title} + +
+ ))} +
+
+ + + + + + +
+
+
+ + setSelectedTemplate(templates.find((template) => template.id === value)) + } + collapsible + className="w-full border-collapse" + > + {templates.map(({ name, description, permissions, id }) => ( + + +
+ {name} + {description} +
+
+ +
+ Grants the following permissions: +
+ {permissions + .map((permission) => ({ + ...permission, + object: PROJECT_PERMISSION_OBJECT[permission.subject] + })) + .sort((a, b) => a.object.title.localeCompare(b.object.title)) + .map(({ subject, actions, object }) => { + return ( +
+ {object.title} +
    + {actions.map((action) => ( +
  • + {object.actions.find((a) => a.value === action)?.label} +
  • + ))} +
+
+ ); + })} +
+
+
+
+ ))} +
+
+ + + + +
+ + ); +}; + +export const PolicyTemplateModal = ({ isOpen, onOpenChange }: Props) => { + return ( + + + onOpenChange(false)} /> + + + ); +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx index bd5cdaa4e..c4249f76d 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx @@ -877,19 +877,19 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { title: "Dynamic Secrets", actions: [ { - label: "Read root credentials", + label: "Read Root Credentials", value: ProjectPermissionDynamicSecretActions.ReadRootCredential }, { - label: "Create root credentials", + label: "Create Root Credentials", value: ProjectPermissionDynamicSecretActions.CreateRootCredential }, { - label: "Modify root credentials", + label: "Modify Root Credentials", value: ProjectPermissionDynamicSecretActions.EditRootCredential }, { - label: "Remove root credentials", + label: "Remove Root Credentials", value: ProjectPermissionDynamicSecretActions.DeleteRootCredential }, { label: "Manage Leases", value: ProjectPermissionDynamicSecretActions.Lease } @@ -1174,23 +1174,23 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = { title: "KMIP", actions: [ { - label: "Read clients", + label: "Read Clients", value: ProjectPermissionKmipActions.ReadClients }, { - label: "Create clients", + label: "Create Clients", value: ProjectPermissionKmipActions.CreateClients }, { - label: "Modify clients", + label: "Modify Clients", value: ProjectPermissionKmipActions.UpdateClients }, { - label: "Delete clients", + label: "Delete Clients", value: ProjectPermissionKmipActions.DeleteClients }, { - label: "Generate client certificates", + label: "Generate Client Certificates", value: ProjectPermissionKmipActions.GenerateClientCertificates } ] @@ -1280,3 +1280,340 @@ export const ProjectTypePermissionSubjects: Record< ...SecretsManagerPermissionSubjects() } }; + +export type RoleTemplate = { + id: string; + name: string; + description: string; + permissions: { subject: ProjectPermissionSub; actions: string[] }[]; +}; + +const projectManagerTemplate = ( + additionalPermissions: RoleTemplate["permissions"] = [] +): RoleTemplate => ({ + id: "project-manager", + name: "Project Management Policies", + description: "Grants access to manage project members and settings", + permissions: [ + { + subject: ProjectPermissionSub.AuditLogs, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.Groups, + actions: Object.values(ProjectPermissionGroupActions) + }, + { + subject: ProjectPermissionSub.Member, + actions: Object.values(ProjectPermissionMemberActions) + }, + { + subject: ProjectPermissionSub.Identity, + actions: Object.values(ProjectPermissionIdentityActions) + }, + { + subject: ProjectPermissionSub.Project, + actions: [ProjectPermissionActions.Edit, ProjectPermissionActions.Delete] + }, + { subject: ProjectPermissionSub.Role, actions: Object.values(ProjectPermissionActions) }, + { + subject: ProjectPermissionSub.Settings, + actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Edit] + }, + ...additionalPermissions + ] +}); + +export const RoleTemplates: Record = { + [ProjectType.SSH]: [ + { + id: "ssh-viewer", + name: "SSH Viewing Policies", + description: "Grants read access to SSH certificates and hosts", + permissions: [ + { + subject: ProjectPermissionSub.SshCertificateAuthorities, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SshCertificates, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SshCertificateTemplates, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SshHosts, + actions: [ProjectPermissionSshHostActions.Read] + }, + { + subject: ProjectPermissionSub.SshHostGroups, + actions: [ProjectPermissionActions.Read] + } + ] + }, + { + id: "ssh-cert-editor", + name: "SSH Certificate Editing Policies", + description: "Grants read and edit access to SSH certificates", + permissions: [ + { + subject: ProjectPermissionSub.SshCertificateAuthorities, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SshCertificates, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SshCertificateTemplates, + actions: Object.values(ProjectPermissionActions) + } + ] + }, + { + id: "ssh-host-editor", + name: "SSH Host Editing Policies", + description: "Grants read and edit access to SSH hosts", + permissions: [ + { + subject: ProjectPermissionSub.SshHosts, + actions: Object.values(ProjectPermissionSshHostActions) + }, + { + subject: ProjectPermissionSub.SshHostGroups, + actions: Object.values(ProjectPermissionActions) + } + ] + }, + projectManagerTemplate() + ], + [ProjectType.KMS]: [ + { + id: "kms-viewer", + name: "KMS Viewing Policies", + description: "Grants read access to KMS keys and KMIP clients", + permissions: [ + { + subject: ProjectPermissionSub.Cmek, + actions: [ProjectPermissionCmekActions.Read] + }, + { + subject: ProjectPermissionSub.Kmip, + actions: [ProjectPermissionKmipActions.ReadClients] + } + ] + }, + { + id: "key-editor", + name: "KMS Key Editing Policies", + description: "Grants read and edit access to KMS keys", + permissions: [ + { + subject: ProjectPermissionSub.Cmek, + actions: Object.values(ProjectPermissionCmekActions) + } + ] + }, + { + id: "kmip-editor", + name: "KMIP Client Editing Policies", + description: "Grants read and edit access to KMIP clients", + permissions: [ + { + subject: ProjectPermissionSub.Kmip, + actions: Object.values(ProjectPermissionKmipActions) + } + ] + }, + projectManagerTemplate() + ], + [ProjectType.CertificateManager]: [ + { + id: "cert-viewer", + name: "Certificate Viewing Policies", + description: "Grants read access to certificates and related resources", + permissions: [ + { + subject: ProjectPermissionSub.PkiCollections, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.PkiAlerts, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.CertificateAuthorities, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.CertificateTemplates, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.Certificates, + actions: [ + ProjectPermissionCertificateActions.Read, + ProjectPermissionCertificateActions.ReadPrivateKey + ] + } + ] + }, + { + id: "cert-editor", + name: "Certificate Editing Policies", + description: "Grants read and edit access to certificates and related resources", + permissions: [ + { + subject: ProjectPermissionSub.PkiCollections, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.PkiAlerts, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.CertificateAuthorities, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.CertificateTemplates, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.Certificates, + actions: Object.values(ProjectPermissionCertificateActions) + } + ] + }, + projectManagerTemplate() + ], + [ProjectType.SecretManager]: [ + { + id: "secret-viewer", + name: "Secret Viewing Policies", + description: "Grants read access to secrets and related resources", + permissions: [ + { + subject: ProjectPermissionSub.SecretRollback, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SecretImports, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.Secrets, + actions: [ + ProjectPermissionSecretActions.DescribeSecret, + ProjectPermissionSecretActions.ReadValue + ] + }, + { + subject: ProjectPermissionSub.DynamicSecrets, + actions: [ProjectPermissionDynamicSecretActions.ReadRootCredential] + }, + { + subject: ProjectPermissionSub.Environments, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.Tags, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SecretRotation, + actions: [ProjectPermissionSecretRotationActions.Read] + }, + { + subject: ProjectPermissionSub.Integrations, + actions: [ProjectPermissionActions.Read] + }, + { + subject: ProjectPermissionSub.SecretSyncs, + actions: [ProjectPermissionSecretSyncActions.Read] + } + ] + }, + { + id: "secret-editor", + name: "Secret Editing Policies", + description: "Grants read and edit access to secrets and related resources", + permissions: [ + { + subject: ProjectPermissionSub.Environments, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.DynamicSecrets, + actions: Object.values(ProjectPermissionDynamicSecretActions) + }, + { + subject: ProjectPermissionSub.Secrets, + actions: [ + ProjectPermissionSecretActions.DescribeSecret, + ProjectPermissionSecretActions.ReadValue, + ProjectPermissionSecretActions.Edit, + ProjectPermissionSecretActions.Create, + ProjectPermissionSecretActions.Delete + ] + }, + { + subject: ProjectPermissionSub.SecretRollback, + actions: [ProjectPermissionActions.Read, ProjectPermissionActions.Create] + }, + { + subject: ProjectPermissionSub.Tags, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SecretImports, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SecretRotation, + actions: Object.values(ProjectPermissionSecretRotationActions) + }, + { + subject: ProjectPermissionSub.SecretFolders, + actions: [ + ProjectPermissionActions.Create, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Delete + ] + }, + { + subject: ProjectPermissionSub.Integrations, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.SecretSyncs, + actions: Object.values(ProjectPermissionSecretSyncActions) + } + ] + }, + projectManagerTemplate([ + { + subject: ProjectPermissionSub.IpAllowList, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.Kms, + actions: [ProjectPermissionActions.Edit] + }, + { + subject: ProjectPermissionSub.SecretApproval, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.ServiceTokens, + actions: Object.values(ProjectPermissionActions) + }, + { + subject: ProjectPermissionSub.Webhooks, + actions: Object.values(ProjectPermissionActions) + } + ]) + ] +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index 8dc9a2fa1..804ad251a 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { MongoAbility, MongoQuery, RawRuleOf } from "@casl/ability"; -import { faPlus, faSave } from "@fortawesome/free-solid-svg-icons"; +import { faSave } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { twMerge } from "tailwind-merge"; @@ -12,17 +12,16 @@ import { Button } from "@app/components/v2"; import { ProjectPermissionSub, useWorkspace } from "@app/context"; import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext"; import { evaluatePermissionsAbility } from "@app/helpers/permissions"; -import { usePopUp } from "@app/hooks"; import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api"; import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { ProjectType } from "@app/hooks/api/workspace/types"; -import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal"; +import { AddPoliciesButton } from "@app/pages/project/RoleDetailsBySlugPage/components/AddPoliciesButton"; +import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies"; +import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState"; import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions"; import { GeneralPermissionConditions } from "./GeneralPermissionConditions"; -import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies"; import { IdentityManagementPermissionConditions } from "./IdentityManagementPermissionConditions"; -import { PermissionEmptyState } from "./PermissionEmptyState"; import { formRolePermission2API, isConditionalSubjects, @@ -86,8 +85,6 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { const { mutateAsync: updateRole } = useUpdateProjectRole(); - const { popUp, handlePopUpToggle } = usePopUp(["addPolicy"] as const); - const onSubmit = async (el: TFormSchema) => { try { if (!projectId || !role?.id) return; @@ -151,7 +148,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { variant="outline_bg" type="submit" className={twMerge( - "h-10 rounded-r-none border border-primary", + "mr-4 h-10 border border-primary", isDirty && "bg-primary text-black" )} isDisabled={isSubmitting || !isDirty} @@ -160,15 +157,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { > Save - +
)} @@ -190,10 +179,6 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { ))}
- handlePopUpToggle("addPolicy", isOpen)} - />