mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 19:28:16 +00:00
feat(rbac): fixed merge conflicts and resolved some more issues with permission checks
This commit is contained in:
@@ -201,14 +201,6 @@ export const logout = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCommonPasswords = async (req: Request, res: Response) => {
|
|
||||||
const commonPasswords = fs
|
|
||||||
.readFileSync(path.resolve(__dirname, "../../data/" + "common_passwords.txt"), "utf8")
|
|
||||||
.split("\n");
|
|
||||||
|
|
||||||
return res.status(200).send(commonPasswords);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const revokeAllSessions = async (req: Request, res: Response) => {
|
export const revokeAllSessions = async (req: Request, res: Response) => {
|
||||||
await TokenVersion.updateMany(
|
await TokenVersion.updateMany(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ export const getUserWorkspacePermissions = async (req: Request, res: Response) =
|
|||||||
params: { workspaceId }
|
params: { workspaceId }
|
||||||
} = await validateRequest(GetUserProjectPermission, req);
|
} = await validateRequest(GetUserProjectPermission, req);
|
||||||
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
data: {
|
data: {
|
||||||
permissions: packRules(permission.rules)
|
permissions: packRules(permission.rules)
|
||||||
|
|||||||
@@ -77,16 +77,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||||
|
|
||||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
|
||||||
await validateServiceTokenDataClientForWorkspace({
|
|
||||||
serviceTokenData: req.authData.authPayload,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (secretPath) {
|
if (secretPath) {
|
||||||
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
}
|
}
|
||||||
@@ -100,7 +90,15 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.user?._id) {
|
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||||
|
await validateServiceTokenDataClientForWorkspace({
|
||||||
|
serviceTokenData: req.authData.authPayload,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
requiredPermissions: [PERMISSION_WRITE_SECRETS]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
|
|||||||
@@ -44,6 +44,4 @@ const membershipOrgSchema = new Schema(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const MembershipOrg = model<IMembershipOrg>("MembershipOrg", membershipOrgSchema);
|
export const MembershipOrg = model<IMembershipOrg>("MembershipOrg", membershipOrgSchema);
|
||||||
|
|
||||||
export default MembershipOrg;
|
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ router.post(
|
|||||||
authController.checkAuth
|
authController.checkAuth
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get("/common-passwords", authLimiter, authController.getCommonPasswords);
|
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
// TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
|
// TODO endpoint: deprecate (moved to DELETE v2/users/me/sessions)
|
||||||
"/sessions",
|
"/sessions",
|
||||||
|
|||||||
@@ -93,11 +93,11 @@ router.get(
|
|||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:integrationAuthId/teamcity/build-configs",
|
"/:integrationAuthId/teamcity/build-configs",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
|
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
|
|||||||
@@ -219,7 +219,6 @@ export const getUserProjectPermissions = async (userId: string, workspaceId: str
|
|||||||
}>("customRole")
|
}>("customRole")
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
console.log(membership, userId, workspaceId);
|
|
||||||
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
||||||
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { MembershipNotFoundError } from "../utils/errors";
|
|||||||
import { AuthData } from "../interfaces/middleware";
|
import { AuthData } from "../interfaces/middleware";
|
||||||
import { ActorType } from "../ee/models";
|
import { ActorType } from "../ee/models";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate authenticated clients for membership with id [membershipId] based
|
* Validate authenticated clients for membership with id [membershipId] based
|
||||||
@@ -66,7 +65,7 @@ export const DeleteMembershipV1 = z.object({
|
|||||||
|
|
||||||
export const ChangeMembershipRoleV1 = z.object({
|
export const ChangeMembershipRoleV1 = z.object({
|
||||||
body: z.object({
|
body: z.object({
|
||||||
role: z.enum([ADMIN, VIEWER, MEMBER, CUSTOM])
|
role: z.string().trim()
|
||||||
}),
|
}),
|
||||||
params: z.object({ membershipId: z.string().trim() })
|
params: z.object({ membershipId: z.string().trim() })
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const CreateRoleSchema = z.object({
|
|||||||
.object({
|
.object({
|
||||||
subject: z.string(),
|
subject: z.string(),
|
||||||
action: z.string(),
|
action: z.string(),
|
||||||
condition: z.record(z.union([z.string(), z.number()])).optional()
|
conditions: z.record(z.union([z.string(), z.number()])).optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
@@ -31,7 +31,7 @@ export const UpdateRoleSchema = z.object({
|
|||||||
.object({
|
.object({
|
||||||
subject: z.string(),
|
subject: z.string(),
|
||||||
action: z.string(),
|
action: z.string(),
|
||||||
condition: z.record(z.union([z.string(), z.number()])).optional()
|
conditions: z.record(z.union([z.string(), z.number()])).optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ export type TRole<T extends string | undefined> = {
|
|||||||
export type TPermission = TWorkspacePermission | TGeneralPermission;
|
export type TPermission = TWorkspacePermission | TGeneralPermission;
|
||||||
|
|
||||||
type TGeneralPermission = {
|
type TGeneralPermission = {
|
||||||
condition?: Record<string, any>;
|
conditions?: Record<string, any>;
|
||||||
action: "read" | "edit" | "create" | "delete";
|
action: "read" | "edit" | "create" | "delete";
|
||||||
subject: "member" | "role" | "incident-contact" | "sso" | "billing" | "settings";
|
subject: "member" | "role" | "incident-contact" | "sso" | "billing" | "settings";
|
||||||
};
|
};
|
||||||
|
|
||||||
type TWorkspacePermission = {
|
type TWorkspacePermission = {
|
||||||
condition?: Record<string, any>;
|
conditions?: Record<string, any>;
|
||||||
action: "read" | "create";
|
action: "read" | "create";
|
||||||
subject: "workspace";
|
subject: "workspace";
|
||||||
};
|
};
|
||||||
@@ -32,7 +32,7 @@ type TWorkspacePermission = {
|
|||||||
export type TProjectPermission = TProjectGeneralPermission | TProjectWorkspacePermission;
|
export type TProjectPermission = TProjectGeneralPermission | TProjectWorkspacePermission;
|
||||||
|
|
||||||
type TProjectGeneralPermission = {
|
type TProjectGeneralPermission = {
|
||||||
condition?: Record<string, any>;
|
conditions?: Record<string, any>;
|
||||||
action: "read" | "edit" | "create" | "delete";
|
action: "read" | "edit" | "create" | "delete";
|
||||||
subject:
|
subject:
|
||||||
| "member"
|
| "member"
|
||||||
@@ -46,7 +46,7 @@ type TProjectGeneralPermission = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TProjectWorkspacePermission = {
|
type TProjectWorkspacePermission = {
|
||||||
condition?: Record<string, any>;
|
conditions?: Record<string, any>;
|
||||||
action: "delete" | "edit";
|
action: "delete" | "edit";
|
||||||
subject: "workspace";
|
subject: "workspace";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
import { FormProvider, useFieldArray, useForm } from "react-hook-form";
|
import { FormProvider, useFieldArray, useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { subject } from "@casl/ability";
|
||||||
import {
|
import {
|
||||||
closestCenter,
|
closestCenter,
|
||||||
DndContext,
|
DndContext,
|
||||||
@@ -25,6 +26,7 @@ import {
|
|||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faFileImport,
|
faFileImport,
|
||||||
faFolderPlus,
|
faFolderPlus,
|
||||||
|
faLock,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
faPlus
|
faPlus
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
@@ -60,6 +62,7 @@ import {
|
|||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useOrganization,
|
useOrganization,
|
||||||
|
useProjectPermission,
|
||||||
useSubscription,
|
useSubscription,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
@@ -79,7 +82,6 @@ import {
|
|||||||
useGetSecretVersion,
|
useGetSecretVersion,
|
||||||
useGetSnapshotSecrets,
|
useGetSnapshotSecrets,
|
||||||
useGetUserAction,
|
useGetUserAction,
|
||||||
useGetUserWsEnvironments,
|
|
||||||
useGetUserWsKey,
|
useGetUserWsKey,
|
||||||
useGetWorkspaceSecretSnapshots,
|
useGetWorkspaceSecretSnapshots,
|
||||||
useGetWsSnapshotCount,
|
useGetWsSnapshotCount,
|
||||||
@@ -139,6 +141,7 @@ export const DashboardPage = withProjectPermission(
|
|||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const envQuery = router.query.env as string;
|
const envQuery = router.query.env as string;
|
||||||
|
const permission = useProjectPermission();
|
||||||
|
|
||||||
const secretContainer = useRef<HTMLDivElement | null>(null);
|
const secretContainer = useRef<HTMLDivElement | null>(null);
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||||
@@ -181,17 +184,6 @@ export const DashboardPage = withProjectPermission(
|
|||||||
const { data: userAction } = useGetUserAction(USER_ACTION_PUSH);
|
const { data: userAction } = useGetUserAction(USER_ACTION_PUSH);
|
||||||
const hasUserPushed = Boolean(userAction);
|
const hasUserPushed = Boolean(userAction);
|
||||||
|
|
||||||
const { data: wsEnv, isLoading: isEnvListLoading } = useGetUserWsEnvironments({
|
|
||||||
workspaceId,
|
|
||||||
onSuccess: (data) => {
|
|
||||||
// get an env with one of the access available
|
|
||||||
const env = data.find(({ isReadDenied, isWriteDenied }) => !isWriteDenied || !isReadDenied);
|
|
||||||
if (env && data?.map((wsenv) => wsenv.slug).includes(envQuery)) {
|
|
||||||
setSelectedEnv(data?.filter((dp) => dp.slug === envQuery)[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: secretVersion } = useGetSecretVersion({
|
const { data: secretVersion } = useGetSecretVersion({
|
||||||
limit: 10,
|
limit: 10,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
@@ -267,6 +259,24 @@ export const DashboardPage = withProjectPermission(
|
|||||||
folderId
|
folderId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const secretPath = `/${(folderData?.dir || [])
|
||||||
|
?.filter(({ name }) => name !== "root")
|
||||||
|
.join("/")}`;
|
||||||
|
|
||||||
|
const userAvailableEnvs = currentWorkspace?.environments?.filter(({ slug }) =>
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: slug, secretPath })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && currentWorkspace) {
|
||||||
|
const env = userAvailableEnvs?.find(({ slug }) => slug === envQuery);
|
||||||
|
if (env) setSelectedEnv(env);
|
||||||
|
}
|
||||||
|
}, [isLoading, workspaceId, userAvailableEnvs]);
|
||||||
|
|
||||||
// This is for dnd-kit. As react-query state mutation async
|
// 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
|
// This will act as a placeholder to avoid a glitching animation on dropping items
|
||||||
const [items, setItems] = useState<
|
const [items, setItems] = useState<
|
||||||
@@ -316,11 +326,19 @@ export const DashboardPage = withProjectPermission(
|
|||||||
reset
|
reset
|
||||||
} = method;
|
} = method;
|
||||||
const { fields, prepend, append, remove } = useFieldArray({ control, name: "secrets" });
|
const { fields, prepend, append, remove } = useFieldArray({ control, name: "secrets" });
|
||||||
const isReadOnly = selectedEnv?.isWriteDenied;
|
|
||||||
const isAddOnly = selectedEnv?.isReadDenied && !selectedEnv?.isWriteDenied;
|
const isReadOnly =
|
||||||
const canDoRollback = !isReadOnly && !isAddOnly;
|
permission.can(
|
||||||
const isSubmitDisabled =
|
ProjectPermissionActions.Read,
|
||||||
isReadOnly || (!isRollbackMode && !isDirty) || isAddOnly || isSubmitting;
|
subject(ProjectPermissionSub.Secrets, { environment: selectedEnvSlug })
|
||||||
|
) &&
|
||||||
|
permission.cannot(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: selectedEnvSlug })
|
||||||
|
);
|
||||||
|
|
||||||
|
const canDoRollback = !isReadOnly;
|
||||||
|
const isSubmitDisabled = isReadOnly || (!isRollbackMode && !isDirty) || isSubmitting;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isSnapshotChanging && Boolean(snapshotId)) {
|
if (!isSnapshotChanging && Boolean(snapshotId)) {
|
||||||
@@ -437,14 +455,12 @@ export const DashboardPage = withProjectPermission(
|
|||||||
}
|
}
|
||||||
// just closing this if save is triggered from drawer
|
// just closing this if save is triggered from drawer
|
||||||
handlePopUpClose("secretDetails");
|
handlePopUpClose("secretDetails");
|
||||||
// when add only mode remove rest of things not created
|
|
||||||
const sec = isAddOnly ? userSec.filter(({ _id }) => !_id) : userSec;
|
|
||||||
// encrypt and format the secrets to batch api format
|
// encrypt and format the secrets to batch api format
|
||||||
// requests = [ {method:"", secret:""} ]
|
// requests = [ {method:"", secret:""} ]
|
||||||
const batchedSecret = transformSecretsToBatchSecretReq(
|
const batchedSecret = transformSecretsToBatchSecretReq(
|
||||||
deletedSecretIds.current,
|
deletedSecretIds.current,
|
||||||
latestFileKey,
|
latestFileKey,
|
||||||
sec,
|
userSec,
|
||||||
secrets?.secrets
|
secrets?.secrets
|
||||||
);
|
);
|
||||||
// type check
|
// type check
|
||||||
@@ -486,7 +502,8 @@ export const DashboardPage = withProjectPermission(
|
|||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
if (!window.confirm(leaveConfirmDefaultMessage)) return;
|
if (!window.confirm(leaveConfirmDefaultMessage)) return;
|
||||||
}
|
}
|
||||||
const env = wsEnv?.find((el) => el.slug === slug);
|
|
||||||
|
const env = userAvailableEnvs?.find((el) => el.slug === slug);
|
||||||
if (env) setSelectedEnv(env);
|
if (env) setSelectedEnv(env);
|
||||||
const query: Record<string, string> = { ...router.query, env: slug };
|
const query: Record<string, string> = { ...router.query, env: slug };
|
||||||
delete query.folderId;
|
delete query.folderId;
|
||||||
@@ -748,7 +765,7 @@ export const DashboardPage = withProjectPermission(
|
|||||||
const isSecretImportEmpty = !secretImportCfg?.imports?.length;
|
const isSecretImportEmpty = !secretImportCfg?.imports?.length;
|
||||||
const isEmptyPage = isFoldersEmpty && isSecretEmpty && isSecretImportEmpty;
|
const isEmptyPage = isFoldersEmpty && isSecretEmpty && isSecretImportEmpty;
|
||||||
|
|
||||||
if (isSecretsLoading || isEnvListLoading) {
|
if (isSecretsLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex h-1/2 w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto flex h-1/2 w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
||||||
@@ -756,9 +773,29 @@ export const DashboardPage = withProjectPermission(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userAvailableEnvs = wsEnv?.filter(
|
if (
|
||||||
({ isReadDenied, isWriteDenied }) => !isReadDenied || !isWriteDenied
|
permission.cannot(
|
||||||
);
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: envQuery, secretPath })
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className="container h-full mx-auto flex justify-center items-center">
|
||||||
|
<div className="rounded-md bg-mineshaft-800 text-bunker-300 p-16 flex space-x-12 items-end">
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon={faLock} size="6x" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-4xl font-medium mb-2">Permission Denied</div>
|
||||||
|
<div className="text-sm">
|
||||||
|
You do not have permission to this page. <br /> Kindly contact your organization
|
||||||
|
administrator
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto h-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto h-full px-6 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
@@ -768,7 +805,7 @@ export const DashboardPage = withProjectPermission(
|
|||||||
<NavHeader
|
<NavHeader
|
||||||
pageName={t("dashboard.title")}
|
pageName={t("dashboard.title")}
|
||||||
currentEnv={
|
currentEnv={
|
||||||
userAvailableEnvs?.filter((envir) => envir.slug === envQuery)[0].name || ""
|
userAvailableEnvs?.filter((envir) => envir.slug === envQuery)?.[0]?.name || ""
|
||||||
}
|
}
|
||||||
isFolderMode
|
isFolderMode
|
||||||
folders={folderData?.dir}
|
folders={folderData?.dir}
|
||||||
@@ -869,7 +906,7 @@ export const DashboardPage = withProjectPermission(
|
|||||||
}}
|
}}
|
||||||
leftIcon={<FontAwesomeIcon icon={faCodeCommit} />}
|
leftIcon={<FontAwesomeIcon icon={faCodeCommit} />}
|
||||||
isLoading={isLoadingSnapshotCount}
|
isLoading={isLoadingSnapshotCount}
|
||||||
isDisabled={!canDoRollback && !isAllowed}
|
isDisabled={!canDoRollback || !isAllowed}
|
||||||
className="h-10"
|
className="h-10"
|
||||||
>
|
>
|
||||||
{snapshotCount} Commits
|
{snapshotCount} Commits
|
||||||
@@ -1037,7 +1074,6 @@ export const DashboardPage = withProjectPermission(
|
|||||||
secUniqId={_id}
|
secUniqId={_id}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
isRollbackMode={isRollbackMode}
|
isRollbackMode={isRollbackMode}
|
||||||
isAddOnly={isAddOnly}
|
|
||||||
index={index}
|
index={index}
|
||||||
searchTerm={searchFilter}
|
searchTerm={searchFilter}
|
||||||
onSecretDelete={onSecretDelete}
|
onSecretDelete={onSecretDelete}
|
||||||
|
|||||||
+8
-9
@@ -58,14 +58,14 @@ const multiEnvApi2Form = (
|
|||||||
formVal: TFormSchema["permissions"]["secrets"],
|
formVal: TFormSchema["permissions"]["secrets"],
|
||||||
permission: TProjectPermission
|
permission: TProjectPermission
|
||||||
) => {
|
) => {
|
||||||
const isCustomRule = Boolean(permission?.condition?.slug);
|
const isCustomRule = Boolean(permission?.conditions?.environment);
|
||||||
// full access
|
// full access
|
||||||
if (isCustomRule && formVal && !formVal?.custom) {
|
if (isCustomRule && formVal && !formVal?.custom) {
|
||||||
formVal.custom = { read: true, edit: true, delete: true, create: true };
|
formVal.custom = { read: true, edit: true, delete: true, create: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretEnv = permission?.condition?.slug || "all";
|
const secretEnv = permission?.conditions?.environment || "all";
|
||||||
const secretPath = permission?.condition?.secretPath;
|
const secretPath = permission?.conditions?.secretPath;
|
||||||
// initialize
|
// initialize
|
||||||
if (formVal && !formVal?.[secretEnv]) {
|
if (formVal && !formVal?.[secretEnv]) {
|
||||||
formVal[secretEnv] = { read: false, edit: false, create: false, delete: false, secretPath };
|
formVal[secretEnv] = { read: false, edit: false, create: false, delete: false, secretPath };
|
||||||
@@ -94,7 +94,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
|
|
||||||
permissions.forEach((permission) => {
|
permissions.forEach((permission) => {
|
||||||
if (["secrets", "folders", "secret-imports"].includes(permission.subject)) {
|
if (["secrets", "folders", "secret-imports"].includes(permission.subject)) {
|
||||||
multiEnvApi2Form(formVal?.secrets, permission);
|
multiEnvApi2Form(formVal[permission.subject], permission);
|
||||||
} else {
|
} else {
|
||||||
// everything else follows same pattern
|
// everything else follows same pattern
|
||||||
// formVal[settings][read | write] = true
|
// formVal[settings][read | write] = true
|
||||||
@@ -134,11 +134,10 @@ const multiEnvForm2Api = (
|
|||||||
actions.forEach((action) => {
|
actions.forEach((action) => {
|
||||||
// if not full access for an action
|
// if not full access for an action
|
||||||
if (!formVal?.all?.[action] && action !== "secretPath" && formVal?.[slug]?.[action]) {
|
if (!formVal?.all?.[action] && action !== "secretPath" && formVal?.[slug]?.[action]) {
|
||||||
permissions.push({
|
const conditions: Record<string, unknown> = { environment: slug };
|
||||||
action,
|
if (formVal[slug]?.secretPath) conditions.secretPath = formVal[slug].secretPath;
|
||||||
subject,
|
|
||||||
condition: { slug, secretPath: formVal[slug]?.secretPath }
|
permissions.push({ action, subject, conditions });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ import { useEffect, useRef, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { faArrowDown, faArrowUp, faFolderBlank, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
|
import { subject } from "@casl/ability";
|
||||||
|
import {
|
||||||
|
faArrowDown,
|
||||||
|
faArrowUp,
|
||||||
|
faFolderBlank,
|
||||||
|
faMagnifyingGlass
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
@@ -27,6 +33,7 @@ import {
|
|||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
useOrganization,
|
useOrganization,
|
||||||
|
useProjectPermission,
|
||||||
useWorkspace
|
useWorkspace
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { withProjectPermission } from "@app/hoc";
|
import { withProjectPermission } from "@app/hoc";
|
||||||
@@ -76,6 +83,7 @@ const SecretOverview = () => {
|
|||||||
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
|
const { data: latestFileKey } = useGetUserWsKey(workspaceId);
|
||||||
const [searchFilter, setSearchFilter] = useState("");
|
const [searchFilter, setSearchFilter] = useState("");
|
||||||
const secretPath = router.query?.secretPath as string;
|
const secretPath = router.query?.secretPath as string;
|
||||||
|
const permission = useProjectPermission();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isWorkspaceLoading && !workspaceId && router.isReady) {
|
if (!isWorkspaceLoading && !workspaceId && router.isReady) {
|
||||||
@@ -83,11 +91,13 @@ const SecretOverview = () => {
|
|||||||
}
|
}
|
||||||
}, [isWorkspaceLoading, workspaceId, router.isReady]);
|
}, [isWorkspaceLoading, workspaceId, router.isReady]);
|
||||||
|
|
||||||
const { data: wsEnv, isLoading: isEnvListLoading } = useGetUserWsEnvironments({
|
const userAvailableEnvs =
|
||||||
workspaceId
|
currentWorkspace?.environments?.filter(({ slug }) =>
|
||||||
});
|
permission.can(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
const userAvailableEnvs = wsEnv?.filter(({ isReadDenied }) => !isReadDenied) || [];
|
subject(ProjectPermissionSub.Secrets, { environment: slug, secretPath: secretPath || "/" })
|
||||||
|
)
|
||||||
|
) || [];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: secrets,
|
data: secrets,
|
||||||
@@ -213,7 +223,7 @@ const SecretOverview = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isEnvListLoading) {
|
if (isWorkspaceLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
<div className="container mx-auto flex h-screen w-full items-center justify-center px-8 text-mineshaft-50 dark:[color-scheme:dark]">
|
||||||
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
<img src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" />
|
||||||
@@ -225,9 +235,9 @@ const SecretOverview = () => {
|
|||||||
folders?.some(({ isLoading }) => !isLoading) && secrets?.some(({ isLoading }) => !isLoading)
|
folders?.some(({ isLoading }) => !isLoading) && secrets?.some(({ isLoading }) => !isLoading)
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredSecretNames = secKeys?.filter((name) =>
|
const filteredSecretNames = secKeys
|
||||||
name.toUpperCase().includes(searchFilter.toUpperCase())
|
?.filter((name) => name.toUpperCase().includes(searchFilter.toUpperCase()))
|
||||||
).sort((a, b) => sortDir === "asc" ? a.localeCompare(b) : b.localeCompare(a));
|
.sort((a, b) => (sortDir === "asc" ? a.localeCompare(b) : b.localeCompare(a)));
|
||||||
const filteredFolderNames = folderNames?.filter((name) =>
|
const filteredFolderNames = folderNames?.filter((name) =>
|
||||||
name.toLowerCase().includes(searchFilter.toLowerCase())
|
name.toLowerCase().includes(searchFilter.toLowerCase())
|
||||||
);
|
);
|
||||||
@@ -286,7 +296,12 @@ const SecretOverview = () => {
|
|||||||
<Th className="sticky left-0 z-20 min-w-[20rem] border-b-0 p-0">
|
<Th className="sticky left-0 z-20 min-w-[20rem] border-b-0 p-0">
|
||||||
<div className="flex items-center border-b border-r border-mineshaft-600 px-5 pt-4 pb-3.5">
|
<div className="flex items-center border-b border-r border-mineshaft-600 px-5 pt-4 pb-3.5">
|
||||||
Name
|
Name
|
||||||
<IconButton variant="plain" className="ml-2" ariaLabel="sort" onClick={() => setSortDir(prev => prev === "asc" ? "desc" : "asc")}>
|
<IconButton
|
||||||
|
variant="plain"
|
||||||
|
className="ml-2"
|
||||||
|
ariaLabel="sort"
|
||||||
|
onClick={() => setSortDir((prev) => (prev === "asc" ? "desc" : "asc"))}
|
||||||
|
>
|
||||||
<FontAwesomeIcon icon={sortDir === "asc" ? faArrowDown : faArrowUp} />
|
<FontAwesomeIcon icon={sortDir === "asc" ? faArrowDown : faArrowUp} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user