Add RBAC rules to the side-nav

Add RBAC rules to the side-nav

System Admin
System Auditor
Org Admin
Notification Admin
Execution Environment Admin
Normal User

Those are the user profiles taken in consideration when displaying the
side-nav.

See: https://github.com/ansible/awx/issues/4426
This commit is contained in:
nixocio 2021-05-20 15:54:55 -04:00 committed by Shane McDonald
parent 04f6fe6cd2
commit 4d2c64ebb4
No known key found for this signature in database
GPG Key ID: 6F374AF6E9EB9374
5 changed files with 328 additions and 10 deletions

View File

@ -12,7 +12,11 @@ import { ErrorBoundary } from 'react-error-boundary';
import { I18nProvider } from '@lingui/react';
import { i18n } from '@lingui/core';
import { Card, PageSection } from '@patternfly/react-core';
import { ConfigProvider, useAuthorizedPath } from './contexts/Config';
import {
ConfigProvider,
useAuthorizedPath,
useUserProfile,
} from './contexts/Config';
import { SessionProvider, useSession } from './contexts/Session';
import AppContainer from './components/AppContainer';
import Background from './components/Background';
@ -38,6 +42,17 @@ function ErrorFallback({ error }) {
);
}
const RenderAppContainer = () => {
const userProfile = useUserProfile();
const navRouteConfig = getRouteConfig(userProfile);
return (
<AppContainer navRouteConfig={navRouteConfig}>
<AuthorizedRoutes routeConfig={navRouteConfig} />
</AppContainer>
);
};
const AuthorizedRoutes = ({ routeConfig }) => {
const isAuthorized = useAuthorizedPath();
const match = useRouteMatch();
@ -150,9 +165,7 @@ function App() {
</Route>
<ProtectedRoute>
<ConfigProvider>
<AppContainer navRouteConfig={getRouteConfig()}>
<AuthorizedRoutes routeConfig={getRouteConfig()} />
</AppContainer>
<RenderAppContainer />
</ConfigProvider>
</ProtectedRoute>
</Switch>

View File

@ -27,7 +27,7 @@ class NavExpandableGroup extends Component {
render() {
const { groupId, groupTitle, routes } = this.props;
if (routes.length === 1) {
if (routes.length === 1 && groupId === 'settings') {
const [{ path }] = routes;
return (
<NavItem itemId={groupId} isActive={this.isActivePath(path)} key={path}>

View File

@ -3,7 +3,7 @@ import { useRouteMatch } from 'react-router-dom';
import { t } from '@lingui/macro';
import { ConfigAPI, MeAPI } from '../api';
import { ConfigAPI, MeAPI, UsersAPI, OrganizationsAPI } from '../api';
import useRequest, { useDismissableError } from '../util/useRequest';
import AlertModal from '../components/AlertModal';
import ErrorDetail from '../components/ErrorDetail';
@ -35,9 +35,32 @@ export const ConfigProvider = ({ children }) => {
},
},
] = await Promise.all([ConfigAPI.read(), MeAPI.read()]);
return { ...data, me };
const [
{
data: { count: adminOrgCount },
},
{
data: { count: notifAdminCount },
},
{
data: { count: execEnvAdminCount },
},
] = await Promise.all([
UsersAPI.readAdminOfOrganizations(me?.id),
OrganizationsAPI.read({
page_size: 1,
role_level: 'notification_admin_role',
}),
OrganizationsAPI.read({
page_size: 1,
role_level: 'execution_environment_admin_role',
}),
]);
return { ...data, me, adminOrgCount, notifAdminCount, execEnvAdminCount };
}, []),
{}
{ adminOrgCount: 0, notifAdminCount: 0, execEnvAdminCount: 0 }
);
const { error, dismissError } = useDismissableError(configError);
@ -77,6 +100,17 @@ export const ConfigProvider = ({ children }) => {
);
};
export const useUserProfile = () => {
const config = useConfig();
return {
isSuperUser: !!config.me?.is_superuser,
isSystemAuditor: !!config.me?.is_system_auditor,
isOrgAdmin: config.adminOrgCount,
isNotificationAdmin: config.notifAdminCount,
isExecEnvAdmin: config.execEnvAdminCount,
};
};
export const useAuthorizedPath = () => {
const config = useConfig();
const subscriptionMgmtRoute = useRouteMatch({

View File

@ -22,8 +22,8 @@ import Users from './screens/User';
import WorkflowApprovals from './screens/WorkflowApproval';
import { Jobs } from './screens/Job';
function getRouteConfig() {
return [
function getRouteConfig(userProfile = {}) {
let routeConfig = [
{
groupTitle: <Trans>Views</Trans>,
groupId: 'views_group',
@ -155,6 +155,29 @@ function getRouteConfig() {
],
},
];
const deleteRoute = name => {
routeConfig.forEach(group => {
group.routes = group.routes.filter(({ path }) => !path.includes(name));
});
routeConfig = routeConfig.filter(groups => groups.routes.length);
};
const deleteRouteGroup = name => {
routeConfig = routeConfig.filter(({ groupId }) => !groupId.includes(name));
};
if (userProfile?.isSuperUser || userProfile?.isSystemAuditor)
return routeConfig;
deleteRouteGroup('settings');
deleteRoute('management_jobs');
deleteRoute('credential_types');
if (userProfile?.isOrgAdmin) return routeConfig;
deleteRoute('applications');
deleteRoute('instance_groups');
if (!userProfile?.isNotificationAdmin) deleteRoute('notification_templates');
return routeConfig;
}
export default getRouteConfig;

View File

@ -0,0 +1,248 @@
import getRouteConfig from './routeConfig';
const userProfile = {
isSuperUser: false,
isSystemAuditor: false,
isOrgAdmin: false,
isNotificationAdmin: false,
isExecEnvAdmin: false,
};
const filterPaths = sidebar => {
const visibleRoutes = [];
sidebar.forEach(({ routes }) => {
routes.forEach(route => {
visibleRoutes.push(route.path);
});
});
return visibleRoutes;
};
describe('getRouteConfig', () => {
test('routes for system admin', () => {
const sidebar = getRouteConfig({ ...userProfile, isSuperUser: true });
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/credential_types',
'/notification_templates',
'/management_jobs',
'/instance_groups',
'/applications',
'/execution_environments',
'/settings',
]);
});
test('routes for system auditor', () => {
const sidebar = getRouteConfig({ ...userProfile, isSystemAuditor: true });
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/credential_types',
'/notification_templates',
'/management_jobs',
'/instance_groups',
'/applications',
'/execution_environments',
'/settings',
]);
});
test('routes for org admin', () => {
const sidebar = getRouteConfig({ ...userProfile, isOrgAdmin: true });
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/notification_templates',
'/instance_groups',
'/applications',
'/execution_environments',
]);
});
test('routes for notifications admin', () => {
const sidebar = getRouteConfig({
...userProfile,
isNotificationAdmin: true,
});
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/notification_templates',
'/execution_environments',
]);
});
test('routes for execution environments admin', () => {
const sidebar = getRouteConfig({ ...userProfile, isExecEnvAdmin: true });
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/execution_environments',
]);
});
test('routes for regular users', () => {
const sidebar = getRouteConfig(userProfile);
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/execution_environments',
]);
});
test('routes for execution environment admins and notification admin', () => {
const sidebar = getRouteConfig({
...userProfile,
isExecEnvAdmin: true,
isNotificationAdmin: true,
});
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/notification_templates',
'/execution_environments',
]);
});
test('routes for execution environment admins and organization admins', () => {
const sidebar = getRouteConfig({
...userProfile,
isExecEnvAdmin: true,
isOrgAdmin: true,
});
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/notification_templates',
'/instance_groups',
'/applications',
'/execution_environments',
]);
});
test('routes for notification admins and organization admins', () => {
const sidebar = getRouteConfig({
...userProfile,
isNotificationAdmin: true,
isOrgAdmin: true,
});
const filteredPaths = filterPaths(sidebar);
expect(filteredPaths).toEqual([
'/home',
'/jobs',
'/schedules',
'/activity_stream',
'/workflow_approvals',
'/templates',
'/credentials',
'/projects',
'/inventories',
'/hosts',
'/organizations',
'/users',
'/teams',
'/notification_templates',
'/instance_groups',
'/applications',
'/execution_environments',
]);
});
});