mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 17:26:36 +00:00
fix: add sub org permissions UI
This commit is contained in:
@@ -62,7 +62,8 @@ export enum OrgPermissionSubjects {
|
|||||||
SecretShare = "secret-share",
|
SecretShare = "secret-share",
|
||||||
GithubOrgSync = "github-org-sync",
|
GithubOrgSync = "github-org-sync",
|
||||||
GithubOrgSyncManual = "github-org-sync-manual",
|
GithubOrgSyncManual = "github-org-sync-manual",
|
||||||
MachineIdentityAuthTemplate = "machine-identity-auth-template"
|
MachineIdentityAuthTemplate = "machine-identity-auth-template",
|
||||||
|
SubOrganization = "sub-organization"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
@@ -112,6 +113,11 @@ export enum OrgPermissionGroupActions {
|
|||||||
RemoveMembers = "remove-members"
|
RemoveMembers = "remove-members"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrgPermissionSubOrgActions {
|
||||||
|
Create = "create",
|
||||||
|
DirectAccess = "direct-access"
|
||||||
|
}
|
||||||
|
|
||||||
export type AppConnectionSubjectFields = {
|
export type AppConnectionSubjectFields = {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
};
|
};
|
||||||
@@ -151,6 +157,7 @@ export type OrgPermissionSet =
|
|||||||
| OrgPermissionSubjects.AppConnections
|
| OrgPermissionSubjects.AppConnections
|
||||||
| (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
| (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
||||||
)
|
)
|
||||||
];
|
]
|
||||||
|
| [OrgPermissionSubOrgActions, OrgPermissionSubjects.SubOrganization];
|
||||||
|
|
||||||
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
export type TOrgPermission = MongoAbility<OrgPermissionSet>;
|
||||||
|
|||||||
+10
-1
@@ -12,6 +12,7 @@ import {
|
|||||||
OrgPermissionKmipActions,
|
OrgPermissionKmipActions,
|
||||||
OrgPermissionMachineIdentityAuthTemplateActions,
|
OrgPermissionMachineIdentityAuthTemplateActions,
|
||||||
OrgPermissionSecretShareAction,
|
OrgPermissionSecretShareAction,
|
||||||
|
OrgPermissionSubOrgActions,
|
||||||
OrgRelayPermissionActions
|
OrgRelayPermissionActions
|
||||||
} from "@app/context/OrgPermissionContext/types";
|
} from "@app/context/OrgPermissionContext/types";
|
||||||
import { TPermission } from "@app/hooks/api/roles/types";
|
import { TPermission } from "@app/hooks/api/roles/types";
|
||||||
@@ -123,6 +124,13 @@ const secretSharingPermissionSchema = z
|
|||||||
})
|
})
|
||||||
.optional();
|
.optional();
|
||||||
|
|
||||||
|
const subOrganizationPermissionSchema = z
|
||||||
|
.object({
|
||||||
|
[OrgPermissionSubOrgActions.Create]: z.boolean().optional(),
|
||||||
|
[OrgPermissionSubOrgActions.DirectAccess]: z.boolean().optional()
|
||||||
|
})
|
||||||
|
.optional();
|
||||||
|
|
||||||
export const formSchema = z.object({
|
export const formSchema = z.object({
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
@@ -159,7 +167,8 @@ export const formSchema = z.object({
|
|||||||
gateway: orgGatewayPermissionSchema,
|
gateway: orgGatewayPermissionSchema,
|
||||||
relay: orgRelayPermissionSchema,
|
relay: orgRelayPermissionSchema,
|
||||||
"machine-identity-auth-template": machineIdentityAuthTemplatePermissionSchema,
|
"machine-identity-auth-template": machineIdentityAuthTemplatePermissionSchema,
|
||||||
"secret-share": secretSharingPermissionSchema
|
"secret-share": secretSharingPermissionSchema,
|
||||||
|
"sub-organization": subOrganizationPermissionSchema
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
});
|
});
|
||||||
|
|||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
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 { OrgPermissionSubOrgActions } from "@app/context/OrgPermissionContext/types";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
|
||||||
|
import { TFormSchema } from "../OrgRoleModifySection.utils";
|
||||||
|
|
||||||
|
const PERMISSION_ACTIONS = [
|
||||||
|
{ action: OrgPermissionSubOrgActions.Create, label: "Create" },
|
||||||
|
{ action: OrgPermissionSubOrgActions.DirectAccess, label: "Direct Access" }
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isEditable: boolean;
|
||||||
|
setValue: UseFormSetValue<TFormSchema>;
|
||||||
|
control: Control<TFormSchema>;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Permission {
|
||||||
|
NoAccess = "no-access",
|
||||||
|
FullAccess = "full-access",
|
||||||
|
Custom = "custom"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrgPermissionSubOrgRow = ({ isEditable, control, setValue }: Props) => {
|
||||||
|
const [isRowExpanded, setIsRowExpanded] = useToggle();
|
||||||
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
|
const rule = useWatch({
|
||||||
|
control,
|
||||||
|
name: "permissions.sub-organization"
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
|
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;
|
||||||
|
return Permission.Custom;
|
||||||
|
}, [rule, isCustom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
|
||||||
|
else setIsCustom.off();
|
||||||
|
}, [selectedPermissionCategory]);
|
||||||
|
|
||||||
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
if (val === Permission.Custom) {
|
||||||
|
setIsRowExpanded.on();
|
||||||
|
setIsCustom.on();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsCustom.off();
|
||||||
|
|
||||||
|
switch (val) {
|
||||||
|
case Permission.NoAccess:
|
||||||
|
setValue(
|
||||||
|
"permissions.sub-organization",
|
||||||
|
{
|
||||||
|
[OrgPermissionSubOrgActions.Create]: false,
|
||||||
|
[OrgPermissionSubOrgActions.DirectAccess]: false
|
||||||
|
},
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Permission.FullAccess:
|
||||||
|
setValue(
|
||||||
|
"permissions.sub-organization",
|
||||||
|
{
|
||||||
|
[OrgPermissionSubOrgActions.Create]: true,
|
||||||
|
[OrgPermissionSubOrgActions.DirectAccess]: true
|
||||||
|
},
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setValue(
|
||||||
|
"permissions.sub-organization",
|
||||||
|
{
|
||||||
|
[OrgPermissionSubOrgActions.Create]: true,
|
||||||
|
[OrgPermissionSubOrgActions.DirectAccess]: true
|
||||||
|
},
|
||||||
|
{ shouldDirty: true }
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tr
|
||||||
|
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||||
|
onClick={() => setIsRowExpanded.toggle()}
|
||||||
|
>
|
||||||
|
<Td className="w-4">
|
||||||
|
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
|
||||||
|
</Td>
|
||||||
|
<Td className="w-full select-none">Sub-Organizations</Td>
|
||||||
|
<Td>
|
||||||
|
<Select
|
||||||
|
value={selectedPermissionCategory}
|
||||||
|
className="h-8 w-40 bg-mineshaft-700"
|
||||||
|
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
|
||||||
|
onValueChange={handlePermissionChange}
|
||||||
|
isDisabled={!isEditable}
|
||||||
|
position="popper"
|
||||||
|
>
|
||||||
|
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||||
|
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
|
||||||
|
<SelectItem value={Permission.Custom}>Custom</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
{isRowExpanded && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
|
||||||
|
<div className="flex grow flex-wrap justify-start gap-x-8 gap-y-4">
|
||||||
|
{PERMISSION_ACTIONS.map(({ action, label }) => {
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
name={`permissions.sub-organization.${action}`}
|
||||||
|
key={`permissions.sub-organization.${action}`}
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Checkbox
|
||||||
|
isChecked={field.value}
|
||||||
|
onCheckedChange={(e) => {
|
||||||
|
if (!isEditable) {
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
text: "Failed to update default role"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
field.onChange(e);
|
||||||
|
}}
|
||||||
|
id={`permissions.sub-organization.${action}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Checkbox>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+1
@@ -74,6 +74,7 @@ type Props = {
|
|||||||
| "billing"
|
| "billing"
|
||||||
| "audit-logs"
|
| "audit-logs"
|
||||||
| "machine-identity-auth-template"
|
| "machine-identity-auth-template"
|
||||||
|
| "sub-organization"
|
||||||
>;
|
>;
|
||||||
setValue: UseFormSetValue<TFormSchema>;
|
setValue: UseFormSetValue<TFormSchema>;
|
||||||
control: Control<TFormSchema>;
|
control: Control<TFormSchema>;
|
||||||
|
|||||||
+6
@@ -25,6 +25,7 @@ import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow";
|
|||||||
import { OrgPermissionMachineIdentityAuthTemplateRow } from "./OrgPermissionMachineIdentityAuthTemplateRow";
|
import { OrgPermissionMachineIdentityAuthTemplateRow } from "./OrgPermissionMachineIdentityAuthTemplateRow";
|
||||||
import { OrgRelayPermissionRow } from "./OrgPermissionRelayRow";
|
import { OrgRelayPermissionRow } from "./OrgPermissionRelayRow";
|
||||||
import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow";
|
import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow";
|
||||||
|
import { OrgPermissionSubOrgRow } from "./OrgPermissionSubOrgRow";
|
||||||
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
|
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
|
||||||
import { RolePermissionRow } from "./RolePermissionRow";
|
import { RolePermissionRow } from "./RolePermissionRow";
|
||||||
|
|
||||||
@@ -224,6 +225,11 @@ export const RolePermissionsSection = ({ roleId }: Props) => {
|
|||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
isEditable={isCustomRole}
|
isEditable={isCustomRole}
|
||||||
/>
|
/>
|
||||||
|
<OrgPermissionSubOrgRow
|
||||||
|
control={control}
|
||||||
|
setValue={setValue}
|
||||||
|
isEditable={isCustomRole}
|
||||||
|
/>
|
||||||
</TBody>
|
</TBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|||||||
-1
@@ -1,6 +1,5 @@
|
|||||||
import { faPlus, faTrash, faUnlock } from "@fortawesome/free-solid-svg-icons";
|
import { faPlus, faTrash, faUnlock } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { OrgPermissionCan } from "@app/components/permissions";
|
import { OrgPermissionCan } from "@app/components/permissions";
|
||||||
|
|||||||
Reference in New Issue
Block a user