diff --git a/frontend/src/context/OrgPermissionContext/types.ts b/frontend/src/context/OrgPermissionContext/types.ts index 854230eb0..c2873e6f9 100644 --- a/frontend/src/context/OrgPermissionContext/types.ts +++ b/frontend/src/context/OrgPermissionContext/types.ts @@ -62,7 +62,8 @@ export enum OrgPermissionIdentityActions { ManagePrivileges = "manage-privileges", RevokeAuth = "revoke-auth", CreateToken = "create-token", - GetToken = "get-token" + GetToken = "get-token", + DeleteToken = "delete-token" } export enum OrgPermissionGroupActions { diff --git a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts index 42dd31417..56d77ae60 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts +++ b/frontend/src/pages/organization/RoleByIDPage/components/OrgRoleModifySection.utils.ts @@ -5,6 +5,8 @@ import { OrgPermissionSubjects } from "@app/context"; import { OrgGatewayPermissionActions, OrgPermissionAppConnectionActions, + OrgPermissionGroupActions, + OrgPermissionIdentityActions, OrgPermissionKmipActions } from "@app/context/OrgPermissionContext/types"; import { TPermission } from "@app/hooks/api/roles/types"; @@ -35,6 +37,32 @@ const kmipPermissionSchema = z }) .optional(); +const identityPermissionSchema = z + .object({ + [OrgPermissionIdentityActions.Read]: z.boolean().optional(), + [OrgPermissionIdentityActions.Edit]: z.boolean().optional(), + [OrgPermissionIdentityActions.Delete]: z.boolean().optional(), + [OrgPermissionIdentityActions.Create]: z.boolean().optional(), + [OrgPermissionIdentityActions.ManagePrivileges]: z.boolean().optional(), + [OrgPermissionIdentityActions.RevokeAuth]: z.boolean().optional(), + [OrgPermissionIdentityActions.CreateToken]: z.boolean().optional(), + [OrgPermissionIdentityActions.GetToken]: z.boolean().optional(), + [OrgPermissionIdentityActions.DeleteToken]: z.boolean().optional() + }) + .optional(); + +const groupPermissionSchema = z + .object({ + [OrgPermissionGroupActions.Read]: z.boolean().optional(), + [OrgPermissionGroupActions.Create]: z.boolean().optional(), + [OrgPermissionGroupActions.Edit]: z.boolean().optional(), + [OrgPermissionGroupActions.Delete]: z.boolean().optional(), + [OrgPermissionGroupActions.ManagePrivileges]: z.boolean().optional(), + [OrgPermissionGroupActions.AddMembers]: z.boolean().optional(), + [OrgPermissionGroupActions.RemoveMembers]: z.boolean().optional() + }) + .optional(); + const orgGatewayPermissionSchema = z .object({ [OrgGatewayPermissionActions.ListGateways]: z.boolean().optional(), @@ -67,7 +95,7 @@ export const formSchema = z.object({ "audit-logs": generalPermissionSchema, member: generalPermissionSchema, - groups: generalPermissionSchema, + groups: groupPermissionSchema, role: generalPermissionSchema, settings: generalPermissionSchema, "service-account": generalPermissionSchema, @@ -77,7 +105,7 @@ export const formSchema = z.object({ scim: generalPermissionSchema, ldap: generalPermissionSchema, billing: generalPermissionSchema, - identity: generalPermissionSchema, + identity: identityPermissionSchema, "organization-admin-console": adminConsolePermissionSchmea, [OrgPermissionSubjects.Kms]: generalPermissionSchema, [OrgPermissionSubjects.ProjectTemplates]: generalPermissionSchema, diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionGroupRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionGroupRow.tsx new file mode 100644 index 000000000..eafdd3583 --- /dev/null +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionGroupRow.tsx @@ -0,0 +1,207 @@ +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 { OrgPermissionGroupActions } from "@app/context/OrgPermissionContext/types"; +import { useToggle } from "@app/hooks"; + +import { TFormSchema } from "../OrgRoleModifySection.utils"; + +const PERMISSION_ACTIONS = [ + { action: OrgPermissionGroupActions.Read, label: "Read Groups" }, + { action: OrgPermissionGroupActions.Create, label: "Create Groups" }, + { action: OrgPermissionGroupActions.Edit, label: "Edit Groups" }, + { action: OrgPermissionGroupActions.Delete, label: "Delete Groups" }, + { action: OrgPermissionGroupActions.ManagePrivileges, label: "Manage Privileges" }, + { action: OrgPermissionGroupActions.AddMembers, label: "Add Members" }, + { action: OrgPermissionGroupActions.RemoveMembers, label: "Remove Members" } +] as const; + +type Props = { + isEditable: boolean; + setValue: UseFormSetValue; + control: Control; +}; + +enum Permission { + NoAccess = "no-access", + ReadOnly = "read-only", + FullAccess = "full-access", + Custom = "custom" +} + +export const OrgPermissionGroupRow = ({ isEditable, control, setValue }: Props) => { + const [isRowExpanded, setIsRowExpanded] = useToggle(); + const [isCustom, setIsCustom] = useToggle(); + + const rule = useWatch({ + control, + name: "permissions.groups" + }); + + const selectedPermissionCategory = useMemo(() => { + const actions = Object.keys(rule || {}) as Array; + const totalActions = PERMISSION_ACTIONS.length; + const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); + + if (isCustom) return Permission.Custom; + if (score === 0) return Permission.NoAccess; + if (score === totalActions) return Permission.FullAccess; + if (score === 1 && rule?.read) return Permission.ReadOnly; + + return Permission.Custom; + }, [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 === Permission.Custom) { + setIsRowExpanded.on(); + setIsCustom.on(); + return; + } + setIsCustom.off(); + + switch (val) { + case Permission.NoAccess: + setValue( + "permissions.groups", + { + [OrgPermissionGroupActions.Read]: false, + [OrgPermissionGroupActions.Create]: false, + [OrgPermissionGroupActions.Edit]: false, + [OrgPermissionGroupActions.Delete]: false, + [OrgPermissionGroupActions.ManagePrivileges]: false, + [OrgPermissionGroupActions.AddMembers]: false, + [OrgPermissionGroupActions.RemoveMembers]: false + }, + { shouldDirty: true } + ); + break; + case Permission.FullAccess: + setValue( + "permissions.groups", + { + [OrgPermissionGroupActions.Read]: true, + [OrgPermissionGroupActions.Create]: true, + [OrgPermissionGroupActions.Edit]: true, + [OrgPermissionGroupActions.Delete]: true, + [OrgPermissionGroupActions.ManagePrivileges]: true, + [OrgPermissionGroupActions.AddMembers]: true, + [OrgPermissionGroupActions.RemoveMembers]: true + }, + { shouldDirty: true } + ); + break; + case Permission.ReadOnly: + setValue( + "permissions.groups", + { + [OrgPermissionGroupActions.Read]: true, + [OrgPermissionGroupActions.Edit]: false, + [OrgPermissionGroupActions.Create]: false, + [OrgPermissionGroupActions.Delete]: false, + [OrgPermissionGroupActions.ManagePrivileges]: false, + [OrgPermissionGroupActions.AddMembers]: false, + [OrgPermissionGroupActions.RemoveMembers]: false + }, + { shouldDirty: true } + ); + break; + default: + setValue( + "permissions.groups", + { + [OrgPermissionGroupActions.Read]: false, + [OrgPermissionGroupActions.Edit]: false, + [OrgPermissionGroupActions.Create]: false, + [OrgPermissionGroupActions.Delete]: false, + [OrgPermissionGroupActions.ManagePrivileges]: false, + [OrgPermissionGroupActions.AddMembers]: false, + [OrgPermissionGroupActions.RemoveMembers]: false + }, + { shouldDirty: true } + ); + break; + } + }; + + return ( + <> + setIsRowExpanded.toggle()} + > + + + + Group Management + + + + + {isRowExpanded && ( + + +
+ {PERMISSION_ACTIONS.map(({ action, label }) => { + return ( + ( + { + if (!isEditable) { + createNotification({ + type: "error", + text: "Failed to update default role" + }); + return; + } + field.onChange(e); + }} + id={`permissions.groups.${action}`} + > + {label} + + )} + /> + ); + })} +
+ + + )} + + ); +}; diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionIdentityRow.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionIdentityRow.tsx new file mode 100644 index 000000000..e1700c17a --- /dev/null +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/OrgPermissionIdentityRow.tsx @@ -0,0 +1,217 @@ +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 { OrgPermissionIdentityActions } from "@app/context/OrgPermissionContext/types"; +import { useToggle } from "@app/hooks"; + +import { TFormSchema } from "../OrgRoleModifySection.utils"; + +const PERMISSION_ACTIONS = [ + { action: OrgPermissionIdentityActions.Read, label: "Read Identities" }, + { action: OrgPermissionIdentityActions.Create, label: "Create Identities" }, + { action: OrgPermissionIdentityActions.Edit, label: "Edit Identities" }, + { action: OrgPermissionIdentityActions.Delete, label: "Delete Identities" }, + { action: OrgPermissionIdentityActions.ManagePrivileges, label: "Manage Privileges" }, + { action: OrgPermissionIdentityActions.RevokeAuth, label: "Revoke Auth" }, + { action: OrgPermissionIdentityActions.CreateToken, label: "Create Token" }, + { action: OrgPermissionIdentityActions.GetToken, label: "Get Token" }, + { action: OrgPermissionIdentityActions.DeleteToken, label: "Delete Token" } +] as const; + +type Props = { + isEditable: boolean; + setValue: UseFormSetValue; + control: Control; +}; + +enum Permission { + NoAccess = "no-access", + ReadOnly = "read-only", + FullAccess = "full-access", + Custom = "custom" +} + +export const OrgPermissionIdentityRow = ({ isEditable, control, setValue }: Props) => { + const [isRowExpanded, setIsRowExpanded] = useToggle(); + const [isCustom, setIsCustom] = useToggle(); + + const rule = useWatch({ + control, + name: "permissions.identity" + }); + + const selectedPermissionCategory = useMemo(() => { + const actions = Object.keys(rule || {}) as Array; + const totalActions = PERMISSION_ACTIONS.length; + const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number); + + if (isCustom) return Permission.Custom; + if (score === 0) return Permission.NoAccess; + if (score === totalActions) return Permission.FullAccess; + if (score === 1 && rule?.read) return Permission.ReadOnly; + + return Permission.Custom; + }, [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 === Permission.Custom) { + setIsRowExpanded.on(); + setIsCustom.on(); + return; + } + setIsCustom.off(); + + switch (val) { + case Permission.NoAccess: + setValue( + "permissions.identity", + { + [OrgPermissionIdentityActions.Read]: false, + [OrgPermissionIdentityActions.Edit]: false, + [OrgPermissionIdentityActions.Create]: false, + [OrgPermissionIdentityActions.Delete]: false, + [OrgPermissionIdentityActions.ManagePrivileges]: false, + [OrgPermissionIdentityActions.RevokeAuth]: false, + [OrgPermissionIdentityActions.CreateToken]: false, + [OrgPermissionIdentityActions.GetToken]: false, + [OrgPermissionIdentityActions.DeleteToken]: false + }, + { shouldDirty: true } + ); + break; + case Permission.FullAccess: + setValue( + "permissions.identity", + { + [OrgPermissionIdentityActions.Read]: true, + [OrgPermissionIdentityActions.Edit]: true, + [OrgPermissionIdentityActions.Create]: true, + [OrgPermissionIdentityActions.Delete]: true, + [OrgPermissionIdentityActions.ManagePrivileges]: true, + [OrgPermissionIdentityActions.RevokeAuth]: true, + [OrgPermissionIdentityActions.CreateToken]: true, + [OrgPermissionIdentityActions.GetToken]: true, + [OrgPermissionIdentityActions.DeleteToken]: true + }, + { shouldDirty: true } + ); + break; + case Permission.ReadOnly: + setValue( + "permissions.identity", + { + [OrgPermissionIdentityActions.Read]: true, + [OrgPermissionIdentityActions.Edit]: false, + [OrgPermissionIdentityActions.Create]: false, + [OrgPermissionIdentityActions.Delete]: false, + [OrgPermissionIdentityActions.ManagePrivileges]: false, + [OrgPermissionIdentityActions.RevokeAuth]: false, + [OrgPermissionIdentityActions.CreateToken]: false, + [OrgPermissionIdentityActions.GetToken]: false, + [OrgPermissionIdentityActions.DeleteToken]: false + }, + { shouldDirty: true } + ); + break; + default: + setValue( + "permissions.identity", + { + [OrgPermissionIdentityActions.Read]: false, + [OrgPermissionIdentityActions.Edit]: false, + [OrgPermissionIdentityActions.Create]: false, + [OrgPermissionIdentityActions.Delete]: false, + [OrgPermissionIdentityActions.ManagePrivileges]: false, + [OrgPermissionIdentityActions.RevokeAuth]: false, + [OrgPermissionIdentityActions.CreateToken]: false, + [OrgPermissionIdentityActions.GetToken]: false, + [OrgPermissionIdentityActions.DeleteToken]: false + }, + { shouldDirty: true } + ); + break; + } + }; + + return ( + <> + setIsRowExpanded.toggle()} + > + + + + Machine Identity Management + + + + + {isRowExpanded && ( + + +
+ {PERMISSION_ACTIONS.map(({ action, label }) => { + return ( + ( + { + if (!isEditable) { + createNotification({ + type: "error", + text: "Failed to update default role" + }); + return; + } + field.onChange(e); + }} + id={`permissions.identity.${action}`} + > + {label} + + )} + /> + ); + })} +
+ + + )} + + ); +}; diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx index de9fbde80..1816f4292 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -15,6 +15,8 @@ import { } from "../OrgRoleModifySection.utils"; import { OrgPermissionAdminConsoleRow } from "./OrgPermissionAdminConsoleRow"; import { OrgGatewayPermissionRow } from "./OrgPermissionGatewayRow"; +import { OrgPermissionGroupRow } from "./OrgPermissionGroupRow"; +import { OrgPermissionIdentityRow } from "./OrgPermissionIdentityRow"; import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow"; import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow"; import { RolePermissionRow } from "./RolePermissionRow"; @@ -24,14 +26,6 @@ const SIMPLE_PERMISSION_OPTIONS = [ title: "User Management", formName: "member" }, - { - title: "Group Management", - formName: "groups" - }, - { - title: "Machine Identity Management", - formName: "identity" - }, { title: "Usage & Billing", formName: "billing" @@ -167,6 +161,16 @@ export const RolePermissionsSection = ({ roleId }: Props) => { /> ); })} + +