Skip to content

Commit 9e46eb4

Browse files
feat: [UIE-9076, UIE-9076] - IAM RBAC: fix permission check for rebuilding and resizing linode (linode#12680)
* feat: [UIE-9076] - IAM RBAC: fix permission check for rebuilding linode * feat: [UIE-9077] - IAM RBAC: fix permission check for resizing linode * Added changeset: IAM RBAC: fix permission check for rebuilding and resizing linode --------- Co-authored-by: Jaalah Ramos <125309814+jaalah-akamai@users.noreply.github.com>
1 parent 603ddd1 commit 9e46eb4

File tree

5 files changed

+123
-23
lines changed

5 files changed

+123
-23
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Changed
3+
---
4+
5+
IAM RBAC: fix permission check for rebuilding and resizing linode ([#12680](https://github.com/linode/manager/pull/12680))

packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/LinodeRebuildForm.test.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ import { renderWithTheme } from 'src/utilities/testHelpers';
66

77
import { LinodeRebuildForm } from './LinodeRebuildForm';
88

9+
const queryMocks = vi.hoisted(() => ({
10+
userPermissions: vi.fn(() => ({
11+
data: {
12+
rebuild_linode: false,
13+
},
14+
})),
15+
}));
16+
17+
vi.mock('src/features/IAM/hooks/usePermissions', () => ({
18+
usePermissions: queryMocks.userPermissions,
19+
}));
20+
921
describe('LinodeRebuildForm', () => {
1022
it('renders a notice reccomending users add user data when the Linode already uses user data', async () => {
1123
const linode = linodeFactory.build({ has_user_data: true });
@@ -48,4 +60,48 @@ describe('LinodeRebuildForm', () => {
4860
getByLabelText('This Linode does not have existing user data.')
4961
).toBeVisible();
5062
});
63+
64+
it('should disable all fields if user does not have permission', async () => {
65+
const linode = linodeFactory.build();
66+
67+
const { getByRole, getByPlaceholderText, getAllByRole } = renderWithTheme(
68+
<LinodeRebuildForm linode={linode} onSuccess={vi.fn()} />
69+
);
70+
71+
const passwordInput = getByPlaceholderText('Enter a password.');
72+
expect(passwordInput).toBeDisabled();
73+
74+
const rebuildBtn = getByRole('button', {
75+
name: 'Rebuild Linode',
76+
});
77+
expect(rebuildBtn).toHaveAttribute('aria-disabled', 'true');
78+
79+
const rebuildInput = getAllByRole('combobox')[0];
80+
expect(rebuildInput).toBeDisabled();
81+
});
82+
83+
it('should enable all fields if user has permission', async () => {
84+
const linode = linodeFactory.build();
85+
86+
queryMocks.userPermissions.mockReturnValue({
87+
data: {
88+
rebuild_linode: true,
89+
},
90+
});
91+
92+
const { getByRole, getByPlaceholderText, getAllByRole } = renderWithTheme(
93+
<LinodeRebuildForm linode={linode} onSuccess={vi.fn()} />
94+
);
95+
96+
const passwordInput = getByPlaceholderText('Enter a password.');
97+
expect(passwordInput).toBeEnabled();
98+
99+
const rebuildBtn = getByRole('button', {
100+
name: 'Rebuild Linode',
101+
});
102+
expect(rebuildBtn).not.toHaveAttribute('aria-disabled', 'true');
103+
104+
const rebuildInput = getAllByRole('combobox')[0];
105+
expect(rebuildInput).toBeEnabled();
106+
});
51107
});

packages/manager/src/features/Linodes/LinodesDetail/LinodeRebuild/LinodeRebuildForm.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { useSnackbar } from 'notistack';
88
import React, { useEffect, useRef, useState } from 'react';
99
import { FormProvider, useForm } from 'react-hook-form';
1010

11-
import { useIsResourceRestricted } from 'src/hooks/useIsResourceRestricted';
11+
import { usePermissions } from 'src/features/IAM/hooks/usePermissions';
1212
import { useEventsPollingActions } from 'src/queries/events/events';
1313

1414
import { StackScriptSelectionList } from '../../LinodeCreate/Tabs/StackScripts/StackScriptSelectionList';
@@ -43,11 +43,11 @@ export const LinodeRebuildForm = (props: Props) => {
4343

4444
const [type, setType] = useState<LinodeRebuildType>('Image');
4545

46-
const isLinodeReadOnly = useIsResourceRestricted({
47-
grantLevel: 'read_only',
48-
grantType: 'linode',
49-
id: linode.id,
50-
});
46+
const { data: permissions } = usePermissions(
47+
'linode',
48+
['rebuild_linode'],
49+
linode.id
50+
);
5151

5252
const { data: isTypeToConfirmEnabled } = usePreferences(
5353
(preferences) => preferences?.type_to_confirm ?? true
@@ -127,7 +127,7 @@ export const LinodeRebuildForm = (props: Props) => {
127127
<FormProvider {...form}>
128128
<form onSubmit={form.handleSubmit(onSubmit)}>
129129
<Stack spacing={2}>
130-
{isLinodeReadOnly && <LinodePermissionsError />}
130+
{!permissions.rebuild_linode && <LinodePermissionsError />}
131131
{form.formState.errors.root && (
132132
<Notice text={form.formState.errors.root.message} variant="error" />
133133
)}
@@ -150,7 +150,7 @@ export const LinodeRebuildForm = (props: Props) => {
150150
}}
151151
>
152152
<RebuildFromSelect
153-
disabled={isLinodeReadOnly}
153+
disabled={!permissions.rebuild_linode}
154154
setType={setType}
155155
type={type}
156156
/>
@@ -167,21 +167,21 @@ export const LinodeRebuildForm = (props: Props) => {
167167
<StackScriptSelectionList type="Community" />
168168
)}
169169
{type.includes('StackScript') && <UserDefinedFields />}
170-
<Image disabled={isLinodeReadOnly} />
171-
<Password disabled={isLinodeReadOnly} />
172-
<SSHKeys disabled={isLinodeReadOnly} />
170+
<Image disabled={!permissions.rebuild_linode} />
171+
<Password disabled={!permissions.rebuild_linode} />
172+
<SSHKeys disabled={!permissions.rebuild_linode} />
173173
<DiskEncryption
174-
disabled={isLinodeReadOnly}
174+
disabled={!permissions.rebuild_linode}
175175
isLKELinode={linode.lke_cluster_id !== null}
176176
linodeRegion={linode.region}
177177
/>
178-
<UserData disabled={isLinodeReadOnly} linode={linode} />
178+
<UserData disabled={!permissions.rebuild_linode} linode={linode} />
179179
<Confirmation
180-
disabled={isLinodeReadOnly}
180+
disabled={!permissions.rebuild_linode}
181181
linodeLabel={linode.label}
182182
/>
183183
</Stack>
184-
<Actions disabled={isLinodeReadOnly} />
184+
<Actions disabled={!permissions.rebuild_linode} />
185185
</Stack>
186186
</form>
187187
</FormProvider>

packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/LinodeResize.test.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { waitFor } from '@testing-library/react';
12
import * as React from 'react';
23

34
import { extDisk, swapDisk } from 'src/__data__/disks';
@@ -19,6 +20,18 @@ const props: Props = {
1920
open: true,
2021
};
2122

23+
const queryMocks = vi.hoisted(() => ({
24+
userPermissions: vi.fn(() => ({
25+
data: {
26+
resize_linode: false,
27+
},
28+
})),
29+
}));
30+
31+
vi.mock('src/features/IAM/hooks/usePermissions', () => ({
32+
usePermissions: queryMocks.userPermissions,
33+
}));
34+
2235
beforeAll(() => {
2336
mockMatchMedia();
2437
});
@@ -101,4 +114,30 @@ describe('LinodeResize', () => {
101114
});
102115
});
103116
});
117+
118+
it('should not allow resizing if user does not have permission', async () => {
119+
const { findByText, getByRole } = renderWithTheme(
120+
<LinodeResize {...props} />
121+
);
122+
await findByText(
123+
"You don't have permissions to edit this Linode. Please contact your account administrator to request the necessary permissions."
124+
);
125+
126+
const resizeBtn = getByRole('button', { name: 'Resize Linode' });
127+
expect(resizeBtn).toBeDisabled();
128+
});
129+
130+
it('should not render LinodePermissionsError when user has resize_linode permission', async () => {
131+
queryMocks.userPermissions.mockReturnValue({
132+
data: {
133+
resize_linode: true,
134+
},
135+
});
136+
137+
const { queryByTestId } = renderWithTheme(<LinodeResize {...props} />);
138+
139+
await waitFor(() => {
140+
expect(queryByTestId('linode-permissions-error')).not.toBeInTheDocument();
141+
});
142+
});
104143
});

