mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 15:31:19 +00:00
feat(rbac): made changes from testing with maidul
This commit is contained in:
+27
-33
@@ -16,37 +16,37 @@ export const formSchema = z.object({
|
||||
name: z.string().trim(),
|
||||
description: z.string().trim().optional(),
|
||||
slug: z.string().trim(),
|
||||
permissions: z.object({
|
||||
workspace: z
|
||||
.object({
|
||||
read: z.boolean().optional(),
|
||||
create: z.boolean().optional()
|
||||
})
|
||||
.optional(),
|
||||
member: generalPermissionSchema,
|
||||
role: generalPermissionSchema,
|
||||
settings: generalPermissionSchema,
|
||||
"service-account": generalPermissionSchema,
|
||||
"incident-contact": generalPermissionSchema,
|
||||
"secret-scanning": generalPermissionSchema,
|
||||
sso: generalPermissionSchema,
|
||||
billing: generalPermissionSchema
|
||||
})
|
||||
permissions: z
|
||||
.object({
|
||||
workspace: z
|
||||
.object({
|
||||
read: z.boolean().optional(),
|
||||
create: z.boolean().optional()
|
||||
})
|
||||
.optional(),
|
||||
member: generalPermissionSchema,
|
||||
role: generalPermissionSchema,
|
||||
settings: generalPermissionSchema,
|
||||
"service-account": generalPermissionSchema,
|
||||
"incident-contact": generalPermissionSchema,
|
||||
"secret-scanning": generalPermissionSchema,
|
||||
sso: generalPermissionSchema,
|
||||
billing: generalPermissionSchema
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
export type TFormSchema = z.infer<typeof formSchema>;
|
||||
|
||||
// convert role permission to form compatiable data structure
|
||||
export const rolePermission2Form = (permissions: TPermission[] = []) => {
|
||||
const formVal: Partial<TFormSchema["permissions"]> = {};
|
||||
|
||||
// any because if it set it as form type due to the discriminated union type of ts
|
||||
// i would have to write a if loop with both conditions same
|
||||
const formVal: Record<string, any> = {};
|
||||
permissions.forEach((permission) => {
|
||||
const { subject, action } = permission;
|
||||
if (!formVal?.[subject]) formVal[subject] = {};
|
||||
|
||||
// akhilmhdh: this is typecast as something other than workspace key else i would need an if loop with same condition on both side
|
||||
const key = subject as keyof TFormSchema["permissions"];
|
||||
(formVal[key] as Exclude<TFormSchema["permissions"]["member"], undefined>)[action] = true;
|
||||
formVal[subject][action] = true;
|
||||
});
|
||||
|
||||
return formVal;
|
||||
@@ -54,19 +54,13 @@ export const rolePermission2Form = (permissions: TPermission[] = []) => {
|
||||
|
||||
export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
|
||||
const permissions: TPermission[] = [];
|
||||
(Object.keys(formVal) as Array<keyof typeof formVal>).forEach((rule) => {
|
||||
// all these type annotations are due to Object.keys of ts cannot infer and put it just a string[]
|
||||
// quite annoying i know
|
||||
const actions = Object.keys(formVal[rule] || {}) as Array<
|
||||
keyof z.infer<typeof generalPermissionSchema>
|
||||
>;
|
||||
|
||||
actions.forEach((action) => {
|
||||
// akhilmhdh: set it as any due to the union type bug i would end up writing an if else with same condition on both side
|
||||
if (formVal?.[rule]?.[action as keyof typeof formVal.workspace]) {
|
||||
permissions.push({ subject: rule, action } as any);
|
||||
Object.entries(formVal || {}).forEach(([rule, actions]) => {
|
||||
Object.entries(actions).forEach(([action, isAllowed]) => {
|
||||
if (isAllowed) {
|
||||
permissions.push({ subject: rule, action });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return permissions;
|
||||
};
|
||||
|
||||
+27
-2
@@ -11,7 +11,7 @@ import { useToggle } from "@app/hooks";
|
||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
||||
|
||||
type Props = {
|
||||
formName: keyof Omit<TFormSchema["permissions"], "workspace">;
|
||||
formName: keyof Omit<Exclude<TFormSchema["permissions"], undefined>, "workspace">;
|
||||
isNonEditable?: boolean;
|
||||
setValue: UseFormSetValue<TFormSchema>;
|
||||
control: Control<TFormSchema>;
|
||||
@@ -34,6 +34,31 @@ const PERMISSIONS = [
|
||||
{ action: "delete", label: "Remove" }
|
||||
] as const;
|
||||
|
||||
const SECRET_SCANNING_PERMISSIONS = [
|
||||
{ action: "read", label: "View risks" },
|
||||
{ action: "create", label: "Add integrations" },
|
||||
{ action: "edit", label: "Edit risk status" },
|
||||
{ action: "delete", label: "Remove integrations" }
|
||||
] as const;
|
||||
|
||||
const BILLING_PERMISSIONS = [
|
||||
{ action: "read", label: "View bills" },
|
||||
{ action: "create", label: "Add payment methods" },
|
||||
{ action: "edit", label: "Edit payments" },
|
||||
{ action: "delete", label: "Remove payments" }
|
||||
] as const;
|
||||
|
||||
const getPermissionList = (option: Props["formName"]) => {
|
||||
switch (option) {
|
||||
case "secret-scanning":
|
||||
return SECRET_SCANNING_PERMISSIONS;
|
||||
case "billing":
|
||||
return BILLING_PERMISSIONS;
|
||||
default:
|
||||
return PERMISSIONS;
|
||||
}
|
||||
};
|
||||
|
||||
export const SimpleLevelPermissionOption = ({
|
||||
isNonEditable,
|
||||
setValue,
|
||||
@@ -138,7 +163,7 @@ export const SimpleLevelPermissionOption = ({
|
||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||
>
|
||||
{isCustom &&
|
||||
PERMISSIONS.map(({ action, label }) => (
|
||||
getPermissionList(formName).map(({ action, label }) => (
|
||||
<Controller
|
||||
name={`permissions.${formName}.${action}`}
|
||||
key={`permissions.${formName}.${action}`}
|
||||
|
||||
+3
-1
@@ -86,7 +86,9 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">Project</div>
|
||||
<div className="text-xs font-light">Project management control</div>
|
||||
<div className="text-xs font-light">
|
||||
More fine granined project access control can be defined with project level roles
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Select
|
||||
|
||||
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
@@ -16,10 +17,9 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useDeleteRole } from "@app/hooks/api";
|
||||
import { TRole } from "@app/hooks/api/roles/types";
|
||||
@@ -65,9 +65,17 @@ export const OrgRoleTable = ({ isRolesLoading, roles = [], onSelectRole }: Props
|
||||
placeholder="Search roles..."
|
||||
/>
|
||||
</div>
|
||||
<Button leftIcon={<FontAwesomeIcon icon={faPlus} />} onClick={() => onSelectRole()}>
|
||||
Add Role
|
||||
</Button>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Role}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => onSelectRole()}
|
||||
>
|
||||
Add Role
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<div>
|
||||
<TableContainer>
|
||||
@@ -95,27 +103,42 @@ export const OrgRoleTable = ({ isRolesLoading, roles = [], onSelectRole }: Props
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex space-x-2 items-center">
|
||||
<Tooltip content="Edit">
|
||||
<IconButton
|
||||
ariaLabel="edit"
|
||||
onClick={() => onSelectRole(role)}
|
||||
variant="plain"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={isNonMutatable ? "Reserved roles are non-removable" : "Delete"}
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Edit}
|
||||
a={OrgPermissionSubjects.Role}
|
||||
renderTooltip
|
||||
allowedLabel="Edit"
|
||||
>
|
||||
<IconButton
|
||||
ariaLabel="delete"
|
||||
onClick={() => handlePopUpOpen("deleteRole", role)}
|
||||
variant="plain"
|
||||
isDisabled={isNonMutatable}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
isDisabled={!isAllowed}
|
||||
ariaLabel="edit"
|
||||
onClick={() => onSelectRole(role)}
|
||||
variant="plain"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
a={OrgPermissionSubjects.Role}
|
||||
renderTooltip
|
||||
allowedLabel={
|
||||
isNonMutatable ? "Reserved roles are non-removable" : "Delete"
|
||||
}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
ariaLabel="delete"
|
||||
onClick={() => handlePopUpOpen("deleteRole", role)}
|
||||
variant="plain"
|
||||
isDisabled={isNonMutatable || !isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
|
||||
Reference in New Issue
Block a user