Merge remote-tracking branch 'origin/main' into feat/pki-ENG-3666

This commit is contained in:
Carlos Monastyrski
2025-09-16 12:48:37 -03:00
717 changed files with 14569 additions and 5316 deletions
@@ -16,7 +16,7 @@ import {
DropdownMenuTrigger,
PageHeader
} from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import { useDeleteProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
@@ -33,8 +33,8 @@ const Page = () => {
strict: false,
select: (el) => el.roleSlug as string
});
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace?.id || "";
const { currentProject } = useProject();
const projectId = currentProject?.id || "";
const { data } = useGetProjectRoleBySlug(projectId, roleSlug as string);
@@ -48,7 +48,7 @@ const Page = () => {
const onDeleteRoleSubmit = async () => {
try {
if (!currentWorkspace?.slug || !data?.id) return;
if (!currentProject?.slug || !data?.id) return;
await deleteProjectRole({
projectId,
@@ -61,7 +61,7 @@ const Page = () => {
});
handlePopUpClose("deleteRole");
navigate({
to: `${getProjectBaseURL(currentWorkspace.type)}/access-management` as const,
to: `${getProjectBaseURL(currentProject.type)}/access-management` as const,
params: {
projectId
},
@@ -9,7 +9,7 @@ import {
IconButton
} from "@app/components/v2";
import { usePopUp } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal";
import { PolicyTemplateModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicyTemplateModal";
@@ -0,0 +1,19 @@
import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types";
import { ConditionsFields } from "./ConditionsFields";
type Props = {
position?: number;
isDisabled?: boolean;
};
export const AppConnectionPermissionConditions = ({ position = 0, isDisabled }: Props) => {
return (
<ConditionsFields
isDisabled={isDisabled}
subject={ProjectPermissionSub.AppConnections}
position={position}
selectOptions={[{ value: "connectionId", label: "Connection ID" }]}
/>
);
};
@@ -5,7 +5,7 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent, Spinner } from "@app/components/v2";
import { ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionSub, useProject } from "@app/context";
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
import { getProjectBaseURL } from "@app/helpers/project";
import { useCreateProjectRole, useGetProjectRoleBySlug } from "@app/hooks/api";
@@ -45,7 +45,7 @@ const Content = ({ role, onClose }: ContentProps) => {
resolver: zodResolver(schema)
});
const { currentWorkspace } = useWorkspace();
const { currentProject } = useProject();
const createRole = useCreateProjectRole();
const navigate = useNavigate();
@@ -70,7 +70,7 @@ const Content = ({ role, onClose }: ContentProps) => {
});
const newRole = await createRole.mutateAsync({
projectId: currentWorkspace.id,
projectId: currentProject.id,
permissions: sanitizedPermission,
...form
});
@@ -81,10 +81,10 @@ const Content = ({ role, onClose }: ContentProps) => {
});
navigate({
to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug` as const,
to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const,
params: {
roleSlug: newRole.slug,
projectId: currentWorkspace.id
projectId: currentProject.id
}
});
@@ -142,9 +142,9 @@ const Content = ({ role, onClose }: ContentProps) => {
};
export const DuplicateProjectRoleModal = ({ isOpen, onOpenChange, roleSlug }: Props) => {
const { currentWorkspace } = useWorkspace();
const { currentProject } = useProject();
const { data: role, isPending } = useGetProjectRoleBySlug(currentWorkspace.id, roleSlug ?? "");
const { data: role, isPending } = useGetProjectRoleBySlug(currentProject.id, roleSlug ?? "");
if (!roleSlug) return null;
@@ -23,6 +23,7 @@ export const renderOperatorSelectItems = (type: string) => {
case "secretTags":
return <SelectItem value={PermissionConditionOperators.$IN}>Contains</SelectItem>;
case "identityId":
case "connectionId":
return (
<>
<SelectItem value={PermissionConditionOperators.$EQ}>Equal</SelectItem>
@@ -19,7 +19,7 @@ import {
Tr
} from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import {
EXCLUDED_PERMISSION_SUBS,
@@ -13,7 +13,7 @@ import {
ModalContent
} from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import {
PROJECT_PERMISSION_OBJECT,
@@ -12,6 +12,7 @@ import {
} from "@app/context";
import {
PermissionConditionOperators,
ProjectPermissionAppConnectionActions,
ProjectPermissionAuditLogsActions,
ProjectPermissionCommitsActions,
ProjectPermissionDynamicSecretActions,
@@ -33,8 +34,8 @@ import {
TPermissionCondition,
TPermissionConditionOperators
} from "@app/context/ProjectPermissionContext/types";
import { ProjectType } from "@app/hooks/api/projects/types";
import { TProjectPermission } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
const GeneralPolicyActionSchema = z.object({
read: z.boolean().optional(),
@@ -145,6 +146,14 @@ const SecretScanningConfigPolicyActionSchema = z.object({
[ProjectPermissionSecretScanningConfigActions.Update]: z.boolean().optional()
});
const AppConnectionPolicyActionSchema = z.object({
[ProjectPermissionAppConnectionActions.Create]: z.boolean().optional(),
[ProjectPermissionAppConnectionActions.Read]: z.boolean().optional(),
[ProjectPermissionAppConnectionActions.Edit]: z.boolean().optional(),
[ProjectPermissionAppConnectionActions.Delete]: z.boolean().optional(),
[ProjectPermissionAppConnectionActions.Connect]: z.boolean().optional()
});
const KmipPolicyActionSchema = z.object({
[ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(),
[ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(),
@@ -322,6 +331,12 @@ export const projectRoleFormSchema = z.object({
})
.array()
.default([]),
[ProjectPermissionSub.AppConnections]: AppConnectionPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
})
.array()
.default([]),
[ProjectPermissionSub.PkiSyncs]: PkiSyncPolicyActionSchema.extend({
inverted: z.boolean().optional(),
conditions: ConditionSchema
@@ -411,7 +426,8 @@ type TConditionalFields =
| ProjectPermissionSub.Identity
| ProjectPermissionSub.SecretSyncs
| ProjectPermissionSub.PkiSyncs
| ProjectPermissionSub.SecretEvents;
| ProjectPermissionSub.SecretEvents
| ProjectPermissionSub.AppConnections;
export const isConditionalSubjects = (
subject: ProjectPermissionSub
@@ -427,7 +443,8 @@ export const isConditionalSubjects = (
subject === ProjectPermissionSub.CertificateTemplates ||
subject === ProjectPermissionSub.SecretSyncs ||
subject === ProjectPermissionSub.PkiSyncs ||
subject === ProjectPermissionSub.SecretEvents;
subject === ProjectPermissionSub.SecretEvents ||
subject === ProjectPermissionSub.AppConnections;
const convertCaslConditionToFormOperator = (caslConditions: TPermissionCondition) => {
const formConditions: z.infer<typeof ConditionSchema> = [];
@@ -535,7 +552,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
ProjectPermissionSub.SshHostGroups,
ProjectPermissionSub.SecretSyncs,
ProjectPermissionSub.PkiSyncs,
ProjectPermissionSub.SecretEvents
ProjectPermissionSub.SecretEvents,
ProjectPermissionSub.AppConnections
].includes(subject)
) {
// from above statement we are sure it won't be undefined
@@ -706,6 +724,27 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
return;
}
if (subject === ProjectPermissionSub.AppConnections) {
const canCreate = action.includes(ProjectPermissionAppConnectionActions.Create);
const canRead = action.includes(ProjectPermissionAppConnectionActions.Read);
const canEdit = action.includes(ProjectPermissionAppConnectionActions.Edit);
const canDelete = action.includes(ProjectPermissionAppConnectionActions.Delete);
const canConnect = action.includes(ProjectPermissionAppConnectionActions.Connect);
// from above statement we are sure it won't be undefined
formVal[subject]!.push({
[ProjectPermissionAppConnectionActions.Read]: canRead,
[ProjectPermissionAppConnectionActions.Create]: canCreate,
[ProjectPermissionAppConnectionActions.Edit]: canEdit,
[ProjectPermissionAppConnectionActions.Delete]: canDelete,
[ProjectPermissionAppConnectionActions.Connect]: canConnect,
conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
inverted
});
return;
}
// for other subjects
const canRead = action.includes(ProjectPermissionActions.Read);
const canEdit = action.includes(ProjectPermissionActions.Edit);
@@ -1667,6 +1706,31 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
value: ProjectPermissionSecretEventActions.SubscribeImportMutations
}
]
},
[ProjectPermissionSub.AppConnections]: {
title: "App Connections",
actions: [
{
label: "Read",
value: ProjectPermissionAppConnectionActions.Read
},
{
label: "Create",
value: ProjectPermissionAppConnectionActions.Create
},
{
label: "Update",
value: ProjectPermissionAppConnectionActions.Edit
},
{
label: "Delete",
value: ProjectPermissionAppConnectionActions.Delete
},
{
label: "Connect",
value: ProjectPermissionAppConnectionActions.Connect
}
]
}
};
@@ -1740,7 +1804,8 @@ export const ProjectTypePermissionSubjects: Record<
...KmsPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...SshPermissionSubjects(),
...SecretScanningSubject()
...SecretScanningSubject(),
[ProjectPermissionSub.AppConnections]: true
},
[ProjectType.KMS]: {
...SharedPermissionSubjects,
@@ -1748,7 +1813,8 @@ export const ProjectTypePermissionSubjects: Record<
...SecretsManagerPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...SshPermissionSubjects(),
...SecretScanningSubject()
...SecretScanningSubject(),
[ProjectPermissionSub.AppConnections]: false
},
[ProjectType.CertificateManager]: {
...SharedPermissionSubjects,
@@ -1756,7 +1822,8 @@ export const ProjectTypePermissionSubjects: Record<
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects(),
...SshPermissionSubjects(),
...SecretScanningSubject()
...SecretScanningSubject(),
[ProjectPermissionSub.AppConnections]: true
},
[ProjectType.SSH]: {
...SharedPermissionSubjects,
@@ -1764,7 +1831,8 @@ export const ProjectTypePermissionSubjects: Record<
...CertificateManagerPermissionSubjects(),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects(),
...SecretScanningSubject()
...SecretScanningSubject(),
[ProjectPermissionSub.AppConnections]: false
},
[ProjectType.SecretScanning]: {
...SharedPermissionSubjects,
@@ -1772,7 +1840,8 @@ export const ProjectTypePermissionSubjects: Record<
...SshPermissionSubjects(),
...CertificateManagerPermissionSubjects(),
...KmsPermissionSubjects(),
...SecretsManagerPermissionSubjects()
...SecretsManagerPermissionSubjects(),
[ProjectPermissionSub.AppConnections]: true
}
};
@@ -6,7 +6,7 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useProject } from "@app/context";
import { getProjectBaseURL } from "@app/helpers/project";
import {
useCreateProjectRole,
@@ -38,8 +38,8 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
roleSlug: string;
};
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace?.id || "";
const { currentProject } = useProject();
const projectId = currentProject?.id || "";
const { data: role } = useGetProjectRoleBySlug(projectId, popupData?.roleSlug ?? "");
@@ -101,7 +101,7 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => {
});
navigate({
to: `${getProjectBaseURL(currentWorkspace.type)}/roles/$roleSlug` as const,
to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const,
params: {
roleSlug: newRole.slug,
projectId
@@ -8,14 +8,15 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { createNotification } from "@app/components/notifications";
import { AccessTree } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionSub, useProject } from "@app/context";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
import { evaluatePermissionsAbility } from "@app/helpers/permissions";
import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api";
import { ProjectType } from "@app/hooks/api/projects/types";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { AddPoliciesButton } from "./AddPoliciesButton";
import { AppConnectionPermissionConditions } from "./AppConnectionPermissionConditions";
import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions";
import { GeneralPermissionConditions } from "./GeneralPermissionConditions";
import { GeneralPermissionPolicies } from "./GeneralPermissionPolicies";
@@ -77,6 +78,10 @@ export const renderConditionalComponents = (
return <SecretEventPermissionConditions isDisabled={isDisabled} />;
}
if (subject === ProjectPermissionSub.AppConnections) {
return <AppConnectionPermissionConditions isDisabled={isDisabled} />;
}
return <GeneralPermissionConditions isDisabled={isDisabled} type={subject} />;
}
@@ -84,10 +89,10 @@ export const renderConditionalComponents = (
};
export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace?.id || "";
const { currentProject } = useProject();
const projectId = currentProject?.id || "";
const { data: role, isPending } = useGetProjectRoleBySlug(
currentWorkspace?.id ?? "",
currentProject?.id ?? "",
roleSlug as string
);
@@ -126,7 +131,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
(role?.slug ?? "") as ProjectMembershipRole
);
const isSecretManagerProject = currentWorkspace.type === ProjectType.SecretManager;
const isSecretManagerProject = currentProject.type === ProjectType.SecretManager;
const permissions = form.watch("permissions");
@@ -178,7 +183,7 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
Save
</Button>
<div className="ml-2 border-l border-mineshaft-500 pl-4">
<AddPoliciesButton isDisabled={isDisabled} projectType={currentWorkspace.type} />
<AddPoliciesButton isDisabled={isDisabled} projectType={currentProject.type} />
</div>
</div>
)}