mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 10:26:35 +00:00
Merge pull request #3807 from Infisical/conditional-dynamic-secret-access-display
improvement(access-tree): dynamic secret conditional display
This commit is contained in:
@@ -7,6 +7,7 @@ import React, {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState
|
useState
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
|
||||||
import { ViewMode } from "../types";
|
import { ViewMode } from "../types";
|
||||||
|
|
||||||
@@ -23,8 +24,11 @@ interface AccessTreeProviderProps {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AccessTreeForm = { metadata: { key: string; value: string }[] };
|
||||||
|
|
||||||
export const AccessTreeProvider: React.FC<AccessTreeProviderProps> = ({ children }) => {
|
export const AccessTreeProvider: React.FC<AccessTreeProviderProps> = ({ children }) => {
|
||||||
const [secretName, setSecretName] = useState("");
|
const [secretName, setSecretName] = useState("");
|
||||||
|
const formMethods = useForm<AccessTreeForm>({ defaultValues: { metadata: [] } });
|
||||||
const [viewMode, setViewMode] = useState(ViewMode.Docked);
|
const [viewMode, setViewMode] = useState(ViewMode.Docked);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
@@ -37,7 +41,11 @@ export const AccessTreeProvider: React.FC<AccessTreeProviderProps> = ({ children
|
|||||||
[secretName, setSecretName, viewMode, setViewMode]
|
[secretName, setSecretName, viewMode, setViewMode]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <AccessTreeContext.Provider value={value}>{children}</AccessTreeContext.Provider>;
|
return (
|
||||||
|
<FormProvider {...formMethods}>
|
||||||
|
<AccessTreeContext.Provider value={value}>{children}</AccessTreeContext.Provider>
|
||||||
|
</FormProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAccessTreeContext = (): AccessTreeContextProps => {
|
export const useAccessTreeContext = (): AccessTreeContextProps => {
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
|
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Panel } from "@xyflow/react";
|
import { Panel } from "@xyflow/react";
|
||||||
|
|
||||||
import { Button, FormLabel, IconButton, Input, Select, SelectItem } from "@app/components/v2";
|
import { Button, FormLabel, IconButton, Input, Select, SelectItem } from "@app/components/v2";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
|
import { MetadataForm } from "@app/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/MetadataForm";
|
||||||
|
|
||||||
import { ViewMode } from "../types";
|
import { ViewMode } from "../types";
|
||||||
|
|
||||||
@@ -32,6 +34,7 @@ export const PermissionSimulation = ({
|
|||||||
setSecretName
|
setSecretName
|
||||||
}: TProps) => {
|
}: TProps) => {
|
||||||
const [expand, setExpand] = useState(false);
|
const [expand, setExpand] = useState(false);
|
||||||
|
const { control } = useFormContext();
|
||||||
|
|
||||||
const handlePermissionSimulation = () => {
|
const handlePermissionSimulation = () => {
|
||||||
setExpand(true);
|
setExpand(true);
|
||||||
@@ -139,6 +142,11 @@ export const PermissionSimulation = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{subject === ProjectPermissionSub.DynamicSecrets && (
|
||||||
|
<div>
|
||||||
|
<MetadataForm control={control} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useFormContext, useWatch } from "react-hook-form";
|
||||||
import { MongoAbility, MongoQuery } from "@casl/ability";
|
import { MongoAbility, MongoQuery } from "@casl/ability";
|
||||||
import { Edge, Node, useEdgesState, useNodesState } from "@xyflow/react";
|
import { Edge, Node, useEdgesState, useNodesState } from "@xyflow/react";
|
||||||
|
|
||||||
@@ -7,7 +8,7 @@ import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
|
|||||||
import { useListProjectEnvironmentsFolders } from "@app/hooks/api/secretFolders/queries";
|
import { useListProjectEnvironmentsFolders } from "@app/hooks/api/secretFolders/queries";
|
||||||
import { TSecretFolderWithPath } from "@app/hooks/api/secretFolders/types";
|
import { TSecretFolderWithPath } from "@app/hooks/api/secretFolders/types";
|
||||||
|
|
||||||
import { useAccessTreeContext } from "../components";
|
import { AccessTreeForm, useAccessTreeContext } from "../components";
|
||||||
import { PermissionAccess } from "../types";
|
import { PermissionAccess } from "../types";
|
||||||
import {
|
import {
|
||||||
createBaseEdge,
|
createBaseEdge,
|
||||||
@@ -36,6 +37,8 @@ export const useAccessTree = (
|
|||||||
) => {
|
) => {
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { secretName, setSecretName, setViewMode, viewMode } = useAccessTreeContext();
|
const { secretName, setSecretName, setViewMode, viewMode } = useAccessTreeContext();
|
||||||
|
const { control } = useFormContext<AccessTreeForm>();
|
||||||
|
const metadata = useWatch({ control, name: "metadata" });
|
||||||
const [nodes, setNodes] = useNodesState<Node>([]);
|
const [nodes, setNodes] = useNodesState<Node>([]);
|
||||||
const [edges, setEdges] = useEdgesState<Edge>([]);
|
const [edges, setEdges] = useEdgesState<Edge>([]);
|
||||||
const [subject, setSubject] = useState(ProjectPermissionSub.Secrets);
|
const [subject, setSubject] = useState(ProjectPermissionSub.Secrets);
|
||||||
@@ -168,7 +171,8 @@ export const useAccessTree = (
|
|||||||
environment,
|
environment,
|
||||||
subject,
|
subject,
|
||||||
secretName,
|
secretName,
|
||||||
actionRuleMap
|
actionRuleMap,
|
||||||
|
metadata
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -266,7 +270,8 @@ export const useAccessTree = (
|
|||||||
subject,
|
subject,
|
||||||
secretName,
|
secretName,
|
||||||
setNodes,
|
setNodes,
|
||||||
setEdges
|
setEdges,
|
||||||
|
metadata
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+71
-35
@@ -17,6 +17,27 @@ type Props = {
|
|||||||
access: PermissionAccess;
|
access: PermissionAccess;
|
||||||
} & Pick<ReturnType<typeof createFolderNode>["data"], "actionRuleMap" | "subject">;
|
} & Pick<ReturnType<typeof createFolderNode>["data"], "actionRuleMap" | "subject">;
|
||||||
|
|
||||||
|
type ConditionDisplayProps = {
|
||||||
|
_key: string;
|
||||||
|
operator: string;
|
||||||
|
value: string | string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConditionDisplay = ({ _key: key, value, operator }: ConditionDisplayProps) => {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<span className="font-medium capitalize text-mineshaft-100">{camelCaseToSpaces(key)}</span>{" "}
|
||||||
|
<span className="text-mineshaft-200">
|
||||||
|
{formatedConditionsOperatorNames[operator as PermissionConditionOperators]}
|
||||||
|
</span>{" "}
|
||||||
|
<span className="rounded bg-mineshaft-600 p-0.5 font-mono">
|
||||||
|
{typeof value === "string" ? value : value.join(", ")}
|
||||||
|
</span>
|
||||||
|
.
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const FolderNodeTooltipContent = ({ action, access, actionRuleMap, subject }: Props) => {
|
export const FolderNodeTooltipContent = ({ action, access, actionRuleMap, subject }: Props) => {
|
||||||
let component: ReactElement;
|
let component: ReactElement;
|
||||||
|
|
||||||
@@ -56,43 +77,58 @@ export const FolderNodeTooltipContent = ({ action, access, actionRuleMap, subjec
|
|||||||
{actionRuleMap.map((ruleMap, index) => {
|
{actionRuleMap.map((ruleMap, index) => {
|
||||||
const rule = ruleMap[action];
|
const rule = ruleMap[action];
|
||||||
|
|
||||||
if (
|
if (!rule || !rule.conditions) return null;
|
||||||
!rule ||
|
|
||||||
!rule.conditions ||
|
|
||||||
(!rule.conditions.secretName && !rule.conditions.secretTags)
|
|
||||||
)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return (
|
if (
|
||||||
<li key={`${action}_${index + 1}`}>
|
rule.conditions.secretName ||
|
||||||
<span className={`italic ${rule.inverted ? "text-red" : "text-green"} `}>
|
rule.conditions.secretTags ||
|
||||||
{rule.inverted ? "Forbids" : "Allows"}
|
rule.conditions.metadata
|
||||||
</span>
|
) {
|
||||||
<span> when:</span>
|
return (
|
||||||
{Object.entries(rule.conditions).map(([key, condition]) => (
|
<li key={`${action}_${index + 1}`}>
|
||||||
<ul key={`${action}_${index + 1}_${key}`} className="list-[square] pl-4">
|
<span className="italic">{rule.inverted ? "Forbids" : "Allows"}</span>
|
||||||
{Object.entries(condition as object).map(([operator, value]) => (
|
<span> when:</span>
|
||||||
<li key={`${action}_${index + 1}_${key}_${operator}`}>
|
{Object.entries(rule.conditions).map(([key, condition]) => {
|
||||||
<span className="font-medium capitalize text-mineshaft-100">
|
if (key.match(/secretPath|environment/)) {
|
||||||
{camelCaseToSpaces(key)}
|
return null;
|
||||||
</span>{" "}
|
}
|
||||||
<span className="text-mineshaft-200">
|
|
||||||
{
|
return (
|
||||||
formatedConditionsOperatorNames[
|
<ul key={`${action}_${index + 1}_${key}`} className="list-[square] pl-4">
|
||||||
operator as PermissionConditionOperators
|
{Object.entries(condition as object).map(([operator, value]) => {
|
||||||
]
|
if (operator === "$elemMatch") {
|
||||||
|
return Object.entries(value as object).map(
|
||||||
|
([nestedKey, nestedCondition]) =>
|
||||||
|
Object.entries(nestedCondition as object).map(
|
||||||
|
([nestedOperator, nestedValue]) => (
|
||||||
|
<ConditionDisplay
|
||||||
|
_key={`${key} ${nestedKey}`}
|
||||||
|
operator={nestedOperator}
|
||||||
|
value={nestedValue}
|
||||||
|
key={`${action}_${index + 1}_${key}_${operator}_${nestedKey}_${nestedOperator}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
</span>{" "}
|
|
||||||
<span className={rule.inverted ? "text-red" : "text-green"}>
|
return (
|
||||||
{typeof value === "string" ? value : value.join(", ")}
|
<ConditionDisplay
|
||||||
</span>
|
_key={key}
|
||||||
.
|
operator={operator}
|
||||||
</li>
|
value={value}
|
||||||
))}
|
key={`${action}_${index + 1}_${key}_${operator}`}
|
||||||
</ul>
|
/>
|
||||||
))}
|
);
|
||||||
</li>
|
})}
|
||||||
);
|
</ul>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
import { faFileImport, faFolder, faKey, faLock } from "@fortawesome/free-solid-svg-icons";
|
import { faFileImport, faFingerprint, faFolder, faKey } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Handle, NodeProps, Position } from "@xyflow/react";
|
import { Handle, NodeProps, Position } from "@xyflow/react";
|
||||||
|
|
||||||
@@ -12,15 +12,15 @@ import { createRoleNode } from "../utils";
|
|||||||
const getSubjectIcon = (subject: ProjectPermissionSub) => {
|
const getSubjectIcon = (subject: ProjectPermissionSub) => {
|
||||||
switch (subject) {
|
switch (subject) {
|
||||||
case ProjectPermissionSub.Secrets:
|
case ProjectPermissionSub.Secrets:
|
||||||
return <FontAwesomeIcon icon={faLock} className="h-4 w-4 text-yellow-700" />;
|
return <FontAwesomeIcon icon={faKey} className="h-4 w-4 text-bunker-300" />;
|
||||||
case ProjectPermissionSub.SecretFolders:
|
case ProjectPermissionSub.SecretFolders:
|
||||||
return <FontAwesomeIcon icon={faFolder} className="h-4 w-4 text-yellow-700" />;
|
return <FontAwesomeIcon icon={faFolder} className="h-4 w-4 text-yellow-700" />;
|
||||||
case ProjectPermissionSub.DynamicSecrets:
|
case ProjectPermissionSub.DynamicSecrets:
|
||||||
return <FontAwesomeIcon icon={faKey} className="h-4 w-4 text-yellow-700" />;
|
return <FontAwesomeIcon icon={faFingerprint} className="h-4 w-4 text-yellow-700" />;
|
||||||
case ProjectPermissionSub.SecretImports:
|
case ProjectPermissionSub.SecretImports:
|
||||||
return <FontAwesomeIcon icon={faFileImport} className="h-4 w-4 text-yellow-700" />;
|
return <FontAwesomeIcon icon={faFileImport} className="h-4 w-4 text-green-700" />;
|
||||||
default:
|
default:
|
||||||
return <FontAwesomeIcon icon={faLock} className="h-4 w-4 text-yellow-700" />;
|
return <FontAwesomeIcon icon={faKey} className="h-4 w-4 text-bunker-300" />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ const ACTION_MAP: Record<string, string[] | undefined> = {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SUBJECT_HEIGHT_MAP: Record<string, number> = {
|
||||||
|
[ProjectPermissionSub.DynamicSecrets]: 130,
|
||||||
|
[ProjectPermissionSub.Secrets]: 85,
|
||||||
|
default: 64
|
||||||
|
};
|
||||||
|
|
||||||
const evaluateCondition = (
|
const evaluateCondition = (
|
||||||
value: string,
|
value: string,
|
||||||
operator: PermissionConditionOperators,
|
operator: PermissionConditionOperators,
|
||||||
@@ -52,13 +58,113 @@ const evaluateCondition = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const doesConditionMatch = (
|
||||||
|
conditions: Record<string, any> | undefined,
|
||||||
|
value: string
|
||||||
|
): boolean => {
|
||||||
|
if (!conditions) return true;
|
||||||
|
|
||||||
|
return Object.entries(conditions).every(([operator, comparisonValue]) =>
|
||||||
|
evaluateCondition(value, operator as PermissionConditionOperators, comparisonValue)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const doBaseConditionsApply = (
|
||||||
|
ruleConditions: any,
|
||||||
|
environment: string,
|
||||||
|
folderPath: string
|
||||||
|
): boolean => {
|
||||||
|
return (
|
||||||
|
doesConditionMatch(ruleConditions?.environment, environment) &&
|
||||||
|
doesConditionMatch(ruleConditions?.secretPath, folderPath)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldShowConditionalAccess = (
|
||||||
|
actionRuleMap: TActionRuleMap,
|
||||||
|
action: string,
|
||||||
|
environment: string,
|
||||||
|
folderPath: string,
|
||||||
|
conditionalFields: string[]
|
||||||
|
): boolean => {
|
||||||
|
return actionRuleMap.some((rule) => {
|
||||||
|
const ruleConditions = rule[action]?.conditions;
|
||||||
|
if (!ruleConditions) return false;
|
||||||
|
|
||||||
|
// Check if any of the conditional fields are present
|
||||||
|
const hasConditionalField = conditionalFields.some((field) => ruleConditions[field]);
|
||||||
|
if (!hasConditionalField) return false;
|
||||||
|
|
||||||
|
// Check if base conditions (environment and secretPath) apply
|
||||||
|
return doBaseConditionsApply(ruleConditions, environment, folderPath);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const determineAccessLevel = (
|
||||||
|
hasPermission: boolean,
|
||||||
|
subject: ProjectPermissionSub,
|
||||||
|
action: string,
|
||||||
|
actionRuleMap: TActionRuleMap,
|
||||||
|
environment: string,
|
||||||
|
folderPath: string,
|
||||||
|
secretName: string,
|
||||||
|
metadata: Array<{ key: string; value: string }>
|
||||||
|
): PermissionAccess => {
|
||||||
|
if (!hasPermission) {
|
||||||
|
return PermissionAccess.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.Secrets) {
|
||||||
|
if (
|
||||||
|
!secretName &&
|
||||||
|
shouldShowConditionalAccess(actionRuleMap, action, environment, folderPath, [
|
||||||
|
"secretName",
|
||||||
|
"secretTags"
|
||||||
|
])
|
||||||
|
) {
|
||||||
|
return PermissionAccess.Partial;
|
||||||
|
}
|
||||||
|
} else if (subject === ProjectPermissionSub.DynamicSecrets) {
|
||||||
|
if (
|
||||||
|
!metadata.length &&
|
||||||
|
shouldShowConditionalAccess(actionRuleMap, action, environment, folderPath, ["metadata"])
|
||||||
|
) {
|
||||||
|
return PermissionAccess.Partial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PermissionAccess.Full;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkPermission = (
|
||||||
|
permissions: MongoAbility<ProjectPermissionSet, MongoQuery>,
|
||||||
|
subject: ProjectPermissionSub,
|
||||||
|
action: string,
|
||||||
|
subjectFields: any
|
||||||
|
): boolean => {
|
||||||
|
if (
|
||||||
|
subject === ProjectPermissionSub.Secrets &&
|
||||||
|
(action === ProjectPermissionSecretActions.ReadValue ||
|
||||||
|
action === ProjectPermissionSecretActions.DescribeSecret)
|
||||||
|
) {
|
||||||
|
return hasSecretReadValueOrDescribePermission(permissions, action, subjectFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissions.can(
|
||||||
|
// @ts-expect-error we are not specifying which so can't resolve if valid
|
||||||
|
action,
|
||||||
|
abilitySubject(subject, subjectFields)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const createFolderNode = ({
|
export const createFolderNode = ({
|
||||||
folder,
|
folder,
|
||||||
permissions,
|
permissions,
|
||||||
environment,
|
environment,
|
||||||
subject,
|
subject,
|
||||||
secretName,
|
secretName,
|
||||||
actionRuleMap
|
actionRuleMap,
|
||||||
|
metadata
|
||||||
}: {
|
}: {
|
||||||
folder: TSecretFolderWithPath;
|
folder: TSecretFolderWithPath;
|
||||||
permissions: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
permissions: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
@@ -66,6 +172,7 @@ export const createFolderNode = ({
|
|||||||
subject: ProjectPermissionSub;
|
subject: ProjectPermissionSub;
|
||||||
secretName: string;
|
secretName: string;
|
||||||
actionRuleMap: TActionRuleMap;
|
actionRuleMap: TActionRuleMap;
|
||||||
|
metadata: Array<{ key: string; value: string }>;
|
||||||
}) => {
|
}) => {
|
||||||
const actions = Object.fromEntries(
|
const actions = Object.fromEntries(
|
||||||
Object.values(ACTION_MAP[subject] ?? Object.values(ProjectPermissionActions)).map((action) => {
|
Object.values(ACTION_MAP[subject] ?? Object.values(ProjectPermissionActions)).map((action) => {
|
||||||
@@ -73,74 +180,26 @@ export const createFolderNode = ({
|
|||||||
|
|
||||||
// wrapped in try because while editing certain conditions, if their values are empty it throws an error
|
// wrapped in try because while editing certain conditions, if their values are empty it throws an error
|
||||||
try {
|
try {
|
||||||
let hasPermission: boolean;
|
|
||||||
|
|
||||||
const subjectFields = {
|
const subjectFields = {
|
||||||
secretPath: folder.path,
|
secretPath: folder.path,
|
||||||
environment,
|
environment,
|
||||||
secretName: secretName || "*",
|
secretName: secretName || "*",
|
||||||
secretTags: ["*"]
|
secretTags: ["*"],
|
||||||
|
metadata: metadata.length ? metadata : ["*"]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
const hasPermission = checkPermission(permissions, subject, action, subjectFields);
|
||||||
subject === ProjectPermissionSub.Secrets &&
|
|
||||||
(action === ProjectPermissionSecretActions.ReadValue ||
|
|
||||||
action === ProjectPermissionSecretActions.DescribeSecret)
|
|
||||||
) {
|
|
||||||
hasPermission = hasSecretReadValueOrDescribePermission(
|
|
||||||
permissions,
|
|
||||||
action,
|
|
||||||
subjectFields
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
hasPermission = permissions.can(
|
|
||||||
// @ts-expect-error we are not specifying which so can't resolve if valid
|
|
||||||
action,
|
|
||||||
abilitySubject(subject, subjectFields)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasPermission) {
|
access = determineAccessLevel(
|
||||||
// we want to show yellow/conditional access if user hasn't specified secret name to fully resolve access
|
hasPermission,
|
||||||
if (
|
subject,
|
||||||
!secretName &&
|
action,
|
||||||
actionRuleMap.some((el) => {
|
actionRuleMap,
|
||||||
// we only show conditional if secretName/secretTags are present - environment and path can be directly determined
|
environment,
|
||||||
if (!el[action]?.conditions?.secretName && !el[action]?.conditions?.secretTags)
|
folder.path,
|
||||||
return false;
|
secretName,
|
||||||
|
metadata
|
||||||
// make sure condition applies to env
|
);
|
||||||
if (el[action]?.conditions?.environment) {
|
|
||||||
if (
|
|
||||||
!Object.entries(el[action]?.conditions?.environment).every(([operator, value]) =>
|
|
||||||
evaluateCondition(environment, operator as PermissionConditionOperators, value)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// and applies to path
|
|
||||||
if (el[action]?.conditions?.secretPath) {
|
|
||||||
if (
|
|
||||||
!Object.entries(el[action]?.conditions?.secretPath).every(([operator, value]) =>
|
|
||||||
evaluateCondition(folder.path, operator as PermissionConditionOperators, value)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
access = PermissionAccess.Partial;
|
|
||||||
} else {
|
|
||||||
access = PermissionAccess.Full;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
access = PermissionAccess.None;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
access = PermissionAccess.None;
|
access = PermissionAccess.None;
|
||||||
@@ -150,18 +209,7 @@ export const createFolderNode = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
let height: number;
|
const height = SUBJECT_HEIGHT_MAP[subject] ?? SUBJECT_HEIGHT_MAP.default;
|
||||||
|
|
||||||
switch (subject) {
|
|
||||||
case ProjectPermissionSub.DynamicSecrets:
|
|
||||||
height = 130;
|
|
||||||
break;
|
|
||||||
case ProjectPermissionSub.Secrets:
|
|
||||||
height = 85;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
height = 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: PermissionNode.Folder,
|
type: PermissionNode.Folder,
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ export const SecretPathInput = ({
|
|||||||
maxHeight: "var(--radix-select-content-available-height)"
|
maxHeight: "var(--radix-select-content-available-height)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="max-h-[25vh] w-full flex-col items-center justify-center overflow-y-scroll rounded-md text-white">
|
<div className="thin-scrollbar max-h-[25vh] w-full flex-col items-center justify-center overflow-y-scroll rounded-md text-white">
|
||||||
{suggestions.map((suggestion, i) => (
|
{suggestions.map((suggestion, i) => (
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ export type IdentityManagementSubjectFields = {
|
|||||||
|
|
||||||
export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = {
|
export const formatedConditionsOperatorNames: { [K in PermissionConditionOperators]: string } = {
|
||||||
[PermissionConditionOperators.$EQ]: "equal to",
|
[PermissionConditionOperators.$EQ]: "equal to",
|
||||||
[PermissionConditionOperators.$IN]: "contains",
|
[PermissionConditionOperators.$IN]: "in",
|
||||||
[PermissionConditionOperators.$ALL]: "contains all",
|
[PermissionConditionOperators.$ALL]: "contains all",
|
||||||
[PermissionConditionOperators.$NEQ]: "not equal to",
|
[PermissionConditionOperators.$NEQ]: "not equal to",
|
||||||
[PermissionConditionOperators.$GLOB]: "matches glob pattern",
|
[PermissionConditionOperators.$GLOB]: "matches glob pattern",
|
||||||
|
|||||||
Reference in New Issue
Block a user