Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to the **Prowler UI** are documented in this file.

## [1.32.2] (Prowler UNRELEASED)

### 🔄 Changed

- RBAC role forms now explain Unlimited Visibility in a dedicated section and preserve explicit visibility choices when admin permissions are toggled [(#11851)](https://github.com/prowler-cloud/prowler/pull/11851)

---

## [1.32.1] (Prowler UNRELEASED)

### 🐞 Fixed
Expand Down
153 changes: 153 additions & 0 deletions ui/components/roles/workflow/forms/add-role-form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,157 @@ describe("AddRoleForm", () => {
// Then
expect(routerMocks.push).toHaveBeenCalledWith("/roles");
});

it("explains Unlimited Visibility independently from admin permissions", () => {
// Given / When
render(<AddRoleForm groups={[]} />);

// Then
expect(
screen.getByRole("heading", { name: "Unlimited Visibility" }),
).toBeInTheDocument();
expect(
screen.getByText(
/grants visibility into every provider, account, resource, finding, scan, and compliance result/i,
),
).toBeInTheDocument();
expect(
screen.getByText(
/does not grant admin actions such as managing users, providers, scans, integrations, billing, or alerts/i,
),
).toBeInTheDocument();
expect(
screen.getByText(
/enable it only for roles that need tenant-wide security visibility/i,
),
).toBeInTheDocument();
expect(
screen.getByText(
/manage providers enables unlimited visibility in this form because provider administration needs tenant-wide provider-group context/i,
),
).toBeInTheDocument();
});

it("enables Unlimited Visibility when Manage Providers is selected", async () => {
// Given
const user = userEvent.setup();
render(<AddRoleForm groups={[]} />);

// When
await user.click(
screen.getByRole("checkbox", { name: "Manage Providers" }),
);

// Then
expect(
screen.getByRole("checkbox", {
name: "Enable Unlimited Visibility for this role",
}),
).toBeChecked();
expect(
screen.getByText(
/Manage Providers is selected, so Unlimited Visibility stays enabled in this form/i,
),
).toBeInTheDocument();
});

it("enables Unlimited Visibility through Manage Providers when granting all admin permissions", async () => {
// Given
const user = userEvent.setup();
render(<AddRoleForm groups={[]} />);

// When
await user.click(
screen.getByRole("checkbox", { name: "Grant all admin permissions" }),
);

// Then
expect(
screen.getByRole("checkbox", { name: "Manage Providers" }),
).toBeChecked();
expect(
screen.getByRole("checkbox", {
name: "Enable Unlimited Visibility for this role",
}),
).toBeChecked();
});

it("clears Unlimited Visibility when all admin permissions are toggled off after only auto-enabling it", async () => {
// Given
const user = userEvent.setup();
render(<AddRoleForm groups={[]} />);

// When
await user.click(
screen.getByRole("checkbox", { name: "Grant all admin permissions" }),
);
await user.click(
screen.getByRole("checkbox", { name: "Grant all admin permissions" }),
);

// Then
expect(
screen.getByRole("checkbox", { name: "Manage Providers" }),
).not.toBeChecked();
expect(
screen.getByRole("checkbox", {
name: "Enable Unlimited Visibility for this role",
}),
).not.toBeChecked();
});

it("keeps explicitly enabled Unlimited Visibility when all admin permissions are toggled off", async () => {
// Given
const user = userEvent.setup();
render(<AddRoleForm groups={[]} />);

// When
await user.click(
screen.getByRole("checkbox", {
name: "Enable Unlimited Visibility for this role",
}),
);
await user.click(
screen.getByRole("checkbox", { name: "Grant all admin permissions" }),
);
await user.click(
screen.getByRole("checkbox", { name: "Grant all admin permissions" }),
);

// Then
expect(
screen.getByRole("checkbox", { name: "Manage Providers" }),
).not.toBeChecked();
expect(
screen.getByRole("checkbox", {
name: "Enable Unlimited Visibility for this role",
}),
).toBeChecked();
});

it("does not describe clearing Manage Providers as removing explicitly enabled Unlimited Visibility", async () => {
// Given
const user = userEvent.setup();
render(<AddRoleForm groups={[]} />);

// When
await user.click(
screen.getByRole("checkbox", {
name: "Enable Unlimited Visibility for this role",
}),
);
await user.click(
screen.getByRole("checkbox", { name: "Manage Providers" }),
);

// Then
expect(
screen.getByText(
/Manage Providers is selected, so Unlimited Visibility stays enabled in this form/i,
),
).toBeInTheDocument();
expect(
screen.queryByText(/remove this automatic visibility grant/i),
).not.toBeInTheDocument();
});
});
57 changes: 30 additions & 27 deletions ui/components/roles/workflow/forms/add-role-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
import clsx from "clsx";
import { InfoIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";

Expand All @@ -16,9 +15,16 @@ import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-s
import { useToast } from "@/components/ui";
import { CustomInput } from "@/components/ui/custom";
import { Form, FormButtons } from "@/components/ui/form";
import { getErrorMessage, permissionFormFields } from "@/lib";
import { useManageProvidersUnlimitedVisibility } from "@/hooks/use-manage-providers-unlimited-visibility";
import { getErrorMessage } from "@/lib";
import {
getUnlimitedVisibilityField,
getVisiblePermissionFormFields,
} from "@/lib/role-permissions";
import { addRoleFormSchema, ApiError } from "@/types";

import { UnlimitedVisibilityField } from "./unlimited-visibility-section";

type FormValues = z.input<typeof addRoleFormSchema>;

export const AddRoleForm = ({
Expand All @@ -29,11 +35,9 @@ export const AddRoleForm = ({
const { toast } = useToast();
const router = useRouter();
const isCloudEnvironment = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true";
const visiblePermissionFormFields = permissionFormFields.filter(
(permission) =>
!["manage_billing", "manage_alerts"].includes(permission.field) ||
isCloudEnvironment,
);
const visiblePermissionFormFields =
getVisiblePermissionFormFields(isCloudEnvironment);
const unlimitedVisibilityField = getUnlimitedVisibilityField();

const form = useForm<FormValues>({
resolver: zodResolver(addRoleFormSchema),
Expand All @@ -52,30 +56,18 @@ export const AddRoleForm = ({
},
});

const { watch, setValue } = form;

const manageProviders = watch("manage_providers");
const unlimitedVisibility = watch("unlimited_visibility");

useEffect(() => {
if (manageProviders && !unlimitedVisibility) {
setValue("unlimited_visibility", true, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
}
}, [manageProviders, unlimitedVisibility, setValue]);
const {
isUnlimitedVisibilityRequiredByManageProviders,
setPermissionValue,
setUnlimitedVisibility,
} = useManageProvidersUnlimitedVisibility(form);
const unlimitedVisibility = form.watch("unlimited_visibility");

const isLoading = form.formState.isSubmitting;

const onSelectAllChange = (checked: boolean) => {
visiblePermissionFormFields.forEach(({ field }) => {
form.setValue(field as keyof FormValues, checked, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
setPermissionValue(field, checked);
});
};

Expand Down Expand Up @@ -160,6 +152,14 @@ export const AddRoleForm = ({
isRequired
/>

{unlimitedVisibilityField && (
Comment thread
HugoPBrito marked this conversation as resolved.
Outdated
<UnlimitedVisibilityField
isSelected={!!form.watch("unlimited_visibility")}
isDisabled={isUnlimitedVisibilityRequiredByManageProviders}
onValueChange={setUnlimitedVisibility}
/>
)}

<div className="flex flex-col gap-4">
<span className="text-lg font-semibold">Admin Permissions</span>

Expand All @@ -168,7 +168,7 @@ export const AddRoleForm = ({
isSelected={visiblePermissionFormFields.every((perm) =>
form.watch(perm.field as keyof FormValues),
)}
onChange={(e) => onSelectAllChange(e.target.checked)}
onValueChange={onSelectAllChange}
classNames={{
label: "text-small",
wrapper: "checkbox-update",
Expand All @@ -186,6 +186,9 @@ export const AddRoleForm = ({
<Checkbox
{...form.register(field as keyof FormValues)}
isSelected={!!form.watch(field as keyof FormValues)}
onValueChange={(checked) =>
setPermissionValue(field, checked)
}
classNames={{
label: "text-small",
wrapper: "checkbox-update",
Expand Down
Loading
Loading