feat: added inverted to project permission

This commit is contained in:
=
2024-10-08 13:34:56 +05:30
parent fc5487396b
commit eb6d5d2fb9
6 changed files with 60 additions and 13 deletions

View File

@@ -39,7 +39,8 @@ export const UnpackedPermissionSchema = z.object({
.transform((el) => (typeof el !== "string" ? el[0] : el))
.optional(),
action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
conditions: z.unknown().optional()
conditions: z.unknown().optional(),
inverted: z.boolean().optional()
});
const unpackPermissions = (permissions: unknown) =>

View File

@@ -309,12 +309,6 @@ const GeneralPermissionSchema = [
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(ProjectPermissionSub.CertificateTemplates).describe("The entity this permission pertains to. "),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(ProjectPermissionSub.PkiAlerts).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
@@ -344,6 +338,7 @@ const GeneralPermissionSchema = [
export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
z.object({
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
),
@@ -353,12 +348,14 @@ export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
}),
z.object({
subject: z.literal(ProjectPermissionSub.SecretFolders).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Read]).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
"Describe what action an entity can take."
)
@@ -369,6 +366,7 @@ export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
z.object({
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
),
@@ -378,6 +376,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
}),
z.object({
subject: z.literal(ProjectPermissionSub.SecretFolders).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
),
@@ -387,6 +386,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
}),
z.object({
subject: z.literal(ProjectPermissionSub.SecretImports).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
),
@@ -396,6 +396,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
}),
z.object({
subject: z.literal(ProjectPermissionSub.DynamicSecrets).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
),

View File

@@ -12,6 +12,7 @@ type Props = {
placeholder?: string;
className?: string;
dropdownContainerClassName?: string;
containerClassName?: string;
isLoading?: boolean;
position?: "item-aligned" | "popper";
isDisabled?: boolean;
@@ -31,12 +32,13 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
isDisabled,
dropdownContainerClassName,
position,
containerClassName,
...props
},
ref
): JSX.Element => {
return (
<div className="flex items-center space-x-2">
<div className={twMerge("flex items-center space-x-2", containerClassName)}>
<SelectPrimitive.Root
{...props}
onValueChange={(value) => {

View File

@@ -40,6 +40,7 @@ export type TPermission = {
export type TProjectPermission = {
conditions?: Record<string, any>;
inverted?: boolean;
action: string | string[];
subject: string | string[];
};

View File

@@ -73,21 +73,25 @@ export const formSchema = z.object({
permissions: z
.object({
[ProjectPermissionSub.Secrets]: GeneralPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
.array()
.default([]),
[ProjectPermissionSub.SecretFolders]: GeneralPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
.array()
.default([]),
[ProjectPermissionSub.SecretImports]: GeneralPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
.array()
.default([]),
[ProjectPermissionSub.DynamicSecrets]: GeneralPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
.array()
@@ -164,7 +168,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
const formVal: Partial<TFormSchema["permissions"]> = {};
permissions.forEach((permission) => {
const { subject: caslSub, action, conditions } = permission;
const { subject: caslSub, action, conditions, inverted } = permission;
const subject = (typeof caslSub === "string" ? caslSub : caslSub[0]) as ProjectPermissionSub;
if (
@@ -208,7 +212,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
create: canCreate,
edit: canEdit,
delete: canDelete,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : []
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
} else {
// deduplicate multiple rules for other policies
@@ -287,7 +292,7 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
Object.entries(formVal || {}).forEach(([subject, rules]) => {
rules.forEach((actions) => {
const caslActions = Object.keys(actions).filter(
(el) => actions?.[el as keyof typeof actions] && el !== "conditions"
(el) => actions?.[el as keyof typeof actions] && el !== "conditions" && el !== "inverted"
);
const caslConditions =
"conditions" in actions
@@ -297,6 +302,7 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
permissions.push({
action: caslActions,
subject,
inverted: (actions as { inverted?: boolean })?.inverted,
conditions: caslConditions
});
});

View File

@@ -1,10 +1,16 @@
import { cloneElement } from "react";
import { Controller, useFieldArray, useFormContext } from "react-hook-form";
import { faChevronDown, faChevronRight, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import {
faChevronDown,
faChevronRight,
faInfoCircle,
faPlus,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { Button, Checkbox, Tag } from "@app/components/v2";
import { Button, Checkbox, Select, SelectItem, Tag, Tooltip } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { useToggle } from "@app/hooks";
@@ -95,6 +101,36 @@ export const GeneralPermissionPolicies = <T extends keyof NonNullable<TFormSchem
})}
</div>
</div>
{isConditionalSubjects(subject) && (
<div className="mt-4 flex w-full items-center text-gray-300">
<div className="w-1/4">Effect</div>
<div className="mr-4 w-1/4">
<Controller
defaultValue={false as any}
name={`permissions.${subject}.${rootIndex}.inverted`}
render={({ field }) => (
<Select
value={String(field.value)}
onValueChange={(val) => field.onChange(val === "true")}
containerClassName="w-full"
className="w-full"
>
<SelectItem value="false">Allow</SelectItem>
<SelectItem value="true">Disallow</SelectItem>
</Select>
)}
/>
</div>
<div>
<Tooltip
asChild
content="Whether to allow or forbid. Forbid rules must be added after allow rules."
>
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="text-gray-400" />
</Tooltip>
</div>
</div>
)}
{children &&
cloneElement(children, {
position: rootIndex