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
@@ -4,7 +4,10 @@ import ms from "ms";
import { SecretKeyEncoding } from "@app/db/schemas"; import { SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import {
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -73,7 +76,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -146,7 +149,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -220,7 +223,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -285,7 +288,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -321,7 +324,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -3,7 +3,10 @@ import { ForbiddenError, subject } from "@casl/ability";
import { SecretKeyEncoding } from "@app/db/schemas"; import { SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import {
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrderByDirection } from "@app/lib/types"; import { OrderByDirection } from "@app/lib/types";
@@ -77,7 +80,7 @@ export const dynamicSecretServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionDynamicSecretActions.Create,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -146,7 +149,7 @@ export const dynamicSecretServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionDynamicSecretActions.Edit,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -225,7 +228,7 @@ export const dynamicSecretServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionDynamicSecretActions.Edit,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -282,7 +285,7 @@ export const dynamicSecretServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionDynamicSecretActions.Edit,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -328,7 +331,7 @@ export const dynamicSecretServiceFactory = ({
// verify user has access to each env in request // verify user has access to each env in request
environmentSlugs.forEach((environmentSlug) => environmentSlugs.forEach((environmentSlug) =>
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
) )
); );
@@ -364,7 +367,7 @@ export const dynamicSecretServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -410,7 +413,7 @@ export const dynamicSecretServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
); );
@@ -452,7 +455,7 @@ export const dynamicSecretServiceFactory = ({
// verify user has access to each env in request // verify user has access to each env in request
environmentSlugs.forEach((environmentSlug) => environmentSlugs.forEach((environmentSlug) =>
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
) )
); );
@@ -23,6 +23,14 @@ export enum ProjectPermissionCmekActions {
Decrypt = "decrypt" Decrypt = "decrypt"
} }
export enum ProjectPermissionDynamicSecretActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
Lease = "lease"
}
export enum ProjectPermissionSub { export enum ProjectPermissionSub {
Role = "role", Role = "role",
Member = "member", Member = "member",
@@ -88,7 +96,7 @@ export type ProjectPermissionSet =
) )
] ]
| [ | [
ProjectPermissionActions, ProjectPermissionDynamicSecretActions,
( (
| ProjectPermissionSub.DynamicSecrets | ProjectPermissionSub.DynamicSecrets
| (ForcedSubject<ProjectPermissionSub.DynamicSecrets> & DynamicSecretSubjectFields) | (ForcedSubject<ProjectPermissionSub.DynamicSecrets> & DynamicSecretSubjectFields)
@@ -397,7 +405,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
z.object({ z.object({
subject: z.literal(ProjectPermissionSub.DynamicSecrets).describe("The entity this permission pertains to."), subject: z.literal(ProjectPermissionSub.DynamicSecrets).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."), inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionDynamicSecretActions).describe(
"Describe what action an entity can take." "Describe what action an entity can take."
), ),
conditions: SecretConditionV1Schema.describe( conditions: SecretConditionV1Schema.describe(
@@ -414,7 +422,6 @@ const buildAdminPermissionRules = () => {
[ [
ProjectPermissionSub.Secrets, ProjectPermissionSub.Secrets,
ProjectPermissionSub.SecretFolders, ProjectPermissionSub.SecretFolders,
ProjectPermissionSub.DynamicSecrets,
ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretImports,
ProjectPermissionSub.SecretApproval, ProjectPermissionSub.SecretApproval,
ProjectPermissionSub.SecretRotation, ProjectPermissionSub.SecretRotation,
@@ -447,6 +454,17 @@ const buildAdminPermissionRules = () => {
); );
}); });
can(
[
ProjectPermissionDynamicSecretActions.Read,
ProjectPermissionDynamicSecretActions.Edit,
ProjectPermissionDynamicSecretActions.Create,
ProjectPermissionDynamicSecretActions.Delete,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project); can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback); can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms); can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
@@ -489,10 +507,11 @@ const buildMemberPermissionRules = () => {
); );
can( can(
[ [
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Read,
ProjectPermissionActions.Edit, ProjectPermissionDynamicSecretActions.Edit,
ProjectPermissionActions.Create, ProjectPermissionDynamicSecretActions.Create,
ProjectPermissionActions.Delete ProjectPermissionDynamicSecretActions.Delete,
ProjectPermissionDynamicSecretActions.Lease
], ],
ProjectPermissionSub.DynamicSecrets ProjectPermissionSub.DynamicSecrets
); );
@@ -629,7 +648,7 @@ const buildViewerPermissionRules = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
can(ProjectPermissionActions.Read, ProjectPermissionSub.DynamicSecrets); can(ProjectPermissionDynamicSecretActions.Read, ProjectPermissionSub.DynamicSecrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
@@ -3,7 +3,10 @@ import { z } from "zod";
import { SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas"; import { SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import {
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { DASHBOARD } from "@app/lib/api-docs"; import { DASHBOARD } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn"; import { removeTrailingSlash } from "@app/lib/fn";
@@ -195,7 +198,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
const allowedDynamicSecretEnviroments = // filter envs user has access to const allowedDynamicSecretEnviroments = // filter envs user has access to
environments.filter((environment) => environments.filter((environment) =>
permission.can( permission.can(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath }) subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })
) )
); );
@@ -3,5 +3,6 @@ export type { ProjectPermissionSet, TProjectPermission } from "./types";
export { export {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionCmekActions, ProjectPermissionCmekActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub ProjectPermissionSub
} from "./types"; } from "./types";
@@ -7,6 +7,14 @@ export enum ProjectPermissionActions {
Delete = "delete" Delete = "delete"
} }
export enum ProjectPermissionDynamicSecretActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
Lease = "lease"
}
export enum ProjectPermissionCmekActions { export enum ProjectPermissionCmekActions {
Read = "read", Read = "read",
Create = "create", Create = "create",
@@ -108,7 +116,7 @@ export type ProjectPermissionSet =
) )
] ]
| [ | [
ProjectPermissionActions, ProjectPermissionDynamicSecretActions,
( (
| ProjectPermissionSub.DynamicSecrets | ProjectPermissionSub.DynamicSecrets
| (ForcedSubject<ProjectPermissionSub.DynamicSecrets> & DynamicSecretSubjectFields) | (ForcedSubject<ProjectPermissionSub.DynamicSecrets> & DynamicSecretSubjectFields)
+1
View File
@@ -11,6 +11,7 @@ export type { TProjectPermission } from "./ProjectPermissionContext";
export { export {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionCmekActions, ProjectPermissionCmekActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionProvider, ProjectPermissionProvider,
ProjectPermissionSub, ProjectPermissionSub,
useProjectPermission useProjectPermission
@@ -7,6 +7,7 @@ import {
} from "@app/context"; } from "@app/context";
import { import {
PermissionConditionOperators, PermissionConditionOperators,
ProjectPermissionDynamicSecretActions,
TPermissionCondition, TPermissionCondition,
TPermissionConditionOperators TPermissionConditionOperators
} from "@app/context/ProjectPermissionContext/types"; } from "@app/context/ProjectPermissionContext/types";
@@ -28,6 +29,14 @@ const CmekPolicyActionSchema = z.object({
decrypt: z.boolean().optional() 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({ const SecretRollbackPolicyActionSchema = z.object({
read: z.boolean().optional(), read: z.boolean().optional(),
create: z.boolean().optional() create: z.boolean().optional()
@@ -90,7 +99,7 @@ export const formSchema = z.object({
}) })
.array() .array()
.default([]), .default([]),
[ProjectPermissionSub.DynamicSecrets]: GeneralPolicyActionSchema.extend({ [ProjectPermissionSub.DynamicSecrets]: DynamicSecretPolicyActionSchema.extend({
inverted: z.boolean().optional(), inverted: z.boolean().optional(),
conditions: ConditionSchema conditions: ConditionSchema
}) })
@@ -174,9 +183,9 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
if ( if (
[ [
ProjectPermissionSub.Secrets, ProjectPermissionSub.Secrets,
ProjectPermissionSub.DynamicSecrets,
ProjectPermissionSub.SecretFolders, ProjectPermissionSub.SecretFolders,
ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretImports,
ProjectPermissionSub.DynamicSecrets,
ProjectPermissionSub.Member, ProjectPermissionSub.Member,
ProjectPermissionSub.Groups, ProjectPermissionSub.Groups,
ProjectPermissionSub.Identity, ProjectPermissionSub.Identity,
@@ -199,14 +208,33 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
ProjectPermissionSub.Kms ProjectPermissionSub.Kms
].includes(subject) ].includes(subject)
) { ) {
// from above statement we are sure it won't be undefined
if (isConditionalSubjects(subject)) {
if (!formVal[subject]) formVal[subject] = [];
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 canRead = action.includes(ProjectPermissionActions.Read);
const canEdit = action.includes(ProjectPermissionActions.Edit); const canEdit = action.includes(ProjectPermissionActions.Edit);
const canDelete = action.includes(ProjectPermissionActions.Delete); const canDelete = action.includes(ProjectPermissionActions.Delete);
const canCreate = action.includes(ProjectPermissionActions.Create); 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({ formVal[subject]!.push({
read: canRead, read: canRead,
create: canCreate, create: canCreate,
@@ -215,9 +243,15 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [], conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted inverted
}); });
}
} else { } else {
// deduplicate multiple rules for other policies // deduplicate multiple rules for other policies
// because they don't have condition it doesn't make sense for multiple rules // 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 (!formVal[subject]) formVal[subject] = [{}];
if (canRead) formVal[subject as ProjectPermissionSub.Member]![0].read = true; if (canRead) formVal[subject as ProjectPermissionSub.Member]![0].read = true;
if (canEdit) formVal[subject as ProjectPermissionSub.Member]![0].edit = true; if (canEdit) formVal[subject as ProjectPermissionSub.Member]![0].edit = true;
@@ -317,7 +351,7 @@ export type TProjectPermissionObject = {
label: string; label: string;
value: keyof Omit< value: keyof Omit<
NonNullable<NonNullable<TFormSchema["permissions"]>[K]>[number], NonNullable<NonNullable<TFormSchema["permissions"]>[K]>[number],
"conditions" "conditions" | "inverted"
>; >;
}[]; }[];
}; };
@@ -357,7 +391,8 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
{ label: "Read", value: "read" }, { label: "Read", value: "read" },
{ label: "Create", value: "create" }, { label: "Create", value: "create" },
{ label: "Modify", value: "edit" }, { label: "Modify", value: "edit" },
{ label: "Remove", value: "delete" } { label: "Remove", value: "delete" },
{ label: "Lease", value: "lease" }
] ]
}, },
[ProjectPermissionSub.Cmek]: { [ProjectPermissionSub.Cmek]: {
@@ -12,6 +12,7 @@ import { PermissionDeniedBanner } from "@app/components/permissions";
import { ContentLoader, Pagination } from "@app/components/v2"; import { ContentLoader, Pagination } from "@app/components/v2";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub, ProjectPermissionSub,
useProjectPermission, useProjectPermission,
useWorkspace useWorkspace
@@ -93,7 +94,7 @@ export const SecretMainPage = () => {
); );
const canReadDynamicSecret = permission.can( const canReadDynamicSecret = permission.can(
ProjectPermissionActions.Read, ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath }) subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })
); );
@@ -45,7 +45,12 @@ import {
Tooltip, Tooltip,
UpgradePlanModal UpgradePlanModal
} from "@app/components/v2"; } 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 { usePopUp } from "@app/hooks";
import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api"; import { useCreateFolder, useDeleteSecretBatch, useMoveSecrets } from "@app/hooks/api";
import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries"; import { fetchProjectSecrets } from "@app/hooks/api/secrets/queries";
@@ -482,8 +487,8 @@ export const ActionBar = ({
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Create} I={ProjectPermissionDynamicSecretActions.Create}
a={subject(ProjectPermissionSub.Secrets, { a={subject(ProjectPermissionSub.DynamicSecrets, {
environment, environment,
secretPath, secretPath,
secretName: "*", secretName: "*",
@@ -26,7 +26,7 @@ import {
Tooltip, Tooltip,
Tr Tr
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useGetDynamicSecretLeases, useRevokeDynamicSecretLease } from "@app/hooks/api"; import { useGetDynamicSecretLeases, useRevokeDynamicSecretLease } from "@app/hooks/api";
import { DynamicSecretLeaseStatus } from "@app/hooks/api/dynamicSecretLease/types"; import { DynamicSecretLeaseStatus } from "@app/hooks/api/dynamicSecretLease/types";
@@ -139,7 +139,7 @@ export const DynamicSecretLease = ({
<Td> <Td>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })} a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip renderTooltip
allowedLabel="Renew" allowedLabel="Renew"
@@ -158,7 +158,7 @@ export const DynamicSecretLease = ({
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })} a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip renderTooltip
allowedLabel="Delete" allowedLabel="Delete"
@@ -178,7 +178,7 @@ export const DynamicSecretLease = ({
</ProjectPermissionCan> </ProjectPermissionCan>
{status === DynamicSecretLeaseStatus.FailedDeletion && ( {status === DynamicSecretLeaseStatus.FailedDeletion && (
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, { a={subject(ProjectPermissionSub.DynamicSecrets, {
environment, environment,
secretPath secretPath
@@ -211,9 +211,19 @@ export const DynamicSecretLease = ({
</TableContainer> </TableContainer>
{!isLeaseLoading && Boolean(leases?.length) && ( {!isLeaseLoading && Boolean(leases?.length) && (
<div className="mt-6 flex items-center space-x-4"> <div className="mt-6 flex items-center space-x-4">
<Button onClick={onClickNewLease} size="xs"> <ProjectPermissionCan
I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, {
environment,
secretPath
})}
>
{(isAllowed) => (
<Button onClick={onClickNewLease} size="xs" isDisabled={!isAllowed}>
New Lease New Lease
</Button> </Button>
)}
</ProjectPermissionCan>
<Button onClick={onClose} variant="plain" colorSchema="secondary" size="xs"> <Button onClick={onClose} variant="plain" colorSchema="secondary" size="xs">
Close Close
</Button> </Button>
@@ -18,7 +18,7 @@ import {
Tag, Tag,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteDynamicSecret } from "@app/hooks/api"; import { useDeleteDynamicSecret } from "@app/hooks/api";
import { import {
@@ -132,10 +132,17 @@ export const DynamicSecretListView = ({
)} )}
</div> </div>
<div className="flex items-center space-x-2 px-4 py-2"> <div className="flex items-center space-x-2 px-4 py-2">
<ProjectPermissionCan
I={ProjectPermissionDynamicSecretActions.Lease}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Edit"
>
{(isAllowed) => (
<Button <Button
size="xs" size="xs"
className="m-0 py-0.5 px-2 opacity-0 group-hover:opacity-100" className="m-0 py-0.5 px-2 opacity-0 group-hover:opacity-100"
isDisabled={isRevoking} isDisabled={isRevoking || !isAllowed}
onClick={(evt) => { onClick={(evt) => {
evt.stopPropagation(); evt.stopPropagation();
handlePopUpOpen("createDynamicSecretLease", secret); handlePopUpOpen("createDynamicSecretLease", secret);
@@ -143,6 +150,9 @@ export const DynamicSecretListView = ({
> >
Generate Generate
</Button> </Button>
)}
</ProjectPermissionCan>
{secret.status === DynamicSecretStatus.FailedDeletion && ( {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."> <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 <Button
@@ -165,7 +175,7 @@ export const DynamicSecretListView = ({
</div> </div>
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3"> <div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Edit} I={ProjectPermissionDynamicSecretActions.Edit}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })} a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip renderTooltip
allowedLabel="Edit" allowedLabel="Edit"
@@ -187,7 +197,7 @@ export const DynamicSecretListView = ({
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionDynamicSecretActions.Delete}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })} a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip renderTooltip
allowedLabel="Delete" allowedLabel="Delete"