Skip to content

Commit c68e4c6

Browse files
feat: [UIE-10184] - IAM: add permissions to UI (#13409)
* feat: [UIE-10184] - IAM: add permissions to UI * typo * Added changeset: IAM Parent/Child: add permissions to UI
1 parent 51bc332 commit c68e4c6

File tree

16 files changed

+234
-17
lines changed

16 files changed

+234
-17
lines changed

packages/api-v4/src/iam/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export type AccountAdmin =
125125
| 'view_account_login'
126126
| 'view_account_settings'
127127
| 'view_child_account'
128+
| 'view_default_delegate_access'
128129
| 'view_enrolled_beta_program'
129130
| 'view_lock'
130131
| 'view_network_usage'
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Added
3+
---
4+
5+
IAM Parent/Child: add permissions to UI ([#13409](https://github.com/linode/manager/pull/13409))

packages/manager/src/features/IAM/Delegations/AccountDelegations.test.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,17 @@ const mocks = vi.hoisted(() => ({
1313
mockUseGetChildAccountsQuery: vi.fn(),
1414
useParams: vi.fn().mockReturnValue({}),
1515
useSearch: vi.fn().mockReturnValue({}),
16+
usePermissions: vi.fn().mockReturnValue({}),
1617
}));
1718

19+
vi.mock('src/features/IAM/hooks/usePermissions', async () => {
20+
const actual = await vi.importActual('src/features/IAM/hooks/usePermissions');
21+
return {
22+
...actual,
23+
usePermissions: mocks.usePermissions,
24+
};
25+
});
26+
1827
vi.mock('@tanstack/react-router', async () => {
1928
const actual = await vi.importActual('@tanstack/react-router');
2029
return {
@@ -53,6 +62,10 @@ describe('AccountDelegations', () => {
5362
data: { data: mockDelegations, results: mockDelegations.length },
5463
isLoading: false,
5564
});
65+
mocks.usePermissions.mockReturnValue({
66+
data: { list_all_child_accounts: true },
67+
isLoading: false,
68+
});
5669
});
5770

5871
it('should render the delegations table with data', async () => {
@@ -93,4 +106,23 @@ describe('AccountDelegations', () => {
93106
expect(emptyElement).toBeInTheDocument();
94107
});
95108
});
109+
110+
it('should not render if user does not have permissions', () => {
111+
mocks.usePermissions.mockReturnValue({
112+
data: {
113+
list_all_child_accounts: false,
114+
},
115+
isLoading: false,
116+
});
117+
118+
renderWithTheme(<AccountDelegations />, {
119+
flags: { iamDelegation: { enabled: true }, iam: { enabled: true } },
120+
initialRoute: '/iam',
121+
});
122+
expect(
123+
screen.queryByText(
124+
'You do not have permission to view account delegations.'
125+
)
126+
).toBeVisible();
127+
});
96128
});

packages/manager/src/features/IAM/Delegations/AccountDelegations.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useGetChildAccountsQuery } from '@linode/queries';
2-
import { CircleProgress, Paper, Stack } from '@linode/ui';
2+
import { CircleProgress, Notice, Paper, Stack } from '@linode/ui';
33
import { useMediaQuery, useTheme } from '@mui/material';
44
import { useNavigate, useSearch } from '@tanstack/react-router';
55
import React from 'react';
@@ -10,13 +10,18 @@ import { useOrderV2 } from 'src/hooks/useOrderV2';
1010
import { usePaginationV2 } from 'src/hooks/usePaginationV2';
1111

1212
import { useIsIAMDelegationEnabled } from '../hooks/useIsIAMEnabled';
13+
import { usePermissions } from '../hooks/usePermissions';
1314
import { AccountDelegationsTable } from './AccountDelegationsTable';
1415

1516
const DELEGATIONS_ROUTE = '/iam/delegations';
1617

1718
export const AccountDelegations = () => {
1819
const navigate = useNavigate();
1920
const { isIAMDelegationEnabled } = useIsIAMDelegationEnabled();
21+
const { data: permissions, isLoading: isPermissionsLoading } = usePermissions(
22+
'account',
23+
['list_all_child_accounts']
24+
);
2025

2126
const { company } = useSearch({
2227
from: '/iam',
@@ -78,12 +83,22 @@ export const AccountDelegations = () => {
7883
});
7984
};
8085

81-
if (isLoading) {
86+
if (isLoading || isPermissionsLoading) {
8287
return <CircleProgress />;
8388
}
89+
90+
if (!permissions?.list_all_child_accounts) {
91+
return (
92+
<Notice variant="error">
93+
You do not have permission to view account delegations.
94+
</Notice>
95+
);
96+
}
97+
8498
if (!isIAMDelegationEnabled) {
8599
return null;
86100
}
101+
87102
return (
88103
<Paper sx={(theme) => ({ marginTop: theme.tokens.spacing.S16 })}>
89104
<Stack

packages/manager/src/features/IAM/Roles/Defaults/DefaultEntityAccess.test.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const queryMocks = vi.hoisted(() => ({
1717
useIsDefaultDelegationRolesForChildAccount: vi
1818
.fn()
1919
.mockReturnValue({ isDefaultDelegationRolesForChildAccount: true }),
20+
usePermissions: vi.fn().mockReturnValue({}),
2021
}));
2122

2223
vi.mock('src/features/IAM/hooks/useDelegationRole', () => ({
@@ -41,6 +42,14 @@ vi.mock('src/queries/entities/entities', async () => {
4142
};
4243
});
4344

45+
vi.mock('src/features/IAM/hooks/usePermissions', async () => {
46+
const actual = await vi.importActual('src/features/IAM/hooks/usePermissions');
47+
return {
48+
...actual,
49+
usePermissions: queryMocks.usePermissions,
50+
};
51+
});
52+
4453
vi.mock('@tanstack/react-router', async () => {
4554
const actual = await vi.importActual('@tanstack/react-router');
4655
return {
@@ -51,6 +60,14 @@ vi.mock('@tanstack/react-router', async () => {
5160
});
5261

5362
describe('DefaultEntityAccess', () => {
63+
beforeEach(() => {
64+
vi.clearAllMocks();
65+
66+
queryMocks.usePermissions.mockReturnValue({
67+
data: { view_default_delegate_access: true },
68+
isLoading: false,
69+
});
70+
});
5471
it('should render', async () => {
5572
queryMocks.useGetDefaultDelegationAccessQuery.mockReturnValue({
5673
data: {
@@ -92,4 +109,21 @@ describe('DefaultEntityAccess', () => {
92109
renderWithTheme(<DefaultEntityAccess />);
93110
expect(screen.getByText(ERROR_STATE_TEXT)).toBeVisible();
94111
});
112+
113+
it('should not render if user does not have permissions', () => {
114+
queryMocks.usePermissions.mockReturnValue({
115+
data: {
116+
view_default_delegate_access: false,
117+
},
118+
isLoading: false,
119+
});
120+
121+
renderWithTheme(<DefaultEntityAccess />);
122+
123+
expect(
124+
screen.queryByText(
125+
'You do not have permission to view default entity access for delegate users.'
126+
)
127+
).toBeVisible();
128+
});
95129
});

packages/manager/src/features/IAM/Roles/Defaults/DefaultEntityAccess.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { useGetDefaultDelegationAccessQuery } from '@linode/queries';
22
import {
33
CircleProgress,
44
ErrorState,
5+
Notice,
56
Paper,
67
Stack,
78
Typography,
89
} from '@linode/ui';
910
import * as React from 'react';
1011

12+
import { usePermissions } from '../../hooks/usePermissions';
1113
import { AssignedEntitiesTable } from '../../Shared/AssignedEntitiesTable/AssignedEntitiesTable';
1214
import {
1315
ERROR_STATE_TEXT,
@@ -16,20 +18,35 @@ import {
1618
import { NoAssignedRoles } from '../../Shared/NoAssignedRoles/NoAssignedRoles';
1719

1820
export const DefaultEntityAccess = () => {
21+
const { data: permissions, isLoading: isPermissionsLoading } = usePermissions(
22+
'account',
23+
['view_default_delegate_access']
24+
);
1925
const {
2026
data: defaultAccess,
2127
isLoading: defaultAccessLoading,
2228
error,
23-
} = useGetDefaultDelegationAccessQuery({ enabled: true });
29+
} = useGetDefaultDelegationAccessQuery({
30+
enabled: permissions?.view_default_delegate_access,
31+
});
2432

2533
const hasAssignedEntities = defaultAccess
2634
? defaultAccess.entity_access.length > 0
2735
: false;
2836

29-
if (defaultAccessLoading) {
37+
if (defaultAccessLoading || isPermissionsLoading) {
3038
return <CircleProgress />;
3139
}
3240

41+
if (!permissions?.view_default_delegate_access) {
42+
return (
43+
<Notice variant="error">
44+
You do not have permission to view default entity access for delegate
45+
users.
46+
</Notice>
47+
);
48+
}
49+
3350
if (error) {
3451
return <ErrorState errorText={ERROR_STATE_TEXT} />;
3552
}

packages/manager/src/features/IAM/Roles/Defaults/DefaultRoles.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const queryMocks = vi.hoisted(() => ({
1717
useIsDefaultDelegationRolesForChildAccount: vi
1818
.fn()
1919
.mockReturnValue({ isDefaultDelegationRolesForChildAccount: true }),
20+
usePermissions: vi.fn().mockReturnValue({}),
2021
}));
2122

2223
vi.mock('@tanstack/react-router', async () => {
@@ -35,11 +36,28 @@ vi.mock('@linode/queries', async () => {
3536
queryMocks.useGetDefaultDelegationAccessQuery,
3637
};
3738
});
39+
40+
vi.mock('src/features/IAM/hooks/usePermissions', async () => {
41+
const actual = await vi.importActual('src/features/IAM/hooks/usePermissions');
42+
return {
43+
...actual,
44+
usePermissions: queryMocks.usePermissions,
45+
};
46+
});
47+
3848
vi.mock('src/features/IAM/hooks/useDelegationRole', () => ({
3949
useIsDefaultDelegationRolesForChildAccount:
4050
queryMocks.useIsDefaultDelegationRolesForChildAccount,
4151
}));
4252
describe('DefaultRoles', () => {
53+
beforeEach(() => {
54+
vi.clearAllMocks();
55+
56+
queryMocks.usePermissions.mockReturnValue({
57+
data: { view_default_delegate_access: true },
58+
isLoading: false,
59+
});
60+
});
4361
it('should render', async () => {
4462
queryMocks.useGetDefaultDelegationAccessQuery.mockReturnValue({
4563
data: {
@@ -84,4 +102,21 @@ describe('DefaultRoles', () => {
84102
renderWithTheme(<DefaultRoles />);
85103
expect(screen.getByText(ERROR_STATE_TEXT)).toBeVisible();
86104
});
105+
106+
it('should not render if user does not have permissions', () => {
107+
queryMocks.usePermissions.mockReturnValue({
108+
data: {
109+
view_default_delegate_access: false,
110+
},
111+
isLoading: false,
112+
});
113+
114+
renderWithTheme(<DefaultRoles />);
115+
116+
expect(
117+
screen.queryByText(
118+
'You do not have permission to view default roles for delegate users.'
119+
)
120+
).toBeVisible();
121+
});
87122
});

packages/manager/src/features/IAM/Roles/Defaults/DefaultRoles.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { useGetDefaultDelegationAccessQuery } from '@linode/queries';
2-
import { CircleProgress, ErrorState, Paper, Typography } from '@linode/ui';
2+
import {
3+
CircleProgress,
4+
ErrorState,
5+
Notice,
6+
Paper,
7+
Typography,
8+
} from '@linode/ui';
39
import * as React from 'react';
410

11+
import { usePermissions } from '../../hooks/usePermissions';
512
import { AssignedRolesTable } from '../../Shared/AssignedRolesTable/AssignedRolesTable';
613
import {
714
ERROR_STATE_TEXT,
@@ -10,20 +17,35 @@ import {
1017
import { NoAssignedRoles } from '../../Shared/NoAssignedRoles/NoAssignedRoles';
1118

1219
export const DefaultRoles = () => {
20+
const { data: permissions, isLoading: isPermissionsLoading } = usePermissions(
21+
'account',
22+
['view_default_delegate_access']
23+
);
1324
const {
1425
data: defaultRolesData,
1526
isLoading: defaultRolesLoading,
1627
error,
17-
} = useGetDefaultDelegationAccessQuery({ enabled: true });
28+
} = useGetDefaultDelegationAccessQuery({
29+
enabled: permissions?.view_default_delegate_access,
30+
});
31+
1832
const hasAssignedRoles = defaultRolesData
1933
? defaultRolesData.account_access.length > 0 ||
2034
defaultRolesData.entity_access.length > 0
2135
: false;
2236

23-
if (defaultRolesLoading) {
37+
if (defaultRolesLoading || isPermissionsLoading) {
2438
return <CircleProgress />;
2539
}
2640

41+
if (!permissions?.view_default_delegate_access) {
42+
return (
43+
<Notice variant="error">
44+
You do not have permission to view default roles for delegate users.
45+
</Notice>
46+
);
47+
}
48+
2749
if (error) {
2850
return <ErrorState errorText={ERROR_STATE_TEXT} />;
2951
}

packages/manager/src/features/IAM/Roles/Roles.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export const RolesLanding = () => {
1919
permissions?.list_role_permissions
2020
);
2121
const { isIAMDelegationEnabled } = useIsIAMDelegationEnabled();
22-
const { isChildUserType, isProfileLoading } = useDelegationRole();
22+
const { isChildUserType, isProfileLoading, isDelegateUserType } =
23+
useDelegationRole();
2324

2425
const { roles } = React.useMemo(() => {
2526
if (!accountRoles) {
@@ -41,7 +42,9 @@ export const RolesLanding = () => {
4142

4243
return (
4344
<>
44-
{isChildUserType && isIAMDelegationEnabled && <DefaultRolesPanel />}
45+
{(isChildUserType || isDelegateUserType) && isIAMDelegationEnabled && (
46+
<DefaultRolesPanel />
47+
)}
4548
<Paper sx={(theme) => ({ marginTop: theme.tokens.spacing.S16 })}>
4649
<Typography variant="h2">Roles</Typography>
4750
<RolesTable roles={roles} />

0 commit comments

Comments
 (0)