From c0de4ae3ee474d7b07dda7bf00343d9befcb4510 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Thu, 13 Mar 2025 12:44:38 -0300 Subject: [PATCH] Add secret share permissions --- .../ee/services/permission/org-permission.ts | 18 ++- .../src/context/OrgPermissionContext/types.ts | 10 +- .../OrganizationLayout/OrganizationLayout.tsx | 12 +- .../components/OrgRoleModifySection.utils.ts | 12 +- .../OrgPermissionSecretShareRow.tsx | 130 ++++++++++++++++++ .../RolePermissionRow.tsx | 2 +- .../RolePermissionsSection.tsx | 8 ++ .../SecretSharingSettingsPage.tsx | 6 +- 8 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionSecretShareRow.tsx diff --git a/backend/src/ee/services/permission/org-permission.ts b/backend/src/ee/services/permission/org-permission.ts index dbabcc2c1..8a2646227 100644 --- a/backend/src/ee/services/permission/org-permission.ts +++ b/backend/src/ee/services/permission/org-permission.ts @@ -32,6 +32,10 @@ export enum OrgPermissionAdminConsoleAction { AccessAllProjects = "access-all-projects" } +export enum OrgPermissionSecretShareAction { + ManageSettings = "manage-settings" +} + export enum OrgPermissionGatewayActions { // is there a better word for this. This mean can an identity be a gateway CreateGateways = "create-gateways", @@ -59,7 +63,8 @@ export enum OrgPermissionSubjects { ProjectTemplates = "project-templates", AppConnections = "app-connections", Kmip = "kmip", - Gateway = "gateway" + Gateway = "gateway", + SecretShare = "secret-share" } export type AppConnectionSubjectFields = { @@ -91,7 +96,8 @@ export type OrgPermissionSet = ) ] | [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole] - | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]; + | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip] + | [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare]; const AppConnectionConditionSchema = z .object({ @@ -185,6 +191,12 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [ "Describe what action an entity can take." ) }), + z.object({ + subject: z.literal(OrgPermissionSubjects.SecretShare).describe("The entity this permission pertains to."), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionSecretShareAction).describe( + "Describe what action an entity can take." + ) + }), z.object({ subject: z.literal(OrgPermissionSubjects.Kmip).describe("The entity this permission pertains to."), action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionKmipActions).describe( @@ -292,6 +304,8 @@ const buildAdminPermission = () => { // the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); + can(OrgPermissionSecretShareAction.ManageSettings, OrgPermissionSubjects.SecretShare); + return rules; }; diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index 2a31ed6a8..9e60b5cb3 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -34,13 +34,18 @@ export enum OrgPermissionSubjects { ProjectTemplates = "project-templates", AppConnections = "app-connections", Kmip = "kmip", - Gateway = "gateway" + Gateway = "gateway", + SecretShare = "secret-share" } export enum OrgPermissionAdminConsoleAction { AccessAllProjects = "access-all-projects" } +export enum OrgPermissionSecretShareAction { + ManageSettings = "manage-settings" +} + export enum OrgPermissionAppConnectionActions { Read = "read", Create = "create", @@ -78,7 +83,8 @@ export type OrgPermissionSet = | [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates] | [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections] | [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip] - | [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]; + | [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway] + | [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare]; // TODO(scott): add back once org UI refactored // | [ // OrgPermissionAppConnectionActions, diff --git a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx index 583beed9a..7cfc21838 100644 --- a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx +++ b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx @@ -8,12 +8,8 @@ import { twMerge } from "tailwind-merge"; import { CreateOrgModal } from "@app/components/organization/CreateOrgModal"; import { Banner } from "@app/components/page-frames/Banner"; import { BreadcrumbContainer, TBreadcrumbFormat } from "@app/components/v2"; -import { - OrgPermissionActions, - OrgPermissionSubjects, - useOrgPermission, - useServerConfig -} from "@app/context"; +import { OrgPermissionSubjects, useOrgPermission, useServerConfig } from "@app/context"; +import { OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types"; import { usePopUp } from "@app/hooks"; import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner"; @@ -28,8 +24,8 @@ export const OrganizationLayout = () => { const { permission } = useOrgPermission(); const shouldShowProductsSidebar = permission.can( - OrgPermissionActions.Edit, - OrgPermissionSubjects.Settings + OrgPermissionSecretShareAction.ManageSettings, + OrgPermissionSubjects.SecretShare ); const isOrganizationSpecificPage = location.pathname.startsWith("/organization"); diff --git a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts index 42dd31417..512368fbf 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts +++ b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts @@ -5,7 +5,8 @@ import { OrgPermissionSubjects } from "@app/context"; import { OrgGatewayPermissionActions, OrgPermissionAppConnectionActions, - OrgPermissionKmipActions + OrgPermissionKmipActions, + OrgPermissionSecretShareAction } from "@app/context/OrgPermissionContext/types"; import { TPermission } from "@app/hooks/api/roles/types"; @@ -50,6 +51,12 @@ const adminConsolePermissionSchmea = z }) .optional(); +const secretSharingPermissionSchema = z + .object({ + [OrgPermissionSecretShareAction.ManageSettings]: z.boolean().optional() + }) + .optional(); + export const formSchema = z.object({ name: z.string().trim(), description: z.string().trim().optional(), @@ -83,7 +90,8 @@ export const formSchema = z.object({ [OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema, "app-connections": appConnectionsPermissionSchema, kmip: kmipPermissionSchema, - gateway: orgGatewayPermissionSchema + gateway: orgGatewayPermissionSchema, + "secret-share": secretSharingPermissionSchema }) .optional() }); diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionSecretShareRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionSecretShareRow.tsx new file mode 100644 index 000000000..4a35644c7 --- /dev/null +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionSecretShareRow.tsx @@ -0,0 +1,130 @@ +import { useEffect, useMemo } from "react"; +import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form"; +import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2"; +import { useToggle } from "@app/hooks"; + +import { TFormSchema } from "../OrgRoleModifySection.utils"; + +type Props = { + isEditable: boolean; + setValue: UseFormSetValue; + control: Control; +}; + +enum Permission { + NoAccess = "no-access", + Custom = "custom" +} + +const PERMISSION_ACTIONS = [{ action: "manage-settings", label: "Manage settings" }] as const; + +export const OrgPermissionSecretShareRow = ({ isEditable, control, setValue }: Props) => { + const [isRowExpanded, setIsRowExpanded] = useToggle(); + const [isCustom, setIsCustom] = useToggle(); + + const rule = useWatch({ + control, + name: "permissions.secret-share" + }); + + const selectedPermissionCategory = useMemo(() => { + if (rule?.["manage-settings"]) { + return Permission.Custom; + } + return Permission.NoAccess; + }, [rule, isCustom]); + + useEffect(() => { + if (selectedPermissionCategory === Permission.Custom) setIsCustom.on(); + else setIsCustom.off(); + }, [selectedPermissionCategory]); + + useEffect(() => { + const isRowCustom = selectedPermissionCategory === Permission.Custom; + if (isRowCustom) { + setIsRowExpanded.on(); + } + }, []); + + const handlePermissionChange = (val: Permission) => { + if (!val) return; + if (val === Permission.Custom) { + setIsRowExpanded.on(); + setIsCustom.on(); + return; + } + setIsCustom.off(); + + if (val === Permission.NoAccess) { + setValue("permissions.secret-share", { "manage-settings": false }, { shouldDirty: true }); + } + }; + + return ( + <> + setIsRowExpanded.toggle()} + > + + + + Secret Share + + + + + {isRowExpanded && ( + + +
+ {PERMISSION_ACTIONS.map(({ action, label }) => { + return ( + ( + { + if (!isEditable) { + createNotification({ + type: "error", + text: "Failed to update default role" + }); + return; + } + field.onChange(e); + }} + id={`permissions.secret-share.${action}`} + > + {label} + + )} + /> + ); + })} +
+ + + )} + + ); +}; diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx index aed309d04..0bc765eb0 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionRow.tsx @@ -74,7 +74,7 @@ type Props = { title: string; formName: keyof Omit< Exclude, - "workspace" | "organization-admin-console" | "kmip" | "gateway" + "workspace" | "organization-admin-console" | "kmip" | "gateway" | "secret-share" >; setValue: UseFormSetValue; control: Control; diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx index de9fbde80..8d0a3d7d9 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -16,6 +16,7 @@ import { import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow"; import { OrgGatewayPermissionRow } from "./OrgPermissionGatewayRow"; import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow"; +import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow"; import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow"; import { RolePermissionRow } from "./RolePermissionRow"; @@ -100,6 +101,8 @@ export const RolePermissionsSection = ({ roleId }: Props) => { const onSubmit = async (el: TFormSchema) => { try { + console.log(el.permissions); + console.log(formRolePermission2API(el.permissions)); await updateRole({ orgId, id: roleId, @@ -177,6 +180,11 @@ export const RolePermissionsSection = ({ roleId }: Props) => { setValue={setValue} isEditable={isCustomRole} /> +