Skip to content

Commit 5aa012f

Browse files
fix(rbac): hide frontend when permission framework was disabled (janus-idp#1493)
* fix(rbac): hide frontend when permission framework was disabled Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): fix playwright test Signed-off-by: Oleksandr Andriienko <[email protected]> * fix(rbac): update rbac backend plugin Signed-off-by: Oleksandr Andriienko <[email protected]> --------- Signed-off-by: Oleksandr Andriienko <[email protected]>
1 parent 1f38503 commit 5aa012f

File tree

6 files changed

+152
-63
lines changed

6 files changed

+152
-63
lines changed

.github/workflows/pr-playwright.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ jobs:
6464
cd packages/backend
6565
readarray folders < <(echo $PLUGINS | sed 's/[][]//g' | sed 's/,/ /g')
6666
67+
# enable permission support and RBAC plugins
68+
printf "\npermission:\n enabled: true\n" >> ${root}/app-config.yaml
69+
6770
# Start backend
6871
echo "Starting backend"
6972
logfile=$(mktemp)

packages/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"@backstage/plugin-search-backend-module-pg": "^0.5.25",
3939
"@backstage/plugin-search-backend-node": "^1.2.20",
4040
"@backstage/plugin-techdocs-backend": "^1.10.3",
41-
"@janus-idp/backstage-plugin-rbac-backend": "^2.7.0",
41+
"@janus-idp/backstage-plugin-rbac-backend": "^2.8.1",
4242
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.1.14",
4343
"@backstage/plugin-search-backend-module-catalog": "^0.1.21",
4444
"@backstage/plugin-search-backend-module-techdocs": "^0.1.21",
Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,45 @@
11
import React from 'react';
2-
import { useAsync } from 'react-use';
32

43
import { SidebarItem } from '@backstage/core-components';
4+
import { ApiRef, configApiRef } from '@backstage/core-plugin-api';
55

66
import { render, screen } from '@testing-library/react';
77

8+
import { rbacApiRef } from '../api/RBACBackendClient';
89
import { Administration } from './Administration';
910

11+
let useAsyncMockResult: { loading: boolean; value?: { status: string } } = {
12+
loading: false,
13+
value: { status: 'Authorized' },
14+
};
15+
1016
jest.mock('react-use', () => ({
1117
...jest.requireActual('react-use'),
12-
useAsync: jest.fn(),
18+
useAsync: jest.fn().mockImplementation((fn: any, _deps: any) => {
19+
fn();
20+
return useAsyncMockResult;
21+
}),
1322
}));
1423

24+
const mockGetUserAuthorization = jest.fn();
25+
26+
const configMock = {
27+
getOptionalBoolean: jest.fn(() => true),
28+
};
29+
1530
jest.mock('@backstage/core-plugin-api', () => ({
1631
...jest.requireActual('@backstage/core-plugin-api'),
17-
useApi: jest.fn(),
32+
useApi: jest.fn((apiRef: ApiRef<any>) => {
33+
if (apiRef === rbacApiRef) {
34+
return {
35+
getUserAuthorization: mockGetUserAuthorization,
36+
};
37+
}
38+
if (apiRef === configApiRef) {
39+
return configMock;
40+
}
41+
return undefined;
42+
}),
1843
}));
1944

