Fix (#6971) UI : Add Permission Provider (#6972)

* Fix (#6971)  UI : Add Permission Provider

* Fix description unit test

* Fix unit tests

* Addressing review comments

* Add checkPermission util

* Add permission for protected route

* Clean up

* Fix rule condition validation issue
This commit is contained in:
Sachin Chaurasiya 2022-08-27 16:05:57 +05:30 committed by GitHub
parent 5cefe1bfbb
commit 1081254f7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 424 additions and 66 deletions

View File

@ -31,6 +31,7 @@ import 'react-toastify/dist/ReactToastify.min.css';
import { AuthProvider } from './authentication/auth-provider/AuthProvider'; import { AuthProvider } from './authentication/auth-provider/AuthProvider';
import Appbar from './components/app-bar/Appbar'; import Appbar from './components/app-bar/Appbar';
import GlobalSearchProvider from './components/GlobalSearchProvider/GlobalSearchProvider'; import GlobalSearchProvider from './components/GlobalSearchProvider/GlobalSearchProvider';
import PermissionProvider from './components/PermissionProvider/PermissionProvider';
import WebSocketProvider from './components/web-scoket/web-scoket.provider'; import WebSocketProvider from './components/web-scoket/web-scoket.provider';
import { toastOptions } from './constants/toast.constants'; import { toastOptions } from './constants/toast.constants';
import ErrorBoundry from './ErrorBoundry/ErrorBoundry'; import ErrorBoundry from './ErrorBoundry/ErrorBoundry';
@ -56,12 +57,14 @@ const App: FunctionComponent = () => {
<Router> <Router>
<ErrorBoundry> <ErrorBoundry>
<AuthProvider childComponentType={AppRouter}> <AuthProvider childComponentType={AppRouter}>
<PermissionProvider>
<WebSocketProvider> <WebSocketProvider>
<GlobalSearchProvider> <GlobalSearchProvider>
<Appbar /> <Appbar />
<AppRouter /> <AppRouter />
</GlobalSearchProvider> </GlobalSearchProvider>
</WebSocketProvider> </WebSocketProvider>
</PermissionProvider>
</AuthProvider> </AuthProvider>
</ErrorBoundry> </ErrorBoundry>
</Router> </Router>

View File

@ -30,9 +30,11 @@ import {
GlobalSettingOptions, GlobalSettingOptions,
GlobalSettingsMenuCategory, GlobalSettingsMenuCategory,
} from '../../constants/globalSettings.constants'; } from '../../constants/globalSettings.constants';
import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { JWTTokenExpiry, User } from '../../generated/entity/teams/user'; import { JWTTokenExpiry, User } from '../../generated/entity/teams/user';
import { EntityReference } from '../../generated/type/entityReference'; import { EntityReference } from '../../generated/type/entityReference';
import { getEntityName, requiredField } from '../../utils/CommonUtils'; import { getEntityName, requiredField } from '../../utils/CommonUtils';
import { checkPemission } from '../../utils/PermissionsUtils';
import { getSettingPath } from '../../utils/RouterUtils'; import { getSettingPath } from '../../utils/RouterUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils'; import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
@ -43,6 +45,8 @@ import { reactSingleSelectCustomStyle } from '../common/react-select-component/r
import TitleBreadcrumb from '../common/title-breadcrumb/title-breadcrumb.component'; import TitleBreadcrumb from '../common/title-breadcrumb/title-breadcrumb.component';
import PageLayout, { leftPanelAntCardStyle } from '../containers/PageLayout'; import PageLayout, { leftPanelAntCardStyle } from '../containers/PageLayout';
import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
import { UserDetails } from '../Users/Users.interface'; import { UserDetails } from '../Users/Users.interface';
interface BotsDetailProp extends HTMLAttributes<HTMLDivElement> { interface BotsDetailProp extends HTMLAttributes<HTMLDivElement> {
@ -61,6 +65,7 @@ const BotDetails: FC<BotsDetailProp> = ({
updateBotsDetails, updateBotsDetails,
revokeTokenHandler, revokeTokenHandler,
}) => { }) => {
const { permissions } = usePermissionProvider();
const [displayName, setDisplayName] = useState(botsData.displayName); const [displayName, setDisplayName] = useState(botsData.displayName);
const [isDisplayNameEdit, setIsDisplayNameEdit] = useState(false); const [isDisplayNameEdit, setIsDisplayNameEdit] = useState(false);
const [isDescriptionEdit, setIsDescriptionEdit] = useState(false); const [isDescriptionEdit, setIsDescriptionEdit] = useState(false);
@ -72,6 +77,23 @@ const BotDetails: FC<BotsDetailProp> = ({
const [generateToken, setGenerateToken] = useState<boolean>(false); const [generateToken, setGenerateToken] = useState<boolean>(false);
const [selectedExpiry, setSelectedExpiry] = useState('7'); const [selectedExpiry, setSelectedExpiry] = useState('7');
const editAllPermission = checkPemission(
Operation.EditAll,
ResourceEntity.BOT,
permissions
);
const displayNamePermission = checkPemission(
Operation.EditDisplayName,
ResourceEntity.BOT,
permissions
);
const descriptionPermission = checkPemission(
Operation.EditDescription,
ResourceEntity.BOT,
permissions
);
const getJWTTokenExpiryOptions = () => { const getJWTTokenExpiryOptions = () => {
return Object.keys(JWTTokenExpiry).map((expiry) => { return Object.keys(JWTTokenExpiry).map((expiry) => {
const expiryValue = JWTTokenExpiry[expiry as keyof typeof JWTTokenExpiry]; const expiryValue = JWTTokenExpiry[expiry as keyof typeof JWTTokenExpiry];
@ -209,13 +231,19 @@ const BotDetails: FC<BotsDetailProp> = ({
Add display name Add display name
</span> </span>
)} )}
{(displayNamePermission || editAllPermission) && (
<button <button
className="tw-ml-2 focus:tw-outline-none" className="tw-ml-2 focus:tw-outline-none"
data-testid="edit-displayName" data-testid="edit-displayName"
onClick={() => setIsDisplayNameEdit(true)}> onClick={() => setIsDisplayNameEdit(true)}>
<SVGIcons alt="edit" icon="icon-edit" title="Edit" width="16px" /> <SVGIcons
alt="edit"
icon="icon-edit"
title="Edit"
width="16px"
/>
</button> </button>
)}
</Fragment> </Fragment>
)} )}
</div> </div>
@ -226,9 +254,9 @@ const BotDetails: FC<BotsDetailProp> = ({
return ( return (
<div className="tw--ml-5"> <div className="tw--ml-5">
<Description <Description
hasEditAccess
description={botsData.description || ''} description={botsData.description || ''}
entityName={getEntityName(botsData as unknown as EntityReference)} entityName={getEntityName(botsData as unknown as EntityReference)}
hasEditAccess={descriptionPermission || editAllPermission}
isEdit={isDescriptionEdit} isEdit={isDescriptionEdit}
onCancel={() => setIsDescriptionEdit(false)} onCancel={() => setIsDescriptionEdit(false)}
onDescriptionEdit={() => setIsDescriptionEdit(true)} onDescriptionEdit={() => setIsDescriptionEdit(true)}
@ -407,7 +435,7 @@ const BotDetails: FC<BotsDetailProp> = ({
<h6 className="tw-mb-2 tw-self-center"> <h6 className="tw-mb-2 tw-self-center">
{generateToken ? 'Generate JWT token' : 'JWT Token'} {generateToken ? 'Generate JWT token' : 'JWT Token'}
</h6> </h6>
{!generateToken ? ( {!generateToken && editAllPermission ? (
<div className="tw-flex"> <div className="tw-flex">
<Button <Button
data-testid="generate-token" data-testid="generate-token"

View File

@ -68,6 +68,26 @@ const mockProp = {
updateBotsDetails, updateBotsDetails,
}; };
jest.mock('../PermissionProvider/PermissionProvider', () => ({
usePermissionProvider: jest.fn().mockReturnValue({
permissions: {
bot: {
Create: true,
Delete: true,
ViewAll: true,
EditAll: true,
EditDescription: true,
EditDisplayName: true,
EditCustomFields: true,
},
},
}),
}));
jest.mock('../../utils/PermissionsUtils', () => ({
checkPemission: jest.fn().mockReturnValue(true),
}));
jest.mock('../../axiosAPIs/userAPI', () => { jest.mock('../../axiosAPIs/userAPI', () => {
return { return {
generateUserToken: jest generateUserToken: jest

View File

@ -24,22 +24,33 @@ import {
} from '../../constants/constants'; } from '../../constants/constants';
import { EntityType } from '../../enums/entity.enum'; import { EntityType } from '../../enums/entity.enum';
import { Bot } from '../../generated/entity/bot'; import { Bot } from '../../generated/entity/bot';
import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { Include } from '../../generated/type/include'; import { Include } from '../../generated/type/include';
import { Paging } from '../../generated/type/paging'; import { Paging } from '../../generated/type/paging';
import { checkPemission } from '../../utils/PermissionsUtils';
import SVGIcons, { Icons } from '../../utils/SvgUtils'; import SVGIcons, { Icons } from '../../utils/SvgUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
import DeleteWidgetModal from '../common/DeleteWidget/DeleteWidgetModal'; import DeleteWidgetModal from '../common/DeleteWidget/DeleteWidgetModal';
import NextPrevious from '../common/next-previous/NextPrevious'; import NextPrevious from '../common/next-previous/NextPrevious';
import Loader from '../Loader/Loader'; import Loader from '../Loader/Loader';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
import { BotListV1Props } from './BotListV1.interfaces'; import { BotListV1Props } from './BotListV1.interfaces';
const BotListV1 = ({ showDeleted }: BotListV1Props) => { const BotListV1 = ({ showDeleted }: BotListV1Props) => {
const { permissions } = usePermissionProvider();
const [botUsers, setBotUsers] = useState<Bot[]>([]); const [botUsers, setBotUsers] = useState<Bot[]>([]);
const [paging, setPaging] = useState<Paging>({} as Paging); const [paging, setPaging] = useState<Paging>({} as Paging);
const [selectedUser, setSelectedUser] = useState<Bot>(); const [selectedUser, setSelectedUser] = useState<Bot>();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE); const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE);
const deletePermission = checkPemission(
Operation.Delete,
ResourceEntity.BOT,
permissions
);
/** /**
* *
* @param after - Pagination value if passed data will be fetched post cursor value * @param after - Pagination value if passed data will be fetched post cursor value
@ -90,8 +101,15 @@ const BotListV1 = ({ showDeleted }: BotListV1Props) => {
width: 90, width: 90,
render: (_, record) => ( render: (_, record) => (
<Space align="center" size={8}> <Space align="center" size={8}>
<Tooltip placement="bottom" title="Delete"> <Tooltip
placement="bottom"
title={
deletePermission
? 'Delete'
: 'You do not have permissions to perform this action.'
}>
<Button <Button
disabled={!deletePermission}
icon={ icon={
<SVGIcons <SVGIcons
alt="Delete" alt="Delete"

View File

@ -0,0 +1,47 @@
import { Operation } from '../../generated/entity/policies/accessControl/resourcePermission';
export type UIPermission = {
[key in ResourceEntity]: OperationPermission;
};
export type OperationPermission = {
[key in Operation]: boolean;
};
export enum ResourceEntity {
ALL = 'all',
BOT = 'bot',
CHART = 'chart',
DASHBOARD = 'dashboard',
DASHBOARDSERVICE = 'dashboardService',
DATABASE = 'database',
DATABASESCHEMA = 'databaseSchema',
DATABASESERVICE = 'databaseService',
EVENTS = 'events',
FEED = 'feed',
GLOSSARY = 'glossary',
GLOSSARYTERM = 'glossaryTerm',
INGESTIONPIPELINE = 'ingestionPipeline',
LOCATION = 'location',
MESSAGINGSERVICE = 'messagingService',
METRICS = 'metrics',
MLMODEL = 'mlmodel',
MLMODELSERVICE = 'mlmodelService',
PIPELINE = 'pipeline',
PIPELINESERVICE = 'pipelineService',
POLICY = 'policy',
REPORT = 'report',
ROLE = 'role',
STORAGESERVICE = 'storageService',
TABLE = 'table',
TAG = 'tag',
TAGCATEGORY = 'tagCategory',
TEAM = 'team',
TESTCASE = 'testCase',
TESTDEFINITION = 'testDefinition',
TESTSUITE = 'testSuite',
TOPIC = 'topic',
TYPE = 'type',
USER = 'user',
WEBHOOK = 'webhook',
}

View File

@ -0,0 +1,89 @@
/*
* Copyright 2021 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AxiosError } from 'axios';
import { observer } from 'mobx-react';
import React, {
createContext,
FC,
ReactNode,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import AppState from '../../AppState';
import { getLoggedInUserPermissions } from '../../axiosAPIs/miscAPI';
import { getUIPermission } from '../../utils/PermissionsUtils';
import { showErrorToast } from '../../utils/ToastUtils';
import { UIPermission } from './PermissionProvider.interface';
/**
* Permission Context
* Returns ResourcePermission List for loggedIn User
* @returns PermissionMap
*/
export const PermissionContext = createContext<{
permissions: UIPermission;
}>({ permissions: {} as UIPermission });
interface PermissionProviderProps {
children: ReactNode;
}
/**
*
* @param children:ReactNode
* @returns JSX
*/
const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
const [permissions, setPermissions] = useState<UIPermission>(
{} as UIPermission
);
// Update current user details of AppState change
const currentUser = useMemo(() => {
return AppState.getCurrentUserDetails();
}, [AppState.userDetails, AppState.nonSecureUserDetails]);
/**
* Fetch permission for logged in user
*/
const fetchLoggedInUserPermissions = async () => {
try {
const response = await getLoggedInUserPermissions();
setPermissions(getUIPermission(response.data || []));
} catch (error) {
showErrorToast(error as AxiosError);
}
};
useEffect(() => {
/**
* Only fetch permission if user is logged In
*/
if (currentUser && currentUser.id) {
fetchLoggedInUserPermissions();
}
}, [currentUser]);
return (
<PermissionContext.Provider value={{ permissions }}>
{children}
</PermissionContext.Provider>
);
};
export const usePermissionProvider = () => useContext(PermissionContext);
export default observer(PermissionProvider);

View File

@ -93,9 +93,12 @@ jest.mock('../non-admin-action/NonAdminAction', () => {
describe('Test Description Component', () => { describe('Test Description Component', () => {
it('Check if it has all child elements', async () => { it('Check if it has all child elements', async () => {
const { container } = render(<Description {...mockDescriptionProp} />, { const { container } = render(
<Description {...mockDescriptionProp} hasEditAccess />,
{
wrapper: MemoryRouter, wrapper: MemoryRouter,
}); }
);
const descriptionContainer = await findByTestId(container, 'description'); const descriptionContainer = await findByTestId(container, 'description');
const editDescriptionButton = await findByTestId( const editDescriptionButton = await findByTestId(
@ -236,4 +239,19 @@ describe('Test Description Component', () => {
// should render requestDescription, as description thread and description are empty value // should render requestDescription, as description thread and description are empty value
expect(requestDescription).toBeInTheDocument(); expect(requestDescription).toBeInTheDocument();
}); });
it('Should not show edit button if hasEditAccess is false', async () => {
const { container } = render(
<Description {...mockDescriptionProp} hasEditAccess={false} />,
{
wrapper: MemoryRouter,
}
);
const descriptionContainer = await findByTestId(container, 'description');
const editDescriptionButton = queryByTestId(container, 'edit-description');
expect(descriptionContainer).toBeInTheDocument();
expect(editDescriptionButton).toBeNull();
});
}); });

View File

@ -17,15 +17,11 @@ import { isUndefined } from 'lodash';
import { EntityFieldThreads } from 'Models'; import { EntityFieldThreads } from 'Models';
import React, { FC, Fragment } from 'react'; import React, { FC, Fragment } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useAuthContext } from '../../../authentication/auth-provider/AuthProvider';
import { EntityField } from '../../../constants/feed.constants'; import { EntityField } from '../../../constants/feed.constants';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { ThreadType } from '../../../generated/entity/feed/thread'; import { ThreadType } from '../../../generated/entity/feed/thread';
import { Operation } from '../../../generated/entity/policies/accessControl/rule';
import { useAuth } from '../../../hooks/authHooks';
import { isTaskSupported } from '../../../utils/CommonUtils'; import { isTaskSupported } from '../../../utils/CommonUtils';
import { getEntityFeedLink } from '../../../utils/EntityUtils'; import { getEntityFeedLink } from '../../../utils/EntityUtils';
import { hasPemission } from '../../../utils/PermissionsUtils';
import SVGIcons, { Icons } from '../../../utils/SvgUtils'; import SVGIcons, { Icons } from '../../../utils/SvgUtils';
import { import {
getRequestDescriptionPath, getRequestDescriptionPath,
@ -54,9 +50,6 @@ const Description: FC<DescriptionProps> = ({
}) => { }) => {
const history = useHistory(); const history = useHistory();
const { isAdminUser, userPermissions } = useAuth();
const { isAuthDisabled } = useAuthContext();
const thread = entityFieldThreads?.[0]; const thread = entityFieldThreads?.[0];
const tasks = entityFieldTasks?.[0]; const tasks = entityFieldTasks?.[0];
@ -72,19 +65,6 @@ const Description: FC<DescriptionProps> = ({
); );
}; };
const checkPermission = () => {
return (
isAdminUser ||
Boolean(hasEditAccess) ||
hasPemission(
Operation.EditDescription,
entityType as EntityType,
userPermissions
) ||
isAuthDisabled
);
};
const handleUpdate = () => { const handleUpdate = () => {
onDescriptionEdit && onDescriptionEdit(); onDescriptionEdit && onDescriptionEdit();
}; };
@ -179,7 +159,7 @@ const Description: FC<DescriptionProps> = ({
const DescriptionActions = () => { const DescriptionActions = () => {
return !isReadOnly ? ( return !isReadOnly ? (
<div className={classNames('tw-w-5 tw-min-w-max tw-flex tw--mt-1')}> <div className={classNames('tw-w-5 tw-min-w-max tw-flex tw--mt-1')}>
{checkPermission() && ( {hasEditAccess && (
<button <button
className="tw-w-7 tw-h-8 tw-flex-none focus:tw-outline-none" className="tw-w-7 tw-h-8 tw-flex-none focus:tw-outline-none"
data-testid="edit-description" data-testid="edit-description"

View File

@ -11,13 +11,18 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Col, Row, Space, Switch } from 'antd'; import { Button, Col, Empty, Row, Space, Switch } from 'antd';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import BotListV1 from '../../components/BotListV1/BotListV1.component'; import BotListV1 from '../../components/BotListV1/BotListV1.component';
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../components/PermissionProvider/PermissionProvider.interface';
import { getCreateUserPath } from '../../constants/constants'; import { getCreateUserPath } from '../../constants/constants';
import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { checkPemission } from '../../utils/PermissionsUtils';
export const BotsPageV1 = () => { export const BotsPageV1 = () => {
const { permissions } = usePermissionProvider();
const history = useHistory(); const history = useHistory();
const [showDeleted, setShowDeleted] = useState(false); const [showDeleted, setShowDeleted] = useState(false);
@ -29,8 +34,22 @@ export const BotsPageV1 = () => {
setShowDeleted(checked); setShowDeleted(checked);
}; };
const viewAllPermission = checkPemission(
Operation.ViewAll,
ResourceEntity.BOT,
permissions
);
const createPermission = checkPemission(
Operation.Create,
ResourceEntity.BOT,
permissions
);
return ( return (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{viewAllPermission ? (
<>
<Col flex={1} /> <Col flex={1} />
<Col> <Col>
<Space size={16}> <Space size={16}>
@ -43,14 +62,21 @@ export const BotsPageV1 = () => {
/> />
<label htmlFor="switch-deleted">Show deleted</label> <label htmlFor="switch-deleted">Show deleted</label>
</Space> </Space>
{createPermission && (
<Button type="primary" onClick={handleAddBotClick}> <Button type="primary" onClick={handleAddBotClick}>
Add Bot Add Bot
</Button> </Button>
)}
</Space> </Space>
</Col> </Col>
<Col span={24}> <Col span={24}>
<BotListV1 showDeleted={showDeleted} /> <BotListV1 showDeleted={showDeleted} />
</Col> </Col>
</>
) : (
<Empty description="You do not have permission to view data" />
)}
</Row> </Row>
); );
}; };

View File

@ -80,11 +80,12 @@ const AddPolicyPage = () => {
}; };
const handleSumbit = async () => { const handleSumbit = async () => {
const { condition, ...rest } = ruleData;
const data: CreatePolicy = { const data: CreatePolicy = {
name, name,
description, description,
policyType: PolicyType.AccessControl, policyType: PolicyType.AccessControl,
rules: [ruleData], rules: [condition ? { ...rest, condition } : rest],
}; };
try { try {

View File

@ -87,9 +87,10 @@ const AddRulePage = () => {
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
const { condition, ...rest } = ruleData;
const patch = compare(policy, { const patch = compare(policy, {
...policy, ...policy,
rules: [...policy.rules, ruleData], rules: [...policy.rules, condition ? { ...rest, condition } : rest],
}); });
try { try {
const data = await patchPolicy(patch, policy.id); const data = await patchPolicy(patch, policy.id);

View File

@ -98,11 +98,14 @@ const EditRulePage = () => {
const existingRules = policy.rules; const existingRules = policy.rules;
const updatedRules = existingRules.map((rule) => { const updatedRules = existingRules.map((rule) => {
if (rule.name === ruleName) { if (rule.name === ruleName) {
return ruleData; const { condition, ...rest } = ruleData;
return condition ? { ...rest, condition } : rest;
} else { } else {
return rule; return rule;
} }
}); });
const patch = compare(policy, { const patch = compare(policy, {
...policy, ...policy,
rules: updatedRules, rules: updatedRules,

View File

@ -17,11 +17,15 @@ import { useAuthContext } from '../authentication/auth-provider/AuthProvider';
import { ROUTES } from '../constants/constants'; import { ROUTES } from '../constants/constants';
import { useAuth } from '../hooks/authHooks'; import { useAuth } from '../hooks/authHooks';
const AdminProtectedRoute = (routeProps: RouteProps) => { interface AdminProtectedRouteProps extends RouteProps {
hasPermission: boolean;
}
const AdminProtectedRoute = (routeProps: AdminProtectedRouteProps) => {
const { isAdminUser } = useAuth(); const { isAdminUser } = useAuth();
const { isAuthDisabled, isAuthenticated } = useAuthContext(); const { isAuthDisabled, isAuthenticated } = useAuthContext();
if (isAuthDisabled || isAdminUser) { if (isAuthDisabled || isAdminUser || routeProps.hasPermission) {
return <Route {...routeProps} />; return <Route {...routeProps} />;
} else if (isAuthenticated) { } else if (isAuthenticated) {
return <Redirect to={ROUTES.NOT_FOUND} />; return <Redirect to={ROUTES.NOT_FOUND} />;

View File

@ -13,11 +13,15 @@
import React from 'react'; import React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom'; import { Redirect, Route, Switch } from 'react-router-dom';
import { usePermissionProvider } from '../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { import {
GlobalSettingOptions, GlobalSettingOptions,
GlobalSettingsMenuCategory, GlobalSettingsMenuCategory,
} from '../constants/globalSettings.constants'; } from '../constants/globalSettings.constants';
import { Operation } from '../generated/entity/policies/policy';
import TeamsPage from '../pages/teams/TeamsPage'; import TeamsPage from '../pages/teams/TeamsPage';
import { checkPemission } from '../utils/PermissionsUtils';
import { getSettingCategoryPath, getSettingPath } from '../utils/RouterUtils'; import { getSettingCategoryPath, getSettingPath } from '../utils/RouterUtils';
import AdminProtectedRoute from './AdminProtectedRoute'; import AdminProtectedRoute from './AdminProtectedRoute';
import withSuspenseFallback from './withSuspenseFallback'; import withSuspenseFallback from './withSuspenseFallback';
@ -64,6 +68,8 @@ const SlackSettingsPage = withSuspenseFallback(
); );
const GlobalSettingRouter = () => { const GlobalSettingRouter = () => {
const { permissions } = usePermissionProvider();
return ( return (
<Switch> <Switch>
<Route exact path={getSettingPath()}> <Route exact path={getSettingPath()}>
@ -97,6 +103,11 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={RolesListPage} component={RolesListPage}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.ROLE,
permissions
)}
path={getSettingPath( path={getSettingPath(
GlobalSettingsMenuCategory.ACCESS, GlobalSettingsMenuCategory.ACCESS,
GlobalSettingOptions.ROLES GlobalSettingOptions.ROLES
@ -106,6 +117,11 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={RolesDetailPage} component={RolesDetailPage}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.ROLE,
permissions
)}
path={getSettingPath( path={getSettingPath(
GlobalSettingsMenuCategory.ACCESS, GlobalSettingsMenuCategory.ACCESS,
GlobalSettingOptions.ROLES, GlobalSettingOptions.ROLES,
@ -119,6 +135,11 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={PoliciesListPage} component={PoliciesListPage}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.POLICY,
permissions
)}
path={getSettingPath( path={getSettingPath(
GlobalSettingsMenuCategory.ACCESS, GlobalSettingsMenuCategory.ACCESS,
GlobalSettingOptions.POLICIES GlobalSettingOptions.POLICIES
@ -127,6 +148,11 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={PoliciesDetailPage} component={PoliciesDetailPage}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.POLICY,
permissions
)}
path={getSettingPath( path={getSettingPath(
GlobalSettingsMenuCategory.ACCESS, GlobalSettingsMenuCategory.ACCESS,
GlobalSettingOptions.POLICIES, GlobalSettingOptions.POLICIES,
@ -136,12 +162,22 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={UserListPageV1} component={UserListPageV1}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.USER,
permissions
)}
path={getSettingCategoryPath(GlobalSettingsMenuCategory.MEMBERS)} path={getSettingCategoryPath(GlobalSettingsMenuCategory.MEMBERS)}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={WebhooksPageV1} component={WebhooksPageV1}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.WEBHOOK,
permissions
)}
path={getSettingPath( path={getSettingPath(
GlobalSettingsMenuCategory.INTEGRATIONS, GlobalSettingsMenuCategory.INTEGRATIONS,
GlobalSettingOptions.WEBHOOK GlobalSettingOptions.WEBHOOK
@ -150,6 +186,11 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={BotsPageV1} component={BotsPageV1}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.BOT,
permissions
)}
path={getSettingPath( path={getSettingPath(
GlobalSettingsMenuCategory.INTEGRATIONS, GlobalSettingsMenuCategory.INTEGRATIONS,
GlobalSettingOptions.BOTS GlobalSettingOptions.BOTS
@ -159,6 +200,11 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={SlackSettingsPage} component={SlackSettingsPage}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.WEBHOOK,
permissions
)}
path={getSettingPath( path={getSettingPath(
GlobalSettingsMenuCategory.INTEGRATIONS, GlobalSettingsMenuCategory.INTEGRATIONS,
GlobalSettingOptions.SLACK GlobalSettingOptions.SLACK
@ -174,6 +220,11 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={CustomPropertiesPageV1} component={CustomPropertiesPageV1}
hasPermission={checkPemission(
Operation.ViewAll,
ResourceEntity.ALL,
permissions
)}
path={getSettingCategoryPath( path={getSettingCategoryPath(
GlobalSettingsMenuCategory.CUSTOM_ATTRIBUTES GlobalSettingsMenuCategory.CUSTOM_ATTRIBUTES
)} )}

View File

@ -11,13 +11,22 @@
* limitations under the License. * limitations under the License.
*/ */
import {
OperationPermission,
ResourceEntity,
UIPermission,
} from '../components/PermissionProvider/PermissionProvider.interface';
import { EntityType } from '../enums/entity.enum'; import { EntityType } from '../enums/entity.enum';
import { import {
Access, Access,
Permission,
ResourcePermission, ResourcePermission,
} from '../generated/entity/policies/accessControl/resourcePermission'; } from '../generated/entity/policies/accessControl/resourcePermission';
import { Operation } from '../generated/entity/policies/policy'; import { Operation } from '../generated/entity/policies/policy';
/**
* TODO: Remove this method once we have new permission structure everywhere
*/
export const hasPemission = ( export const hasPemission = (
operation: Operation, operation: Operation,
entityType: EntityType, entityType: EntityType,
@ -34,4 +43,64 @@ export const hasPemission = (
return currentPermission?.access === Access.Allow; return currentPermission?.access === Access.Allow;
}; };
/**
*
* @param operation operation like Edit, Delete
* @param resourceType Resource type like "bot", "table"
* @param permissions UIPermission
* @returns boolean - true/false
*/
export const checkPemission = (
operation: Operation,
resourceType: ResourceEntity,
permissions: UIPermission
) => {
const allResource = permissions.all;
const entityResource = permissions[resourceType];
/**
* If allresource is present then check for permission and return it
*/
if (allResource) {
return allResource.All || allResource[operation];
}
return entityResource[operation];
};
/**
*
* @param permission ResourcePermission
* @returns OperationPermission - {Operation:true/false}
*/
export const getOperationPermissions = (
permission: ResourcePermission
): OperationPermission => {
return permission.permissions.reduce(
(acc: OperationPermission, curr: Permission) => {
return {
...acc,
[curr.operation as Operation]: curr.access === Access.Allow,
};
},
{} as OperationPermission
);
};
/**
*
* @param permissions Take ResourcePermission list
* @returns UIPermission
*/
export const getUIPermission = (
permissions: ResourcePermission[]
): UIPermission => {
return permissions.reduce((acc: UIPermission, curr: ResourcePermission) => {
return {
...acc,
[curr.resource as ResourceEntity]: getOperationPermissions(curr),
};
}, {} as UIPermission);
};
export const LIST_CAP = 1; export const LIST_CAP = 1;