mirror of
https://github.com/ansible/awx.git
synced 2026-02-26 23:46:05 -03:30
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:
@@ -12,7 +12,11 @@ import { ErrorBoundary } from 'react-error-boundary';
|
|||||||
import { I18nProvider } from '@lingui/react';
|
import { I18nProvider } from '@lingui/react';
|
||||||
import { i18n } from '@lingui/core';
|
import { i18n } from '@lingui/core';
|
||||||
import { Card, PageSection } from '@patternfly/react-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 { SessionProvider, useSession } from './contexts/Session';
|
||||||
import AppContainer from './components/AppContainer';
|
import AppContainer from './components/AppContainer';
|
||||||
import Background from './components/Background';
|
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 AuthorizedRoutes = ({ routeConfig }) => {
|
||||||
const isAuthorized = useAuthorizedPath();
|
const isAuthorized = useAuthorizedPath();
|
||||||
const match = useRouteMatch();
|
const match = useRouteMatch();
|
||||||
@@ -150,9 +165,7 @@ function App() {
|
|||||||
</Route>
|
</Route>
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<ConfigProvider>
|
<ConfigProvider>
|
||||||
<AppContainer navRouteConfig={getRouteConfig()}>
|
<RenderAppContainer />
|
||||||
<AuthorizedRoutes routeConfig={getRouteConfig()} />
|
|
||||||
</AppContainer>
|
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class NavExpandableGroup extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { groupId, groupTitle, routes } = this.props;
|
const { groupId, groupTitle, routes } = this.props;
|
||||||
|
|
||||||
if (routes.length === 1) {
|
if (routes.length === 1 && groupId === 'settings') {
|
||||||
const [{ path }] = routes;
|
const [{ path }] = routes;
|
||||||
return (
|
return (
|
||||||
<NavItem itemId={groupId} isActive={this.isActivePath(path)} key={path}>
|
<NavItem itemId={groupId} isActive={this.isActivePath(path)} key={path}>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useRouteMatch } from 'react-router-dom';
|
|||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
import { ConfigAPI, MeAPI } from '../api';
|
import { ConfigAPI, MeAPI, UsersAPI, OrganizationsAPI } from '../api';
|
||||||
import useRequest, { useDismissableError } from '../util/useRequest';
|
import useRequest, { useDismissableError } from '../util/useRequest';
|
||||||
import AlertModal from '../components/AlertModal';
|
import AlertModal from '../components/AlertModal';
|
||||||
import ErrorDetail from '../components/ErrorDetail';
|
import ErrorDetail from '../components/ErrorDetail';
|
||||||
@@ -35,9 +35,32 @@ export const ConfigProvider = ({ children }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
] = await Promise.all([ConfigAPI.read(), MeAPI.read()]);
|
] = 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);
|
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 = () => {
|
export const useAuthorizedPath = () => {
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const subscriptionMgmtRoute = useRouteMatch({
|
const subscriptionMgmtRoute = useRouteMatch({
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import Users from './screens/User';
|
|||||||
import WorkflowApprovals from './screens/WorkflowApproval';
|
import WorkflowApprovals from './screens/WorkflowApproval';
|
||||||
import { Jobs } from './screens/Job';
|
import { Jobs } from './screens/Job';
|
||||||
|
|
||||||
function getRouteConfig() {
|
function getRouteConfig(userProfile = {}) {
|
||||||
return [
|
let routeConfig = [
|
||||||
{
|
{
|
||||||
groupTitle: <Trans>Views</Trans>,
|
groupTitle: <Trans>Views</Trans>,
|
||||||
groupId: 'views_group',
|
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;
|
export default getRouteConfig;
|
||||||
|
|||||||
248
awx/ui_next/src/routeConfig.test.jsx
Normal file
248
awx/ui_next/src/routeConfig.test.jsx
Normal 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',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user