2045
jest.mock('@backstage/core-components', () => ({
@@ -29,8 +54,6 @@ const mockedSidebarItem = SidebarItem as jest.MockedFunction<
2954
typeof SidebarItem
3055
>;
3156

32-
const mockGetUserAuthorization = jest.fn();
33-
3457
const mockUseApi = jest.fn(() => ({
3558
getUserAuthorization: mockGetUserAuthorization,
3659
}));
@@ -41,56 +64,50 @@ describe('Administration component', () => {
4164
beforeEach(() => {
4265
mockGetUserAuthorization.mockClear();
4366
mockUseApi.mockClear();
44-
(useAsync as jest.Mock).mockClear();
4567
mockRbacApiRef.mockClear();
4668
mockedSidebarItem.mockClear();
4769
});
4870

4971
it('renders Administration sidebar item if user is authorized', async () => {
50-
(useAsync as jest.Mock).mockReturnValueOnce({
51-
loading: false,
52-
value: { status: 'Authorized' },
53-
});
54-
55-
(useAsync as jest.Mock).mockImplementation(() => ({
56-
...mockUseApi(),
57-
getUserAuthorization: mockGetUserAuthorization,
58-
}));
59-
6072
render(<Administration />);
6173
expect(mockedSidebarItem).toHaveBeenCalled();
6274
expect(screen.queryByText('Administration')).toBeInTheDocument();
75+
expect(mockGetUserAuthorization).toHaveBeenCalledTimes(1);
6376
});
6477

6578
it('does not render Administration sidebar item if user is not authorized', async () => {
66-
(useAsync as jest.Mock).mockReturnValueOnce({
79+
useAsyncMockResult = {
6780
loading: false,
6881
value: { status: 'Unauthorized' },
69-
});
70-
71-
(useAsync as jest.Mock).mockImplementation(() => ({
72-
...mockUseApi(),
73-
getUserAuthorization: mockGetUserAuthorization,
74-
}));
82+
};
7583

7684
render(<Administration />);
7785
expect(mockedSidebarItem).not.toHaveBeenCalled();
86+
expect(mockGetUserAuthorization).toHaveBeenCalledTimes(1);
7887
expect(screen.queryByText('Administration')).toBeNull();
7988
});
8089

8190
it('does not render Administration sidebar item if user loading state is true', async () => {
82-
(useAsync as jest.Mock).mockReturnValueOnce({
91+
useAsyncMockResult = {
8392
loading: true,
84-
value: null,
85-
});
93+
value: undefined,
94+
};
8695

87-
(useAsync as jest.Mock).mockImplementation(() => ({
88-
...mockUseApi(),
89-
getUserAuthorization: mockGetUserAuthorization,
90-
}));
96+
render(<Administration />);
97+
expect(mockedSidebarItem).not.toHaveBeenCalled();
98+
expect(screen.queryByText('Administration')).toBeNull();
99+
});
100+
101+
it('does not render Administration sidebar item if plugin is disabled in the configuration', async () => {
102+
useAsyncMockResult = {
103+
loading: false,
104+
value: { status: 'Authorized' },
105+
};
106+
configMock.getOptionalBoolean.mockReturnValueOnce(false);
91107

92108
render(<Administration />);
93109
expect(mockedSidebarItem).not.toHaveBeenCalled();
110+
expect(mockGetUserAuthorization).toHaveBeenCalledTimes(1);
94111
expect(screen.queryByText('Administration')).toBeNull();
95112
});
96113
});

plugins/rbac/src/components/Administration.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import React from 'react';
22
import { useAsync } from 'react-use';
33

44
import { SidebarItem } from '@backstage/core-components';
5-
import { IconComponent, useApi } from '@backstage/core-plugin-api';
5+
import {
6+
configApiRef,
7+
IconComponent,
8+
useApi,
9+
} from '@backstage/core-plugin-api';
610

711
import AdminPanelSettingsOutlinedIcon from '@mui/icons-material/AdminPanelSettingsOutlined';
812

@@ -15,7 +19,10 @@ export const Administration = () => {
1519
[],
1620
);
1721

18-
if (!isUserLoading) {
22+
const config = useApi(configApiRef);
23+
const isRBACPluginEnabled = config.getOptionalBoolean('permission.enabled');
24+
25+
if (!isUserLoading && isRBACPluginEnabled) {
1926
return result?.status === 'Authorized' ? (
2027
<SidebarItem
2128
text="Administration"

plugins/rbac/src/components/Router.test.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ import { render, screen } from '@testing-library/react';
77

88
import { Router } from './Router';
99

10+
const configMock = {
11+
getOptionalBoolean: jest.fn(() => true),
12+
};
13+
14+
jest.mock('@backstage/core-plugin-api', () => ({
15+
...jest.requireActual('@backstage/core-plugin-api'),
16+
useApi: jest.fn(() => configMock),
17+
}));
18+
1019
jest.mock('./RbacPage', () => ({
1120
RbacPage: () => <div>RBAC</div>,
1221
}));
@@ -48,6 +57,16 @@ describe('Router component', () => {
4857
expect(screen.queryByText('RBAC')).toBeInTheDocument();
4958
});
5059

60+
it(`should not render RbacPage when path is "/", when plugin is disabled`, () => {
61+
configMock.getOptionalBoolean.mockReturnValueOnce(false);
62+
render(
63+
<MemoryRouter initialEntries={['/']}>
64+
<Router />
65+
</MemoryRouter>,
66+
);
67+
expect(screen.queryByText('RBAC')).not.toBeInTheDocument();
68+
});
69+
5170
it('renders RoleOverviewPage when path matches roleRouteRef', () => {
5271
render(
5372
<MemoryRouter initialEntries={['/roles/user/testns/testname']}>
@@ -58,6 +77,17 @@ describe('Router component', () => {
5877
expect(screen.queryByText('Role')).toBeInTheDocument();
5978
});
6079

80+
it('should not render RoleOverviewPage when path matches roleRouteRef, when plugin is disabled', () => {
81+
configMock.getOptionalBoolean.mockReturnValueOnce(false);
82+
render(
83+
<MemoryRouter initialEntries={['/roles/user/testns/testname']}>
84+
<Router />
85+
</MemoryRouter>,
86+
);
87+
88+
expect(screen.queryByText('Role')).not.toBeInTheDocument();
89+
});
90+
6191
it('renders CreateRolePage with the right permissions when path matches createRoleRouteRef', () => {
6292
render(
6393
<MemoryRouter initialEntries={['/role/new']}>
@@ -74,6 +104,17 @@ describe('Router component', () => {
74104
expect(screen.queryByText('CreateRole')).toBeInTheDocument();
75105
});
76106

107+
it('should not render CreateRolePage with the right permissions when path matches createRoleRouteRef, when plugin is disabled', () => {
108+
configMock.getOptionalBoolean.mockReturnValueOnce(false);
109+
render(
110+
<MemoryRouter initialEntries={['/role/new']}>
111+
<Router />
112+
</MemoryRouter>,
113+
);
114+
115+
expect(screen.queryByText('CreateRole')).not.toBeInTheDocument();
116+
});
117+
77118
it('renders EditRolePage with the right permissions when path matches editRoleRouteRef', () => {
78119
render(
79120
<MemoryRouter initialEntries={['/role/user/testns/testname']}>
@@ -90,4 +131,15 @@ describe('Router component', () => {
90131
);
91132
expect(screen.queryByText('EditRole')).toBeInTheDocument();
92133
});
134+
135+
it('should not render EditRolePage with the right permissions when path matches editRoleRouteRef, when plugin is disabled', () => {
136+
configMock.getOptionalBoolean.mockReturnValueOnce(false);
137+
render(
138+
<MemoryRouter initialEntries={['/role/user/testns/testname']}>
139+
<Router />
140+
</MemoryRouter>,
141+
);
142+
143+
expect(screen.queryByText('EditRole')).not.toBeInTheDocument();
144+
});
93145
});
Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { Route, Routes } from 'react-router-dom';
33

4+
import { configApiRef, useApi } from '@backstage/core-plugin-api';
45
import { RequirePermission } from '@backstage/plugin-permission-react';
56

67
import {
@@ -19,33 +20,42 @@ import { ToastContextProvider } from './ToastContext';
1920
*
2021
* @public
2122
*/
22-
export const Router = ({ useHeader = true }: { useHeader?: boolean }) => (
23-
<ToastContextProvider>
24-
<Routes>
25-
<Route path="/" element={<RbacPage useHeader={useHeader} />} />
26-
<Route path={roleRouteRef.path} element={<RoleOverviewPage />} />
27-
<Route
28-
path={createRoleRouteRef.path}
29-
element={
30-
<RequirePermission
31-
permission={policyEntityCreatePermission}
32-
resourceRef={policyEntityCreatePermission.resourceType}
33-
>
34-
<CreateRolePage />
35-
</RequirePermission>
36-
}
37-
/>
38-
<Route
39-
path={editRoleRouteRef.path}
40-
element={
41-
<RequirePermission
42-
permission={policyEntityUpdatePermission}
43-
resourceRef={policyEntityUpdatePermission.resourceType}
44-
>
45-
<EditRolePage />
46-
</RequirePermission>
47-
}
48-
/>
49-
</Routes>
50-
</ToastContextProvider>
51-
);
23+
export const Router = ({ useHeader = true }: { useHeader?: boolean }) => {
24+
const config = useApi(configApiRef);
25+
const isRBACPluginEnabled = config.getOptionalBoolean('permission.enabled');
26+
27+
if (!isRBACPluginEnabled) {
28+
return null;
29+
}
30+
31+
return (
32+
<ToastContextProvider>
33+
<Routes>
34+
<Route path="/" element={<RbacPage useHeader={useHeader} />} />
35+
<Route path={roleRouteRef.path} element={<RoleOverviewPage />} />
36+
<Route
37+
path={createRoleRouteRef.path}
38+
element={
39+
<RequirePermission
40+
permission={policyEntityCreatePermission}
41+
resourceRef={policyEntityCreatePermission.resourceType}
42+
>
43+
<CreateRolePage />
44+
</RequirePermission>
45+
}
46+
/>
47+
<Route
48+
path={editRoleRouteRef.path}
49+
element={
50+
<RequirePermission
51+
permission={policyEntityUpdatePermission}
52+
resourceRef={policyEntityUpdatePermission.resourceType}
53+
>
54+
<EditRolePage />
55+
</RequirePermission>
56+
}
57+
/>
58+
</Routes>
59+
</ToastContextProvider>
60+
);
61+
};

0 commit comments

Comments
 (0)