feat: added lease permission for dynamic secret

This commit is contained in:
=
2024-10-18 00:33:36 +05:30
parent 2f691db0a2
commit 7cd83ad945
12 changed files with 166 additions and 67 deletions
@@ -3,5 +3,6 @@ export type { ProjectPermissionSet, TProjectPermission } from "./types";
export {
ProjectPermissionActions,
ProjectPermissionCmekActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub
} from "./types";
@@ -7,6 +7,14 @@ export enum ProjectPermissionActions {
Delete = "delete"
}
export enum ProjectPermissionDynamicSecretActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
Lease = "lease"
}
export enum ProjectPermissionCmekActions {
Read = "read",
Create = "create",
@@ -108,7 +116,7 @@ export type ProjectPermissionSet =
)
]
| [
ProjectPermissionActions,
ProjectPermissionDynamicSecretActions,
(
| ProjectPermissionSub.DynamicSecrets
| (ForcedSubject<ProjectPermissionSub.DynamicSecrets> & DynamicSecretSubjectFields)
+1
View File
@@ -11,6 +11,7 @@ export type { TProjectPermission } from "./ProjectPermissionContext";
export {
ProjectPermissionActions,
ProjectPermissionCmekActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionProvider,
ProjectPermissionSub,
useProjectPermission
@@ -7,6 +7,7 @@ import {
} from "@app/context";
import {
PermissionConditionOperators,
ProjectPermissionDynamicSecretActions,
TPermissionCondition,
TPermissionConditionOperators
} from "@app/context/ProjectPermissionContext/types";
@@ -28,6 +29,14 @@ const CmekPolicyActionSchema = z.object({
decrypt: z.boolean().optional()
});
const DynamicSecretPolicyActionSchema = z.object({
read: z.boolean().optional(),
edit: z.boolean().optional(),
delete: z.boolean().optional(),
create: z.boolean().optional(),
lease: z.boolean().optional()
});
const SecretRollbackPolicyActionSchema = z.object({
read: z.boolean().optional(),
create: z.boolean().optional()
@@ -90,7 +99,7 @@ export const formSchema = z.object({
})
.array()
.default([]),
[ProjectPermissionSub.DynamicSecrets]: GeneralPolicyActionSchema.extend({
[ProjectPermissionSub.DynamicSecrets]: DynamicSecretPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
@@ -174,9 +183,9 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
if (
[
ProjectPermissionSub.Secrets,
ProjectPermissionSub.DynamicSecrets,
ProjectPermissionSub.SecretFolders,
ProjectPermissionSub.SecretImports,
ProjectPermissionSub.DynamicSecrets,
ProjectPermissionSub.Member,
ProjectPermissionSub.Groups,
ProjectPermissionSub.Identity,
@@ -199,25 +208,50 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
ProjectPermissionSub.Kms
].includes(subject)
) {
const canRead = action.includes(ProjectPermissionActions.Read);
const canEdit = action.includes(ProjectPermissionActions.Edit);
const canDelete = action.includes(ProjectPermissionActions.Delete);
const canCreate = action.includes(ProjectPermissionActions.Create);
// from above statement we are sure it won't be undefined
if (isConditionalSubjects(subject)) {
if (!formVal[subject]) formVal[subject] = [];
formVal[subject]!.push({
read: canRead,
create: canCreate,
edit: canEdit,
delete: canDelete,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
if (subject === ProjectPermissionSub.DynamicSecrets) {
const canRead = action.includes(ProjectPermissionDynamicSecretActions.Read);
const canEdit = action.includes(ProjectPermissionDynamicSecretActions.Edit);
const canDelete = action.includes(ProjectPermissionDynamicSecretActions.Delete);
const canCreate = action.includes(ProjectPermissionDynamicSecretActions.Create);
const canLease = action.includes(ProjectPermissionDynamicSecretActions.Lease);
// from above statement we are sure it won't be undefined
formVal[subject]!.push({
read: canRead,
create: canCreate,
edit: canEdit,
delete: canDelete,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted,
lease: canLease
});
} else {
// for other subjects
const canRead = action.includes(ProjectPermissionActions.Read);
const canEdit = action.includes(ProjectPermissionActions.Edit);
const canDelete = action.includes(ProjectPermissionActions.Delete);
const canCreate = action.includes(ProjectPermissionActions.Create);
formVal[subject]!.push({
read: canRead,
create: canCreate,
edit: canEdit,
delete: canDelete,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
}
} else {
// deduplicate multiple rules for other policies
// because they don't have condition it doesn't make sense for multiple rules
const canRead = action.includes(ProjectPermissionActions.Read);
const canEdit = action.includes(ProjectPermissionActions.Edit);
const canDelete = action.includes(ProjectPermissionActions.Delete);
const canCreate = action.includes(ProjectPermissionActions.Create);
if (!formVal[subject]) formVal[subject] = [{}];
if (canRead) formVal[subject as ProjectPermissionSub.Member]![0].read = true;
if (canEdit) formVal[subject as ProjectPermissionSub.Member]![0].edit = true;
@@ -317,7 +351,7 @@ export type TProjectPermissionObject = {
label: string;
value: keyof Omit<
NonNullable<NonNullable<TFormSchema["permissions"]>[K]>[number],
"conditions"
"conditions" | "inverted"
>;
}[];
};
@@ -357,7 +391,8 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
{ label: "Read", value: "read" },
{ label: "Create", value: "create" },
{ label: "Modify", value: "edit" },
{ label: "Remove", value: "delete" }
{ label: "Remove", value: "delete" },
{ label: "Lease", value: "lease" }
]
},
[ProjectPermissionSub.Cmek]: {
@@ -12,6 +12,7 @@ import { PermissionDeniedBanner } from "@app/components/permissions";
import { ContentLoader, Pagination } from "@app/components/v2";
import {
ProjectPermissionActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub,
useProjectPermission,
useWorkspace
@@ -93,7 +94,7 @@ export const SecretMainPage = () => {
);
const canReadDynamicSecret = permission.can(
ProjectPermissionActions.Read,
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })
);
@@ -45,7 +45,12 @@ import {
Tooltip,
UpgradePlanModal
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from "@app/context";
import {
ProjectPermissionActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub,
useSubscription
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
@@ -482,8 +487,8 @@ export const ActionBar = ({
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, {
I={ProjectPermissionDynamicSecretActions.Create}
a={subject(ProjectPermissionSub.DynamicSecrets, {
environment,
secretPath,
secretName: "*",
@@ -26,7 +26,7 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useGetDynamicSecretLeases, useRevokeDynamicSecretLease } from "@app/hooks/api";
import { DynamicSecretLeaseStatus } from "@app/hooks/api/dynamicSecretLease/types";
@@ -139,7 +139,7 @@ export const DynamicSecretLease = ({
<Td>
<div className="flex items-center space-x-4">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Renew"
@@ -158,7 +158,7 @@ export const DynamicSecretLease = ({
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Delete"
@@ -178,7 +178,7 @@ export const DynamicSecretLease = ({
</ProjectPermissionCan>
{status === DynamicSecretLeaseStatus.FailedDeletion && (
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, {
environment,
secretPath
@@ -211,9 +211,19 @@ export const DynamicSecretLease = ({
</TableContainer>
{!isLeaseLoading && Boolean(leases?.length) && (
<div className="mt-6 flex items-center space-x-4">
<Button onClick={onClickNewLease} size="xs">
New Lease
</Button>
<ProjectPermissionCan
I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, {
environment,
secretPath
})}
>
{(isAllowed) => (
<Button onClick={onClickNewLease} size="xs" isDisabled={!isAllowed}>
New Lease
</Button>
)}
</ProjectPermissionCan>
<Button onClick={onClose} variant="plain" colorSchema="secondary" size="xs">
Close
</Button>
@@ -18,7 +18,7 @@ import {
Tag,
Tooltip
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteDynamicSecret } from "@app/hooks/api";
import {
@@ -132,17 +132,27 @@ export const DynamicSecretListView = ({
)}
</div>
<div className="flex items-center space-x-2 px-4 py-2">
<Button
size="xs"
className="m-0 py-0.5 px-2 opacity-0 group-hover:opacity-100"
isDisabled={isRevoking}
onClick={(evt) => {
evt.stopPropagation();
handlePopUpOpen("createDynamicSecretLease", secret);
}}
<ProjectPermissionCan
I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Edit"
>
Generate
</Button>
{(isAllowed) => (
<Button
size="xs"
className="m-0 py-0.5 px-2 opacity-0 group-hover:opacity-100"
isDisabled={isRevoking || !isAllowed}
onClick={(evt) => {
evt.stopPropagation();
handlePopUpOpen("createDynamicSecretLease", secret);
}}
>
Generate
</Button>
)}
</ProjectPermissionCan>
{secret.status === DynamicSecretStatus.FailedDeletion && (
<Tooltip content="This action will remove the secret from internal storage, but it will remain in external systems. Use this option only after you've confirmed that your external leases are handled.">
<Button
@@ -165,7 +175,7 @@ export const DynamicSecretListView = ({
</div>
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
I={ProjectPermissionDynamicSecretActions.Edit}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Edit"
@@ -187,7 +197,7 @@ export const DynamicSecretListView = ({
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
I={ProjectPermissionDynamicSecretActions.Delete}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Delete"