feat: updated frontend project permission logic

This commit is contained in:
=
2024-10-18 00:33:35 +05:30
parent 181821f8f5
commit b6dc6ffc01
29 changed files with 663 additions and 274 deletions
@@ -79,8 +79,23 @@ export const SecretMainPage = () => {
const secretPath = (router.query.secretPath as string) || "/";
const canReadSecret = permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})
);
const canReadSecretImports = permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
);
const canReadDynamicSecret = permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })
);
const canDoReadRollback = permission.can(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback
@@ -91,8 +106,8 @@ export const SecretMainPage = () => {
searchFilter: (router.query.searchFilter as string) || "",
include: {
[RowType.Folder]: true,
[RowType.Import]: canReadSecret,
[RowType.DynamicSecret]: canReadSecret,
[RowType.Import]: canReadSecretImports,
[RowType.DynamicSecret]: canReadDynamicSecret,
[RowType.Secret]: canReadSecret
}
};
@@ -107,12 +122,12 @@ export const SecretMainPage = () => {
...prev,
include: {
[RowType.Folder]: true,
[RowType.Import]: canReadSecret,
[RowType.DynamicSecret]: canReadSecret,
[RowType.Import]: canReadSecretImports,
[RowType.DynamicSecret]: canReadDynamicSecret,
[RowType.Secret]: canReadSecret
}
}));
}, [canReadSecret]);
}, [canReadSecret, canReadSecretImports, canReadDynamicSecret]);
useEffect(() => {
if (
@@ -141,9 +156,9 @@ export const SecretMainPage = () => {
orderBy,
search: debouncedSearchFilter,
orderDirection,
includeImports: canReadSecret && filter.include.import,
includeImports: canReadSecretImports && filter.include.import,
includeFolders: filter.include.folder,
includeDynamicSecrets: canReadSecret && filter.include.dynamic,
includeDynamicSecrets: canReadDynamicSecret && filter.include.dynamic,
includeSecrets: canReadSecret && filter.include.secret,
tags: filter.tags
});
@@ -362,7 +377,7 @@ export const SecretMainPage = () => {
<div className="flex-grow px-4 py-2">Value</div>
</div>
)}
{canReadSecret && imports?.length && (
{canReadSecretImports && imports?.length && (
<SecretImportListView
searchTerm={debouncedSearchFilter}
secretImports={imports}
@@ -382,7 +397,7 @@ export const SecretMainPage = () => {
onNavigateToFolder={handleResetFilter}
/>
)}
{canReadSecret && dynamicSecrets?.length && (
{canReadDynamicSecret && dynamicSecrets?.length && (
<DynamicSecretListView
environment={environment}
projectSlug={projectSlug}
@@ -401,7 +416,10 @@ export const SecretMainPage = () => {
isProtectedBranch={isProtectedBranch}
/>
)}
{!canReadSecret && folders?.length === 0 && <PermissionDeniedBanner />}
{!canReadSecret &&
!canReadDynamicSecret &&
!canReadSecretImports &&
folders?.length === 0 && <PermissionDeniedBanner />}
</div>
</div>
{!isDetailsLoading && totalCount > 0 && (
@@ -45,12 +45,7 @@ import {
Tooltip,
UpgradePlanModal
} from "@app/components/v2";
import {
ProjectPermissionActions,
ProjectPermissionSub,
useProjectPermission,
useSubscription
} from "@app/context";
import { ProjectPermissionActions, 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";
@@ -125,12 +120,6 @@ export const ActionBar = ({
const { reset: resetSelectedSecret } = useSelectedSecretActions();
const isMultiSelectActive = Boolean(Object.keys(selectedSecrets).length);
const { permission } = useProjectPermission();
const shouldCheckFolderPermission = permission.rules.some((rule) =>
(rule.subject as ProjectPermissionSub[]).includes(ProjectPermissionSub.SecretFolders)
);
const handleFolderCreate = async (folderName: string) => {
try {
await createFolder({
@@ -438,7 +427,12 @@ export const ActionBar = ({
<div className="flex items-center">
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})}
>
{(isAllowed) => (
<Button
@@ -469,12 +463,7 @@ export const ActionBar = ({
<div className="flex flex-col space-y-1 p-1.5">
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(
shouldCheckFolderPermission
? ProjectPermissionSub.SecretFolders
: ProjectPermissionSub.Secrets,
{ environment, secretPath }
)}
a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })}
>
{(isAllowed) => (
<Button
@@ -494,7 +483,12 @@ export const ActionBar = ({
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})}
>
{(isAllowed) => (
<Button
@@ -518,7 +512,10 @@ export const ActionBar = ({
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.SecretImports, {
environment,
secretPath
})}
>
{(isAllowed) => (
<Button
@@ -558,7 +555,12 @@ export const ActionBar = ({
</div>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})}
renderTooltip
allowedLabel="Move"
>
@@ -577,7 +579,12 @@ export const ActionBar = ({
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})}
renderTooltip
allowedLabel="Delete"
>
@@ -60,7 +60,6 @@ export const DynamicSecretLease = ({
path: secretPath,
dynamicSecretName
});
const deleteDynamicSecretLease = useRevokeDynamicSecretLease();
@@ -141,7 +140,7 @@ export const DynamicSecretLease = ({
<div className="flex items-center space-x-4">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Renew"
>
@@ -160,7 +159,7 @@ export const DynamicSecretLease = ({
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Delete"
>
@@ -180,7 +179,10 @@ export const DynamicSecretLease = ({
{status === DynamicSecretLeaseStatus.FailedDeletion && (
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.DynamicSecrets, {
environment,
secretPath
})}
renderTooltip
allowedLabel="Force Delete. This action will remove the secret from internal storage, but it will remain in external systems."
>
@@ -166,7 +166,7 @@ export const DynamicSecretListView = ({
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Edit"
>
@@ -188,7 +188,7 @@ export const DynamicSecretListView = ({
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath })}
renderTooltip
allowedLabel="Delete"
>
@@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { DeleteActionModal, IconButton, Modal, ModalContent } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useProjectPermission } from "@app/context";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteFolder, useUpdateFolder } from "@app/hooks/api";
import { TSecretFolder } from "@app/hooks/api/secretFolders/types";
@@ -33,11 +33,6 @@ export const FolderListView = ({
"deleteFolder"
] as const);
const router = useRouter();
const { permission } = useProjectPermission();
const shouldCheckFolderPermission = permission.rules.some((rule) =>
(rule.subject as ProjectPermissionSub[]).includes(ProjectPermissionSub.SecretFolders)
);
const { mutateAsync: updateFolder } = useUpdateFolder();
const { mutateAsync: deleteFolder } = useDeleteFolder();
@@ -126,12 +121,7 @@ export const FolderListView = ({
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-3 py-3">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(
shouldCheckFolderPermission
? ProjectPermissionSub.SecretFolders
: ProjectPermissionSub.Secrets,
{ environment, secretPath }
)}
a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })}
renderTooltip
allowedLabel="Edit"
>
@@ -150,12 +140,7 @@ export const FolderListView = ({
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(
shouldCheckFolderPermission
? ProjectPermissionSub.SecretFolders
: ProjectPermissionSub.Secrets,
{ environment, secretPath }
)}
a={subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })}
renderTooltip
allowedLabel="Delete"
>
@@ -142,7 +142,12 @@ export const CopySecretsFromBoard = ({
<div>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})}
>
{(isAllowed) => (
<Button
@@ -250,7 +250,12 @@ export const SecretDropzone = ({
</div>
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})}
>
{(isAllowed) => (
<input
@@ -287,7 +292,12 @@ export const SecretDropzone = ({
{!isSmaller && (
<ProjectPermissionCan
I={ProjectPermissionActions.Create}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: "*",
secretTags: ["*"]
})}
>
{(isAllowed) => (
<Button
@@ -67,7 +67,7 @@ export const SecretImportItem = ({
isReplicationExpand,
importedSecrets = [],
searchTerm = "",
secretPath,
secretPath = "/",
environment,
secretImport,
onExpandReplicateSecrets: onExpandReplicate
@@ -209,7 +209,7 @@ export const SecretImportItem = ({
{isReplication && (
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.SecretImports, { environment, secretPath })}
renderTooltip
allowedLabel="Resync replicated secrets"
>
@@ -235,7 +235,10 @@ export const SecretImportItem = ({
<div className="flex items-center space-x-4 border-l border-mineshaft-600 px-4 py-2">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.SecretImports, {
environment,
secretPath: secretPath || "/"
})}
renderTooltip
allowedLabel="Change order"
>
@@ -256,7 +259,7 @@ export const SecretImportItem = ({
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.SecretImports, { environment, secretPath })}
renderTooltip
allowedLabel="Delete"
>
@@ -85,15 +85,6 @@ export const SecretDetailSidebar = ({
values: secret
});
const { permission } = useProjectPermission();
const cannotEditSecret = permission.cannot(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
const isReadOnly =
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
) && cannotEditSecret;
const { fields, append, remove } = useFieldArray({
control,
@@ -104,6 +95,27 @@ export const SecretDetailSidebar = ({
(prev, curr) => ({ ...prev, [curr.id]: true }),
{}
);
const selectTagSlugs = selectedTags.map((i) => i.slug);
const cannotEditSecret = permission.cannot(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secret.key,
secretTags: selectTagSlugs
})
);
const isReadOnly =
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secret.key,
secretTags: selectTagSlugs
})
) && cannotEditSecret;
const overrideAction = watch("overrideAction");
const isOverridden =
@@ -194,7 +206,12 @@ export const SecretDetailSidebar = ({
</FormControl>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secret.key,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<Controller
@@ -221,7 +238,12 @@ export const SecretDetailSidebar = ({
<div className="mb-2 border-b border-mineshaft-600 pb-4">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secret.key,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<Switch
@@ -277,7 +299,12 @@ export const SecretDetailSidebar = ({
<DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secret.key,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<DropdownMenuTrigger asChild>
@@ -388,7 +415,12 @@ export const SecretDetailSidebar = ({
render={({ field: { value, onChange, onBlur } }) => (
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secret.key,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<Switch
@@ -450,7 +482,12 @@ export const SecretDetailSidebar = ({
<div className="flex items-center space-x-4">
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secret.key,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<Button
@@ -465,7 +502,12 @@ export const SecretDetailSidebar = ({
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName: secret.key,
secretTags: selectTagSlugs
})}
>
{(isAllowed) => (
<Button colorSchema="danger" isDisabled={!isAllowed} onClick={onDeleteSecret}>
@@ -81,15 +81,6 @@ export const SecretItem = memo(
}: Props) => {
const { currentWorkspace } = useWorkspace();
const { permission } = useProjectPermission();
const isReadOnly =
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
) &&
permission.cannot(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
);
const {
handleSubmit,
@@ -107,6 +98,8 @@ export const SecretItem = memo(
resolver: zodResolver(formSchema)
});
const secretName = watch("key");
const secretReminderRepeatDays = watch("reminderRepeatDays");
const secretReminderNote = watch("reminderNote");
@@ -118,11 +111,33 @@ export const SecretItem = memo(
(prev, curr) => ({ ...prev, [curr.id]: true }),
{}
);
const selectedTagSlugs = selectedTags.map((i) => i.slug);
const { fields, append, remove } = useFieldArray({
control,
name: "tags"
});
const isReadOnly =
permission.can(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName,
secretTags: selectedTagSlugs
})
) &&
permission.cannot(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName,
secretTags: selectedTagSlugs
})
);
const [isSecValueCopied, setIsSecValueCopied] = useToggle(false);
const [createReminderFormOpen, setCreateReminderFormOpen] = useToggle(false);
useEffect(() => {
@@ -309,7 +324,12 @@ export const SecretItem = memo(
<DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName,
secretTags: selectedTagSlugs
})}
>
{(isAllowed) => (
<DropdownMenuTrigger asChild disabled={!isAllowed}>
@@ -384,7 +404,12 @@ export const SecretItem = memo(
</DropdownMenu>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName,
secretTags: selectedTagSlugs
})}
renderTooltip
allowedLabel="Override"
>
@@ -440,7 +465,12 @@ export const SecretItem = memo(
<Popover>
<ProjectPermissionCan
I={ProjectPermissionActions.Edit}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName,
secretTags: selectedTagSlugs
})}
>
{(isAllowed) => (
<PopoverTrigger asChild disabled={!isAllowed}>
@@ -519,7 +549,12 @@ export const SecretItem = memo(
</Tooltip>
<ProjectPermissionCan
I={ProjectPermissionActions.Delete}
a={subject(ProjectPermissionSub.Secrets, { environment, secretPath })}
a={subject(ProjectPermissionSub.Secrets, {
environment,
secretPath,
secretName,
secretTags: selectedTagSlugs
})}
renderTooltip
allowedLabel="Delete"
>