packages/manager/src/features/Linodes/LinodesDetail/LinodeResize/LinodeResize.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import { ErrorMessage } from 'src/components/ErrorMessage';
2727
import { Link } from 'src/components/Link';
2828
import { TypeToConfirm } from 'src/components/TypeToConfirm/TypeToConfirm';
2929
import { PlansPanel } from 'src/features/components/PlansPanel/PlansPanel';
30+
import { usePermissions } from 'src/features/IAM/hooks/usePermissions';
3031
import { linodeInTransition } from 'src/features/Linodes/transitions';
31-
import { useIsResourceRestricted } from 'src/hooks/useIsResourceRestricted';
3232
import { useEventsPollingActions } from 'src/queries/events/events';
3333
import { extendType } from 'src/utilities/extendType';
3434

@@ -96,11 +96,11 @@ export const LinodeResize = (props: Props) => {
9696
const hostMaintenance = linode?.status === 'stopped';
9797
const isLinodeOffline = linode?.status === 'offline';
9898

99-
const isLinodesGrantReadOnly = useIsResourceRestricted({
100-
grantLevel: 'read_only',
101-
grantType: 'linode',
102-
id: linodeId,
103-
});
99+
const { data: permissions } = usePermissions(
100+
'linode',
101+
['resize_linode'],
102+
linodeId
103+
);
104104

105105
const formik = useFormik<ResizeLinodePayload>({
106106
initialValues: {
@@ -164,7 +164,7 @@ export const LinodeResize = (props: Props) => {
164164
}
165165
}, [error]);
166166

167-
const tableDisabled = hostMaintenance || isLinodesGrantReadOnly;
167+
const tableDisabled = hostMaintenance || !permissions.resize_linode;
168168

169169
const submitButtonDisabled =
170170
Boolean(typeToConfirmPreference) && confirmationText !== linode?.label;
@@ -195,7 +195,7 @@ export const LinodeResize = (props: Props) => {
195195
<CircleProgress />
196196
) : (
197197
<form onSubmit={formik.handleSubmit} ref={formRef}>
198-
{isLinodesGrantReadOnly && <LinodePermissionsError />}
198+
{!permissions.resize_linode && <LinodePermissionsError />}
199199
{hostMaintenance && <HostMaintenanceError />}
200200
{disksError && (
201201
<Notice

0 commit comments

Comments
 (0)