mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 14:34:09 +00:00
feat(rbac): grouped folder and imports permission into secret permission
This commit is contained in:
@@ -20,9 +20,7 @@ export enum ProjectPermissionSub {
|
||||
IpAllowList = "ip-allowlist",
|
||||
Workspace = "workspace",
|
||||
Secrets = "secrets",
|
||||
SecretImports = "secret-imports",
|
||||
SecretRollback = "secret-rollback",
|
||||
Folders = "folders"
|
||||
SecretRollback = "secret-rollback"
|
||||
}
|
||||
|
||||
type SubjectFields = {
|
||||
@@ -35,17 +33,6 @@ export type ProjectPermissionSet =
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub.Folders | (ForcedSubject<ProjectPermissionSub.Folders> & SubjectFields)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionActions,
|
||||
(
|
||||
| ProjectPermissionSub.SecretImports
|
||||
| (ForcedSubject<ProjectPermissionSub.SecretImports> & SubjectFields)
|
||||
)
|
||||
]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
||||
|
||||
@@ -258,21 +258,7 @@ export const DashboardPage = () => {
|
||||
?.map(({ name }) => name)
|
||||
.join("/")}`;
|
||||
|
||||
const userAvailableEnvs = currentWorkspace?.environments?.filter(
|
||||
({ slug }) =>
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: slug, secretPath })
|
||||
) ||
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Folders, { environment: slug, secretPath })
|
||||
) ||
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.SecretImports, { environment: slug, secretPath })
|
||||
)
|
||||
);
|
||||
const userAvailableEnvs = currentWorkspace?.environments || [];
|
||||
|
||||
// This is for dnd-kit. As react-query state mutation async
|
||||
// This will act as a placeholder to avoid a glitching animation on dropping items
|
||||
@@ -943,7 +929,7 @@ export const DashboardPage = () => {
|
||||
<div className="w-full pb-1">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={ProjectPermissionSub.Folders}
|
||||
a={ProjectPermissionSub.Secrets}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
@@ -962,7 +948,7 @@ export const DashboardPage = () => {
|
||||
<div className="w-full">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.SecretImports, {
|
||||
a={subject(ProjectPermissionSub.Secrets, {
|
||||
environment,
|
||||
secretPath
|
||||
})}
|
||||
|
||||
@@ -56,7 +56,7 @@ export const FolderSection = memo(
|
||||
<div className="duration-0 flex h-10 w-16 items-center justify-end space-x-2.5 overflow-hidden border-l border-mineshaft-600 transition-all">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={subject(ProjectPermissionSub.Folders, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<div className="opacity-0 group-hover:opacity-100">
|
||||
@@ -77,7 +77,7 @@ export const FolderSection = memo(
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.Folders, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<div className="opacity-0 group-hover:opacity-100">
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ export const SecretImportItem = ({
|
||||
</div>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={subject(ProjectPermissionSub.SecretImports, { environment, secretPath })}
|
||||
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<div className="opacity-0 group-hover:opacity-100">
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import { useWorkspace } from "@app/context";
|
||||
import { TFormSchema } from "./ProjectRoleModifySection.utils";
|
||||
|
||||
type Props = {
|
||||
formName: "secrets" | "folders" | "secret-imports";
|
||||
formName: "secrets";
|
||||
isNonEditable?: boolean;
|
||||
setValue: UseFormSetValue<TFormSchema>;
|
||||
control: Control<TFormSchema>;
|
||||
|
||||
+1
-25
@@ -6,9 +6,7 @@ import {
|
||||
faArrowLeft,
|
||||
faBook,
|
||||
faCog,
|
||||
faFolder,
|
||||
faKey,
|
||||
faLink,
|
||||
faLock,
|
||||
faMagnifyingGlass,
|
||||
faNetworkWired,
|
||||
@@ -244,32 +242,10 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
setValue={setValue}
|
||||
icon={faLock}
|
||||
title="Secrets"
|
||||
subtitle="Secret management control"
|
||||
subtitle="Create, modify and remove secrets, folders and secret imports"
|
||||
formName="secrets"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<MultiEnvProjectPermission
|
||||
isNonEditable={isNonEditable}
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
icon={faFolder}
|
||||
title="Folders"
|
||||
subtitle="Folder management control"
|
||||
formName="folders"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<MultiEnvProjectPermission
|
||||
isNonEditable={isNonEditable}
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
icon={faLink}
|
||||
title="Secret Imports"
|
||||
subtitle="Secret import management control"
|
||||
formName="secret-imports"
|
||||
/>
|
||||
</div>
|
||||
<div key="permission-ws">
|
||||
<WsProjectPermission
|
||||
control={control}
|
||||
|
||||
+4
-11
@@ -23,7 +23,6 @@ const multiEnvPermissionSchema = z
|
||||
.optional();
|
||||
|
||||
const PERMISSION_ACTIONS = ["read", "create", "edit", "delete"] as const;
|
||||
const MULTI_ENV_KEY = ["secrets", "folders", "secret-imports"] as const;
|
||||
|
||||
export const formSchema = z.object({
|
||||
name: z.string().trim(),
|
||||
@@ -32,8 +31,6 @@ export const formSchema = z.object({
|
||||
permissions: z
|
||||
.object({
|
||||
secrets: z.record(multiEnvPermissionSchema).optional(),
|
||||
folders: z.record(multiEnvPermissionSchema).optional(),
|
||||
"secret-imports": z.record(multiEnvPermissionSchema).optional(),
|
||||
member: generalPermissionSchema,
|
||||
role: generalPermissionSchema,
|
||||
integrations: generalPermissionSchema,
|
||||
@@ -92,7 +89,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
const { subject, action } = permission;
|
||||
if (!formVal?.[subject]) formVal[subject] = {};
|
||||
|
||||
if (["secrets", "folders", "secret-imports"].includes(subject)) {
|
||||
if (subject === "secrets") {
|
||||
multiEnvApi2Form(formVal[subject], permission);
|
||||
} else {
|
||||
// everything else follows same pattern
|
||||
@@ -107,7 +104,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
||||
const multiEnvForm2Api = (
|
||||
permissions: TProjectPermission[],
|
||||
formVal: Record<string, { secretPath?: string } & { [key: string]: boolean }>,
|
||||
subject: (typeof MULTI_ENV_KEY)[number]
|
||||
subject: "secrets"
|
||||
) => {
|
||||
if (!formVal) return;
|
||||
|
||||
@@ -147,12 +144,8 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
|
||||
// other than workspace everything else follows same
|
||||
// if in future there is a different follow the above on how workspace is done
|
||||
Object.entries(formVal || {}).forEach(([rule, actions]) => {
|
||||
if (MULTI_ENV_KEY.includes(rule as (typeof MULTI_ENV_KEY)[number])) {
|
||||
multiEnvForm2Api(
|
||||
permissions,
|
||||
JSON.parse(JSON.stringify(actions || {})),
|
||||
rule as (typeof MULTI_ENV_KEY)[number]
|
||||
);
|
||||
if (rule === "secrets") {
|
||||
multiEnvForm2Api(permissions, JSON.parse(JSON.stringify(actions || {})), rule);
|
||||
} else {
|
||||
Object.entries(actions).forEach(([action, isAllowed]) => {
|
||||
if (isAllowed) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { subject } from "@casl/ability";
|
||||
import {
|
||||
faArrowDown,
|
||||
faArrowUp,
|
||||
@@ -30,13 +29,7 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useOrganization,
|
||||
useProjectPermission,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import { useOrganization, useWorkspace } from "@app/context";
|
||||
import {
|
||||
useCreateSecretV3,
|
||||
useDeleteSecretV3,
|
||||
@@ -82,7 +75,6 @@ export const SecretOverviewPage = () => {
|
||||
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
|
||||
const [searchFilter, setSearchFilter] = useState("");
|
||||
const secretPath = (router.query?.secretPath as string) || "/";
|
||||
const permission = useProjectPermission();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWorkspaceLoading && !workspaceId && router.isReady) {
|
||||
@@ -90,22 +82,7 @@ export const SecretOverviewPage = () => {
|
||||
}
|
||||
}, [isWorkspaceLoading, workspaceId, router.isReady]);
|
||||
|
||||
const userAvailableEnvs =
|
||||
currentWorkspace?.environments?.filter(
|
||||
({ slug }) =>
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { environment: slug, secretPath })
|
||||
) ||
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Folders, { environment: slug, secretPath })
|
||||
) ||
|
||||
permission.can(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.SecretImports, { environment: slug, secretPath })
|
||||
)
|
||||
) || [];
|
||||
const userAvailableEnvs = currentWorkspace?.environments || [];
|
||||
|
||||
const {
|
||||
data: secrets,
|
||||
|
||||
Reference in New Issue
Block a user