diff --git a/packages/manager/.changeset/pr-12534-upcoming-features-1752829040541.md b/packages/manager/.changeset/pr-12534-upcoming-features-1752829040541.md new file mode 100644 index 00000000000..f114f4b1ecb --- /dev/null +++ b/packages/manager/.changeset/pr-12534-upcoming-features-1752829040541.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +Implement the new RBAC permission hook in Firewalls Rules flow ([#12534](https://github.com/linode/manager/pull/12534)) diff --git a/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRulesLanding.test.tsx b/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRulesLanding.test.tsx new file mode 100644 index 00000000000..e05ab9304dd --- /dev/null +++ b/packages/manager/src/features/Firewalls/FirewallDetail/Rules/FirewallRulesLanding.test.tsx @@ -0,0 +1,191 @@ +import React from 'react'; + +import { firewallRulesFactory } from 'src/factories/firewalls'; +import { renderWithTheme } from 'src/utilities/testHelpers'; + +import { FirewallRulesLanding } from './FirewallRulesLanding'; + +const queryMocks = vi.hoisted(() => ({ + userPermissions: vi.fn(() => ({ + permissions: { + update_firewall_rules: false, + }, + })), + useNavigate: vi.fn(), + useLocation: vi.fn(), + useBlocker: vi.fn(() => ({ + proceed: vi.fn(), + reset: vi.fn(), + status: 'unblocked', + })), +})); + +vi.mock('src/features/IAM/hooks/usePermissions', () => ({ + usePermissions: queryMocks.userPermissions, +})); + +vi.mock('@tanstack/react-router', async () => { + const actual = await vi.importActual('@tanstack/react-router'); + return { + ...actual, + useNavigate: queryMocks.useNavigate, + useLocation: vi.fn(() => ({ + pathname: '/firewalls/1/rules', + })), + useBlocker: queryMocks.useBlocker, + }; +}); + +const firewallRules = firewallRulesFactory.build(); +const getDisabledState = () => + !queryMocks.userPermissions().permissions.update_firewall_rules; + +describe('FirewallRuleTable', () => { + it('should disable "Add AN Inbound Rule" button if the user does not have update_firewall_rules permission', () => { + const { getByRole } = renderWithTheme( + + ); + + const addInboundBtn = getByRole('button', { name: 'Add an Inbound Rule' }); + + expect(addInboundBtn).toBeInTheDocument(); + expect(addInboundBtn).toHaveAttribute('aria-disabled', 'true'); + }); + + it('should disable "Add AN Outbound Rule" button if the user does not have update_firewall_rules permission', () => { + const { getByRole } = renderWithTheme( + + ); + + const addOutboundBtn = getByRole('button', { + name: 'Add an Outbound Rule', + }); + expect(addOutboundBtn).toBeInTheDocument(); + expect(addOutboundBtn).toHaveAttribute('aria-disabled', 'true'); + }); + + it('should disable "Save Changes" button if the user does not have update_firewall_rules permission', () => { + const { getByRole } = renderWithTheme( + + ); + + const saveBtn = getByRole('button', { name: 'Save Changes' }); + + expect(saveBtn).toBeInTheDocument(); + expect(saveBtn).toHaveAttribute('aria-disabled', 'true'); + }); + + it('should disable menu buttons if the user does not have update_firewall_rules permission', () => { + const { getAllByRole } = renderWithTheme( + + ); + + const editBtns = getAllByRole('button', { + name: 'Edit', + }); + expect(editBtns[0]).toBeVisible(); + expect(editBtns[0]).toHaveAttribute('aria-disabled', 'true'); + + const cloneBtns = getAllByRole('button', { + name: 'Clone', + }); + expect(cloneBtns[0]).toBeVisible(); + expect(cloneBtns[0]).toHaveAttribute('aria-disabled', 'true'); + + const deleteBtns = getAllByRole('button', { + name: 'Delete', + }); + expect(deleteBtns[0]).toBeVisible(); + expect(deleteBtns[0]).toHaveAttribute('aria-disabled', 'true'); + }); + + it('should enable menu buttons if the user has update_firewall_rules permission', () => { + queryMocks.userPermissions.mockReturnValue({ + permissions: { + update_firewall_rules: true, + }, + }); + const { getAllByRole } = renderWithTheme( + + ); + + const editBtns = getAllByRole('button', { + name: 'Edit', + }); + expect(editBtns[0]).toBeVisible(); + expect(editBtns[0]).not.toHaveAttribute('aria-disabled', 'true'); + + const cloneBtns = getAllByRole('button', { + name: 'Clone', + }); + expect(cloneBtns[0]).toBeVisible(); + expect(cloneBtns[0]).not.toHaveAttribute('aria-disabled', 'true'); + + const deleteBtns = getAllByRole('button', { + name: 'Delete', + }); + expect(deleteBtns[0]).toBeVisible(); + expect(deleteBtns[0]).not.toHaveAttribute('aria-disabled', 'true'); + }); + + it('should enable "Add AN Inbound Rule" button if the user has update_firewall_rules permission', () => { + queryMocks.userPermissions.mockReturnValue({ + permissions: { + update_firewall_rules: true, + }, + }); + const { getByRole } = renderWithTheme( + + ); + + const addInboundBtn = getByRole('button', { name: 'Add an Inbound Rule' }); + + expect(addInboundBtn).toBeInTheDocument(); + expect(addInboundBtn).not.toHaveAttribute('aria-disabled', 'true'); + }); + + it('should enable "Add AN Outbound Rule" button if the user has update_firewall_rules permission', () => { + queryMocks.userPermissions.mockReturnValue({ + permissions: { + update_firewall_rules: true, + }, + }); + const { getByRole } = renderWithTheme( + + ); + + const addOutboundBtn = getByRole('button', { + name: 'Add an Outbound Rule', + }); + expect(addOutboundBtn).toBeInTheDocument(); + expect(addOutboundBtn).not.toHaveAttribute('aria-disabled', 'true'); + }); +}); diff --git a/packages/manager/src/features/Firewalls/FirewallDetail/index.tsx b/packages/manager/src/features/Firewalls/FirewallDetail/index.tsx index 5e8bd07315b..72d6a4363b3 100644 --- a/packages/manager/src/features/Firewalls/FirewallDetail/index.tsx +++ b/packages/manager/src/features/Firewalls/FirewallDetail/index.tsx @@ -27,6 +27,7 @@ import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel'; import { TabPanels } from 'src/components/Tabs/TabPanels'; import { Tabs } from 'src/components/Tabs/Tabs'; import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList'; +import { usePermissions } from 'src/features/IAM/hooks/usePermissions'; import { useFlags } from 'src/hooks/useFlags'; import { useSecureVMNoticesEnabled } from 'src/hooks/useSecureVMNoticesEnabled'; import { useTabs } from 'src/hooks/useTabs'; @@ -82,6 +83,12 @@ export const FirewallDetail = () => { grants ); + const { permissions } = usePermissions( + 'firewall', + ['update_firewall_rules'], + firewallId + ); + const { data: allDevices } = useAllFirewallDevicesQuery(firewallId); const { linodeCount, nodebalancerCount } = allDevices?.reduce( @@ -230,7 +237,7 @@ export const FirewallDetail = () => {