feat(ui): update global settings page with new roles and policy (#7002)

* feat(ui): update global settings page with new roles and policy

* Modify the permission util and remove access prop from create bot and user page

* Fix typo

* Fix console errors

* fix unit tests

* Add getEntityPermission to provider

* Remove admin and auth disable check from global setting

* Add check for edit description.

Co-authored-by: Sachin Chaurasiya <sachinchaurasiyachotey87@gmail.com>
This commit is contained in:
Chirag Madlani 2022-08-29 16:35:42 +05:30 committed by GitHub
parent 15c83e3f2d
commit cc0449e506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 771 additions and 257 deletions

View File

@ -13,9 +13,11 @@
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { CreatePolicy } from '../generated/api/policies/createPolicy'; import { CreatePolicy } from '../generated/api/policies/createPolicy';
import { CreateRole } from '../generated/api/teams/createRole'; import { CreateRole } from '../generated/api/teams/createRole';
import { ResourceDescriptor } from '../generated/entity/policies/accessControl/resourceDescriptor'; import { ResourceDescriptor } from '../generated/entity/policies/accessControl/resourceDescriptor';
import { ResourcePermission } from '../generated/entity/policies/accessControl/resourcePermission';
import { Policy } from '../generated/entity/policies/policy'; import { Policy } from '../generated/entity/policies/policy';
import { Role } from '../generated/entity/teams/role'; import { Role } from '../generated/entity/teams/role';
import { Function } from '../generated/type/function'; import { Function } from '../generated/type/function';
@ -154,3 +156,14 @@ export const validateRuleCondition = async (condition: string) => {
*/ */
return response; return response;
}; };
export const getEntityPermissionById = async (
resource: ResourceEntity,
entityId: string
) => {
const response = await APIClient.get<ResourcePermission>(
`/permissions/${resource}/${entityId}`
);
return response.data;
};

View File

@ -13,12 +13,19 @@
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from 'antd';
import { Store } from 'antd/lib/form/interface'; import { Store } from 'antd/lib/form/interface';
import classNames from 'classnames'; import classNames from 'classnames';
import cryptoRandomString from 'crypto-random-string-with-promisify-polyfill'; import cryptoRandomString from 'crypto-random-string-with-promisify-polyfill';
import { cloneDeep, isEqual, isNil } from 'lodash'; import { cloneDeep, isEmpty, isEqual, isNil } from 'lodash';
import { EditorContentRef } from 'Models'; import { EditorContentRef } from 'Models';
import React, { FunctionComponent, useCallback, useRef, useState } from 'react'; import React, {
FunctionComponent,
useCallback,
useMemo,
useRef,
useState,
} from 'react';
import { ROUTES, TERM_ALL } from '../../constants/constants'; import { ROUTES, TERM_ALL } from '../../constants/constants';
import { import {
GlobalSettingOptions, GlobalSettingOptions,
@ -27,6 +34,7 @@ import {
import { import {
CONFIGURE_SLACK_TEXT, CONFIGURE_SLACK_TEXT,
CONFIGURE_WEBHOOK_TEXT, CONFIGURE_WEBHOOK_TEXT,
NO_PERMISSION_FOR_ACTION,
} from '../../constants/HelperTextUtil'; } from '../../constants/HelperTextUtil';
import { UrlEntityCharRegEx } from '../../constants/regex.constants'; import { UrlEntityCharRegEx } from '../../constants/regex.constants';
import { FormSubmitType } from '../../enums/form.enum'; import { FormSubmitType } from '../../enums/form.enum';
@ -37,12 +45,14 @@ import {
Filters, Filters,
} from '../../generated/api/events/createWebhook'; } from '../../generated/api/events/createWebhook';
import { WebhookType } from '../../generated/entity/events/webhook'; import { WebhookType } from '../../generated/entity/events/webhook';
import { Operation } from '../../generated/entity/policies/policy';
import { import {
errorMsg, errorMsg,
getSeparator, getSeparator,
isValidUrl, isValidUrl,
requiredField, requiredField,
} from '../../utils/CommonUtils'; } from '../../utils/CommonUtils';
import { checkPermission } 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 { Button } from '../buttons/Button/Button'; import { Button } from '../buttons/Button/Button';
@ -52,6 +62,8 @@ import TitleBreadcrumb from '../common/title-breadcrumb/title-breadcrumb.compone
import PageLayout from '../containers/PageLayout'; import PageLayout from '../containers/PageLayout';
import Loader from '../Loader/Loader'; import Loader from '../Loader/Loader';
import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
import { AddWebhookProps } from './AddWebhook.interface'; import { AddWebhookProps } from './AddWebhook.interface';
import SelectComponent from './select-component'; import SelectComponent from './select-component';
import { import {
@ -163,6 +175,29 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
const [generatingSecret, setGeneratingSecret] = useState<boolean>(false); const [generatingSecret, setGeneratingSecret] = useState<boolean>(false);
const [isDelete, setIsDelete] = useState<boolean>(false); const [isDelete, setIsDelete] = useState<boolean>(false);
const { permissions } = usePermissionProvider();
const editWebhookPermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(Operation.EditAll, ResourceEntity.WEBHOOK, permissions),
[permissions]
);
const addWebhookPermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(Operation.Create, ResourceEntity.WEBHOOK, permissions),
[permissions]
);
const deleteWebhookPermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(Operation.Delete, ResourceEntity.WEBHOOK, permissions),
[permissions]
);
const handleDelete = () => { const handleDelete = () => {
if (data) { if (data) {
onDelete && onDelete(data.id); onDelete && onDelete(data.id);
@ -259,7 +294,7 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
}; };
const getDeleteButton = () => { const getDeleteButton = () => {
return allowAccess ? ( return (
<> <>
{deleteState === 'waiting' ? ( {deleteState === 'waiting' ? (
<Button <Button
@ -271,24 +306,32 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
<Loader size="small" type="default" /> <Loader size="small" type="default" />
</Button> </Button>
) : ( ) : (
<Button <Tooltip
className={classNames({ placement="left"
'tw-opacity-40': !allowAccess, title={
})} deleteWebhookPermission ? 'Delete' : NO_PERMISSION_FOR_ACTION
data-testid="delete-webhook" }>
size="regular" <Button
theme="primary" data-testid="delete-webhook"
variant="text" disabled={!deleteWebhookPermission}
onClick={() => setIsDelete(true)}> size="regular"
Delete theme="primary"
</Button> variant="text"
onClick={() => setIsDelete(true)}>
Delete
</Button>
</Tooltip>
)} )}
</> </>
) : null; );
}; };
const getSaveButton = () => { const getSaveButton = () => {
return allowAccess ? ( const savePermission =
(mode === 'add' && addWebhookPermission) ||
(mode === 'edit' && editWebhookPermission);
return (
<> <>
{saveState === 'waiting' ? ( {saveState === 'waiting' ? (
<Button <Button
@ -309,20 +352,23 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
<FontAwesomeIcon icon="check" /> <FontAwesomeIcon icon="check" />
</Button> </Button>
) : ( ) : (
<Button <Tooltip
className={classNames('tw-w-16 tw-h-10', { placement="left"
'tw-opacity-40': !allowAccess, title={savePermission ? 'Save' : NO_PERMISSION_FOR_ACTION}>
})} <Button
data-testid="save-webhook" className={classNames('tw-w-16 tw-h-10')}
size="regular" data-testid="save-webhook"
theme="primary" disabled={!savePermission}
variant="contained" size="regular"
onClick={handleSave}> theme="primary"
Save variant="contained"
</Button> onClick={handleSave}>
Save
</Button>
</Tooltip>
)} )}
</> </>
) : null; );
}; };
const fetchRightPanel = useCallback(() => { const fetchRightPanel = useCallback(() => {
@ -631,7 +677,7 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
</div> </div>
)} )}
</Field> </Field>
{data && isDelete && ( {data && isDelete && deleteWebhookPermission && (
<ConfirmationModal <ConfirmationModal
bodyText={`You want to delete webhook ${data.name} permanently? This action cannot be reverted.`} bodyText={`You want to delete webhook ${data.name} permanently? This action cannot be reverted.`}
cancelText="Cancel" cancelText="Cancel"

View File

@ -34,7 +34,7 @@ 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 { checkPermission } 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';
@ -77,18 +77,18 @@ 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( const editAllPermission = checkPermission(
Operation.EditAll, Operation.EditAll,
ResourceEntity.BOT, ResourceEntity.BOT,
permissions permissions
); );
const displayNamePermission = checkPemission( const displayNamePermission = checkPermission(
Operation.EditDisplayName, Operation.EditDisplayName,
ResourceEntity.BOT, ResourceEntity.BOT,
permissions permissions
); );
const descriptionPermission = checkPemission( const descriptionPermission = checkPermission(
Operation.EditDescription, Operation.EditDescription,
ResourceEntity.BOT, ResourceEntity.BOT,
permissions permissions

View File

@ -85,7 +85,7 @@ jest.mock('../PermissionProvider/PermissionProvider', () => ({
})); }));
jest.mock('../../utils/PermissionsUtils', () => ({ jest.mock('../../utils/PermissionsUtils', () => ({
checkPemission: jest.fn().mockReturnValue(true), checkPermission: jest.fn().mockReturnValue(true),
})); }));
jest.mock('../../axiosAPIs/userAPI', () => { jest.mock('../../axiosAPIs/userAPI', () => {

View File

@ -22,12 +22,13 @@ import {
INITIAL_PAGING_VALUE, INITIAL_PAGING_VALUE,
PAGE_SIZE, PAGE_SIZE,
} from '../../constants/constants'; } from '../../constants/constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
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 { 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 { checkPermission } 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';
@ -45,7 +46,7 @@ const BotListV1 = ({ showDeleted }: BotListV1Props) => {
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( const deletePermission = checkPermission(
Operation.Delete, Operation.Delete,
ResourceEntity.BOT, ResourceEntity.BOT,
permissions permissions
@ -103,11 +104,7 @@ const BotListV1 = ({ showDeleted }: BotListV1Props) => {
<Space align="center" size={8}> <Space align="center" size={8}>
<Tooltip <Tooltip
placement="bottom" placement="bottom"
title={ title={deletePermission ? 'Delete' : NO_PERMISSION_FOR_ACTION}>
deletePermission
? 'Delete'
: 'You do not have permissions to perform this action.'
}>
<Button <Button
disabled={!deletePermission} disabled={!deletePermission}
icon={ icon={

View File

@ -36,7 +36,6 @@ import TeamsSelectable from '../TeamsSelectable/TeamsSelectable';
import { CreateUserProps } from './CreateUser.interface'; import { CreateUserProps } from './CreateUser.interface';
const CreateUser = ({ const CreateUser = ({
allowAccess,
roles, roles,
saveState = 'initial', saveState = 'initial',
onCancel, onCancel,
@ -81,10 +80,6 @@ const CreateUser = ({
const handleValidation = ( const handleValidation = (
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement> event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => { ) => {
if (!allowAccess) {
return;
}
const value = event.target.value; const value = event.target.value;
const eleName = event.target.name; const eleName = event.target.name;
@ -188,7 +183,7 @@ const CreateUser = ({
* @returns Button * @returns Button
*/ */
const getSaveButton = () => { const getSaveButton = () => {
return allowAccess ? ( return (
<> <>
{saveState === 'waiting' ? ( {saveState === 'waiting' ? (
<Button <Button
@ -210,9 +205,7 @@ const CreateUser = ({
</Button> </Button>
) : ( ) : (
<Button <Button
className={classNames('tw-w-16 tw-h-10', { className={classNames('tw-w-16 tw-h-10')}
'tw-opacity-40': !allowAccess,
})}
data-testid="save-user" data-testid="save-user"
size="regular" size="regular"
theme="primary" theme="primary"
@ -222,7 +215,7 @@ const CreateUser = ({
</Button> </Button>
)} )}
</> </>
) : null; );
}; };
return ( return (
@ -310,10 +303,8 @@ const CreateUser = ({
className={classNames('toggle-switch', { open: isAdmin })} className={classNames('toggle-switch', { open: isAdmin })}
data-testid="admin" data-testid="admin"
onClick={() => { onClick={() => {
if (allowAccess) { setIsAdmin((prev) => !prev);
setIsAdmin((prev) => !prev); setIsBot(false);
setIsBot(false);
}
}}> }}>
<div className="switch" /> <div className="switch" />
</div> </div>
@ -324,10 +315,8 @@ const CreateUser = ({
className={classNames('toggle-switch', { open: isBot })} className={classNames('toggle-switch', { open: isBot })}
data-testid="bot" data-testid="bot"
onClick={() => { onClick={() => {
if (allowAccess) { setIsBot((prev) => !prev);
setIsBot((prev) => !prev); setIsAdmin(false);
setIsAdmin(false);
}
}}> }}>
<div className="switch" /> <div className="switch" />
</div> </div>

View File

@ -16,7 +16,6 @@ import { CreateUser } from '../../generated/api/teams/createUser';
import { Role } from '../../generated/entity/teams/role'; import { Role } from '../../generated/entity/teams/role';
export interface CreateUserProps { export interface CreateUserProps {
allowAccess: boolean;
saveState?: LoadingState; saveState?: LoadingState;
roles: Array<Role>; roles: Array<Role>;
onSave: (data: CreateUser) => void; onSave: (data: CreateUser) => void;

View File

@ -37,7 +37,6 @@ jest.mock('../common/rich-text-editor/RichTextEditor', () => {
}); });
const propsValue: CreateUserProps = { const propsValue: CreateUserProps = {
allowAccess: true,
saveState: 'initial', saveState: 'initial',
roles: [], roles: [],
forceBot: false, forceBot: false,

View File

@ -16,23 +16,29 @@ import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { camelCase } from 'lodash'; import { camelCase } from 'lodash';
import React from 'react'; import React from 'react';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { GLOBAL_SETTINGS_MENU } from '../../constants/globalSettings.constants'; import { GLOBAL_SETTINGS_MENU } from '../../constants/globalSettings.constants';
import { useAuth } from '../../hooks/authHooks'; import { Operation } from '../../generated/entity/policies/accessControl/rule';
import { getGlobalSettingMenus } from '../../utils/GlobalSettingsUtils'; import { getGlobalSettingMenus } from '../../utils/GlobalSettingsUtils';
import { checkPermission } from '../../utils/PermissionsUtils';
import { getSettingPath } from '../../utils/RouterUtils'; import { getSettingPath } from '../../utils/RouterUtils';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
const GlobalSettingLeftPanel = () => { const GlobalSettingLeftPanel = () => {
const { tab, settingCategory } = useParams<{ [key: string]: string }>(); const { tab, settingCategory } = useParams<{ [key: string]: string }>();
const { isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext();
const isHasAccess = isAdminUser || isAuthDisabled; const { permissions } = usePermissionProvider();
const viewAllPermission = checkPermission(
Operation.ViewAll,
ResourceEntity.ALL,
permissions
);
const history = useHistory(); const history = useHistory();
const items: ItemType[] = GLOBAL_SETTINGS_MENU.filter(({ isProtected }) => { const items: ItemType[] = GLOBAL_SETTINGS_MENU.filter(({ isProtected }) => {
if (isHasAccess) { if (viewAllPermission) {
return isHasAccess; return viewAllPermission;
} }
return !isProtected; return !isProtected;
@ -44,7 +50,7 @@ const GlobalSettingLeftPanel = () => {
'', '',
items, items,
'group', 'group',
isHasAccess viewAllPermission
); );
}); });

View File

@ -1,3 +1,17 @@
/*
* 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 { Operation } from '../../generated/entity/policies/accessControl/resourcePermission'; import { Operation } from '../../generated/entity/policies/accessControl/resourcePermission';
export type UIPermission = { export type UIPermission = {
@ -13,35 +27,47 @@ export enum ResourceEntity {
BOT = 'bot', BOT = 'bot',
CHART = 'chart', CHART = 'chart',
DASHBOARD = 'dashboard', DASHBOARD = 'dashboard',
DASHBOARDSERVICE = 'dashboardService', DASHBOARD_SERVICE = 'dashboardService',
DATABASE = 'database', DATABASE = 'database',
DATABASESCHEMA = 'databaseSchema', DATABASE_SCHEMA = 'databaseSchema',
DATABASESERVICE = 'databaseService', DATABASE_SERVICE = 'databaseService',
EVENTS = 'events', EVENTS = 'events',
FEED = 'feed', FEED = 'feed',
GLOSSARY = 'glossary', GLOSSARY = 'glossary',
GLOSSARYTERM = 'glossaryTerm', GLOSSARY_TERM = 'glossaryTerm',
INGESTIONPIPELINE = 'ingestionPipeline', INGESTION_PIPELINE = 'ingestionPipeline',
LOCATION = 'location', LOCATION = 'location',
MESSAGINGSERVICE = 'messagingService', MESSAGING_SERVICE = 'messagingService',
METRICS = 'metrics', METRICS = 'metrics',
MLMODEL = 'mlmodel', ML_MODEL = 'mlmodel',
MLMODELSERVICE = 'mlmodelService', ML_MODEL_SERVICE = 'mlmodelService',
PIPELINE = 'pipeline', PIPELINE = 'pipeline',
PIPELINESERVICE = 'pipelineService', PIPELINE_SERVICE = 'pipelineService',
POLICY = 'policy', POLICY = 'policy',
REPORT = 'report', REPORT = 'report',
ROLE = 'role', ROLE = 'role',
STORAGESERVICE = 'storageService', STORAGE_SERVICE = 'storageService',
TABLE = 'table', TABLE = 'table',
TAG = 'tag', TAG = 'tag',
TAGCATEGORY = 'tagCategory', TAG_CATEGORY = 'tagCategory',
TEAM = 'team', TEAM = 'team',
TESTCASE = 'testCase', TEST_CASE = 'testCase',
TESTDEFINITION = 'testDefinition', TEST_DEFINITION = 'testDefinition',
TESTSUITE = 'testSuite', TEST_SUITE = 'testSuite',
TOPIC = 'topic', TOPIC = 'topic',
TYPE = 'type', TYPE = 'type',
USER = 'user', USER = 'user',
WEBHOOK = 'webhook', WEBHOOK = 'webhook',
} }
export interface PermissionContextType {
permissions: UIPermission;
getEntityPermission: (
resource: ResourceEntity,
entityId: string
) => Promise<OperationPermission | AxiosError>;
}
export interface EntityPermissionMap {
[key: string]: OperationPermission;
}

View File

@ -24,18 +24,27 @@ import React, {
} from 'react'; } from 'react';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { getLoggedInUserPermissions } from '../../axiosAPIs/miscAPI'; import { getLoggedInUserPermissions } from '../../axiosAPIs/miscAPI';
import { getUIPermission } from '../../utils/PermissionsUtils'; import { getEntityPermissionById } from '../../axiosAPIs/rolesAPIV1';
import {
getOperationPermissions,
getUIPermission,
} from '../../utils/PermissionsUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
import { UIPermission } from './PermissionProvider.interface'; import {
EntityPermissionMap,
PermissionContextType,
ResourceEntity,
UIPermission,
} from './PermissionProvider.interface';
/** /**
* Permission Context * Permission Context
* Returns ResourcePermission List for loggedIn User * Returns ResourcePermission List for loggedIn User
* @returns PermissionMap * @returns PermissionMap
*/ */
export const PermissionContext = createContext<{ export const PermissionContext = createContext<PermissionContextType>(
permissions: UIPermission; {} as PermissionContextType
}>({ permissions: {} as UIPermission }); );
interface PermissionProviderProps { interface PermissionProviderProps {
children: ReactNode; children: ReactNode;
@ -51,6 +60,9 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
{} as UIPermission {} as UIPermission
); );
const [entitiesPermission, setEntitiesPermission] =
useState<EntityPermissionMap>({} as EntityPermissionMap);
// Update current user details of AppState change // Update current user details of AppState change
const currentUser = useMemo(() => { const currentUser = useMemo(() => {
return AppState.getCurrentUserDetails(); return AppState.getCurrentUserDetails();
@ -68,6 +80,29 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
} }
}; };
const fetchEntityPermission = async (
resource: ResourceEntity,
entityId: string
) => {
try {
const entityPermission = entitiesPermission[entityId];
if (entityPermission) {
return entityPermission;
} else {
const response = await getEntityPermissionById(resource, entityId);
const operationPermission = getOperationPermissions(response);
setEntitiesPermission((prev) => ({
...prev,
[entityId]: operationPermission,
}));
return operationPermission;
}
} catch (error) {
return error as AxiosError;
}
};
useEffect(() => { useEffect(() => {
/** /**
* Only fetch permission if user is logged In * Only fetch permission if user is logged In
@ -78,7 +113,11 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
}, [currentUser]); }, [currentUser]);
return ( return (
<PermissionContext.Provider value={{ permissions }}> <PermissionContext.Provider
value={{
permissions,
getEntityPermission: fetchEntityPermission,
}}>
{children} {children}
</PermissionContext.Provider> </PermissionContext.Provider>
); );

View File

@ -11,8 +11,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { Card, Col, Row } from 'antd'; import { Card, Col, Row, Tooltip } from 'antd';
import React, { Fragment } from 'react'; import { isEmpty } from 'lodash';
import React, { Fragment, useMemo } from 'react';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider'; import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { import {
@ -20,26 +21,32 @@ import {
PAGE_SIZE, PAGE_SIZE,
TITLE_FOR_NON_ADMIN_ACTION, TITLE_FOR_NON_ADMIN_ACTION,
} from '../../constants/constants'; } from '../../constants/constants';
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
import { import {
NoDataFoundPlaceHolder, NoDataFoundPlaceHolder,
servicesDisplayName, servicesDisplayName,
} from '../../constants/services.const'; } from '../../constants/services.const';
import { ServiceCategory } from '../../enums/service.enum'; import { ServiceCategory } from '../../enums/service.enum';
import { Operation } from '../../generated/entity/policies/policy';
import { Paging } from '../../generated/type/paging'; import { Paging } from '../../generated/type/paging';
import { useAuth } from '../../hooks/authHooks';
import { ServicesType } from '../../interface/service.interface'; import { ServicesType } from '../../interface/service.interface';
import { import {
getEntityName, getEntityName,
getServiceLogo, getServiceLogo,
showPagination, showPagination,
} from '../../utils/CommonUtils'; } from '../../utils/CommonUtils';
import { checkPermission } from '../../utils/PermissionsUtils';
import { getAddServicePath } from '../../utils/RouterUtils'; import { getAddServicePath } from '../../utils/RouterUtils';
import { getOptionalFields } from '../../utils/ServiceUtils'; import {
getOptionalFields,
getResourceEntityFromServiceCategory,
} from '../../utils/ServiceUtils';
import { Button } from '../buttons/Button/Button'; import { Button } from '../buttons/Button/Button';
import NextPrevious from '../common/next-previous/NextPrevious'; import NextPrevious from '../common/next-previous/NextPrevious';
import NonAdminAction from '../common/non-admin-action/NonAdminAction'; import NonAdminAction from '../common/non-admin-action/NonAdminAction';
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
import { leftPanelAntCardStyle } from '../containers/PageLayout'; import { leftPanelAntCardStyle } from '../containers/PageLayout';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
interface ServicesProps { interface ServicesProps {
serviceData: ServicesType[]; serviceData: ServicesType[];
@ -56,33 +63,49 @@ const Services = ({
currentPage, currentPage,
onPageChange, onPageChange,
}: ServicesProps) => { }: ServicesProps) => {
const { isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext(); const { isAuthDisabled } = useAuthContext();
const history = useHistory(); const history = useHistory();
const handleAddServiceClick = () => { const handleAddServiceClick = () => {
history.push(getAddServicePath(serviceName)); history.push(getAddServicePath(serviceName));
}; };
const { permissions } = usePermissionProvider();
const addServicePermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(
Operation.Create,
getResourceEntityFromServiceCategory(serviceName),
permissions
),
[permissions, serviceName]
);
return ( return (
<Row data-testid="services-container"> <Row data-testid="services-container">
{serviceData.length ? ( {serviceData.length ? (
<Fragment> <Fragment>
<Col span={24}> <Col span={24}>
<div className="tw-flex tw-justify-end" data-testid="header"> <div className="tw-flex tw-justify-end" data-testid="header">
<NonAdminAction <Tooltip
position="bottom" placement="left"
title={TITLE_FOR_NON_ADMIN_ACTION}> title={
addServicePermission
? 'Add Service'
: NO_PERMISSION_FOR_ACTION
}>
<Button <Button
className="tw-h-8 tw-rounded tw-mb-2" className="tw-h-8 tw-rounded tw-mb-2"
data-testid="add-new-service-button" data-testid="add-new-service-button"
disabled={!isAdminUser && !isAuthDisabled} disabled={!addServicePermission && !isAuthDisabled}
size="small" size="small"
theme="primary" theme="primary"
variant="contained" variant="contained"
onClick={handleAddServiceClick}> onClick={handleAddServiceClick}>
Add New Service Add New Service
</Button> </Button>
</NonAdminAction> </Tooltip>
</div> </div>
</Col> </Col>
<Col span={24}> <Col span={24}>

View File

@ -11,28 +11,28 @@
* limitations under the License. * limitations under the License.
*/ */
import { Card, Col, Row, Select, Space } from 'antd'; import { Card, Col, Row, Select, Space, Tooltip } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { isNil } from 'lodash'; import { isEmpty, isNil } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react'; import React, { FC, useEffect, useMemo, useState } from 'react';
import { PAGE_SIZE } from '../../constants/constants';
import { import {
PAGE_SIZE, NO_PERMISSION_FOR_ACTION,
TITLE_FOR_NON_ADMIN_ACTION,
} from '../../constants/constants';
import {
SLACK_LISTING_TEXT, SLACK_LISTING_TEXT,
WEBHOOK_LISTING_TEXT, WEBHOOK_LISTING_TEXT,
} from '../../constants/HelperTextUtil'; } from '../../constants/HelperTextUtil';
import { WebhookType } from '../../generated/api/events/createWebhook'; import { WebhookType } from '../../generated/api/events/createWebhook';
import { Webhook } from '../../generated/entity/events/webhook'; import { Webhook } from '../../generated/entity/events/webhook';
import { useAuth } from '../../hooks/authHooks'; import { Operation } from '../../generated/entity/policies/policy';
import { checkPermission } from '../../utils/PermissionsUtils';
import { statuses } from '../AddWebhook/WebhookConstants'; import { statuses } from '../AddWebhook/WebhookConstants';
import { Button } from '../buttons/Button/Button'; import { Button } from '../buttons/Button/Button';
import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder';
import NextPrevious from '../common/next-previous/NextPrevious'; import NextPrevious from '../common/next-previous/NextPrevious';
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
import WebhookDataCard from '../common/webhook-data-card/WebhookDataCard'; import WebhookDataCard from '../common/webhook-data-card/WebhookDataCard';
import { leftPanelAntCardStyle } from '../containers/PageLayout'; import { leftPanelAntCardStyle } from '../containers/PageLayout';
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
import { WebhooksV1Props } from './WebhooksV1.interface'; import { WebhooksV1Props } from './WebhooksV1.interface';
import './webhookV1.less'; import './webhookV1.less';
@ -47,9 +47,17 @@ const WebhooksV1: FC<WebhooksV1Props> = ({
onStatusFilter, onStatusFilter,
currentPage, currentPage,
}) => { }) => {
const { isAuthDisabled, isAdminUser } = useAuth();
const [filteredData, setFilteredData] = useState<Array<Webhook>>(data); const [filteredData, setFilteredData] = useState<Array<Webhook>>(data);
const { permissions } = usePermissionProvider();
const addWebhookPermission = useMemo(
() =>
!isEmpty(permissions) &&
checkPermission(Operation.Create, ResourceEntity.WEBHOOK, permissions),
[permissions]
);
const getFilteredWebhooks = () => { const getFilteredWebhooks = () => {
return selectedStatus.length return selectedStatus.length
? data.filter( ? data.filter(
@ -79,21 +87,22 @@ const WebhooksV1: FC<WebhooksV1Props> = ({
<ErrorPlaceHolder> <ErrorPlaceHolder>
<p className="tw-text-center">{message}</p> <p className="tw-text-center">{message}</p>
<p className="tw-text-center"> <p className="tw-text-center">
<NonAdminAction <Tooltip
position="bottom" placement="left"
title={TITLE_FOR_NON_ADMIN_ACTION}> title={
addWebhookPermission ? 'Add Webhook' : NO_PERMISSION_FOR_ACTION
}>
<Button <Button
className={classNames('tw-h-8 tw-rounded tw-my-3', { className={classNames('tw-h-8 tw-rounded tw-my-3')}
'tw-opacity-40': !isAdminUser && !isAuthDisabled,
})}
data-testid="add-webhook-button" data-testid="add-webhook-button"
disabled={!addWebhookPermission}
size="small" size="small"
theme="primary" theme="primary"
variant="contained" variant="contained"
onClick={onAddWebhook}> onClick={onAddWebhook}>
Add {webhookType === WebhookType.Slack ? 'Slack' : 'Webhook'} Add {webhookType === WebhookType.Slack ? 'Slack' : 'Webhook'}
</Button> </Button>
</NonAdminAction> </Tooltip>
</p> </p>
</ErrorPlaceHolder> </ErrorPlaceHolder>
); );
@ -131,14 +140,17 @@ const WebhooksV1: FC<WebhooksV1Props> = ({
className="tw-w-full tw-justify-end" className="tw-w-full tw-justify-end"
size={16}> size={16}>
{filteredData.length > 0 && ( {filteredData.length > 0 && (
<NonAdminAction <Tooltip
position="bottom" placement="left"
title={TITLE_FOR_NON_ADMIN_ACTION}> title={
addWebhookPermission
? 'Add Webhook'
: NO_PERMISSION_FOR_ACTION
}>
<Button <Button
className={classNames('tw-h-8 tw-rounded ', { className={classNames('tw-h-8 tw-rounded ')}
'tw-opacity-40': !isAdminUser && !isAuthDisabled,
})}
data-testid="add-webhook-button" data-testid="add-webhook-button"
disabled={!addWebhookPermission}
size="small" size="small"
theme="primary" theme="primary"
variant="contained" variant="contained"
@ -146,7 +158,7 @@ const WebhooksV1: FC<WebhooksV1Props> = ({
Add{' '} Add{' '}
{webhookType === WebhookType.Slack ? 'Slack' : 'Webhook'} {webhookType === WebhookType.Slack ? 'Slack' : 'Webhook'}
</Button> </Button>
</NonAdminAction> </Tooltip>
)} )}
</Space> </Space>
</Col> </Col>

View File

@ -36,3 +36,8 @@ export const ADD_POLICY_TEXT = `
Policies are assigned to Teams. In OpenMetadata, a Policy is a collection of Rules, which define access based on certain conditions. We support rich SpEL (Spring Expression Language) based conditions. All the operations supported by an Entity are published. Use these fine grained operations to define the conditional Rules for each Policy. Policies are assigned to Teams. In OpenMetadata, a Policy is a collection of Rules, which define access based on certain conditions. We support rich SpEL (Spring Expression Language) based conditions. All the operations supported by an Entity are published. Use these fine grained operations to define the conditional Rules for each Policy.
Create well-defined policies based on conditional rules to build rich access control roles. Create well-defined policies based on conditional rules to build rich access control roles.
`; `;
export const NO_PERMISSION_FOR_ACTION =
'You do not have permissions to perform this action.';
export const NO_PERMISSION_TO_VIEW = 'You do not have permission to view data';

View File

@ -11,7 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Col, Empty, Row, Space, Switch } from 'antd'; import { Button, Col, 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';
@ -19,7 +19,7 @@ import { usePermissionProvider } from '../../components/PermissionProvider/Permi
import { ResourceEntity } from '../../components/PermissionProvider/PermissionProvider.interface'; 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 { Operation } from '../../generated/entity/policies/accessControl/rule';
import { checkPemission } from '../../utils/PermissionsUtils'; import { checkPermission } from '../../utils/PermissionsUtils';
export const BotsPageV1 = () => { export const BotsPageV1 = () => {
const { permissions } = usePermissionProvider(); const { permissions } = usePermissionProvider();
@ -34,13 +34,7 @@ export const BotsPageV1 = () => {
setShowDeleted(checked); setShowDeleted(checked);
}; };
const viewAllPermission = checkPemission( const createPermission = checkPermission(
Operation.ViewAll,
ResourceEntity.BOT,
permissions
);
const createPermission = checkPemission(
Operation.Create, Operation.Create,
ResourceEntity.BOT, ResourceEntity.BOT,
permissions permissions
@ -48,35 +42,29 @@ export const BotsPageV1 = () => {
return ( return (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{viewAllPermission ? ( <Col flex={1} />
<> <Col>
<Col flex={1} /> <Space size={16}>
<Col> <Space align="end" size={5}>
<Space size={16}> <Switch
<Space align="end" size={5}> checked={showDeleted}
<Switch id="switch-deleted"
checked={showDeleted} size="small"
id="switch-deleted" onClick={handleShowDeleted}
size="small" />
onClick={handleShowDeleted} <label htmlFor="switch-deleted">Show deleted</label>
/> </Space>
<label htmlFor="switch-deleted">Show deleted</label>
</Space>
{createPermission && ( {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

@ -17,7 +17,6 @@ import { LoadingState } from 'Models';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
import { createBot } from '../../axiosAPIs/botsAPI'; import { createBot } from '../../axiosAPIs/botsAPI';
import { createUser } from '../../axiosAPIs/userAPI'; import { createUser } from '../../axiosAPIs/userAPI';
import PageContainerV1 from '../../components/containers/PageContainerV1'; import PageContainerV1 from '../../components/containers/PageContainerV1';
@ -30,14 +29,11 @@ import { EntityType } from '../../enums/entity.enum';
import { CreateUser } from '../../generated/api/teams/createUser'; import { CreateUser } from '../../generated/api/teams/createUser';
import { Bot } from '../../generated/entity/bot'; import { Bot } from '../../generated/entity/bot';
import { Role } from '../../generated/entity/teams/role'; import { Role } from '../../generated/entity/teams/role';
import { useAuth } from '../../hooks/authHooks';
import jsonData from '../../jsons/en'; import jsonData from '../../jsons/en';
import { getSettingPath } from '../../utils/RouterUtils'; import { getSettingPath } from '../../utils/RouterUtils';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
const CreateUserPage = () => { const CreateUserPage = () => {
const { isAdminUser } = useAuth();
const { isAuthDisabled } = useAuthContext();
const history = useHistory(); const history = useHistory();
const [roles, setRoles] = useState<Array<Role>>([]); const [roles, setRoles] = useState<Array<Role>>([]);
@ -128,7 +124,6 @@ const CreateUserPage = () => {
<PageContainerV1> <PageContainerV1>
<div className="tw-self-center"> <div className="tw-self-center">
<CreateUserComponent <CreateUserComponent
allowAccess={isAdminUser || isAuthDisabled}
forceBot={Boolean(bot)} forceBot={Boolean(bot)}
roles={roles} roles={roles}
saveState={status} saveState={status}

View File

@ -11,10 +11,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { Col, Row } from 'antd'; import { Col, Empty, Row } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isUndefined } from 'lodash'; import { isEmpty, isUndefined } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { getTypeByFQN, updateType } from '../../axiosAPIs/metadataTypeAPI'; import { getTypeByFQN, updateType } from '../../axiosAPIs/metadataTypeAPI';
@ -23,11 +23,16 @@ import ErrorPlaceHolder from '../../components/common/error-with-placeholder/Err
import TabsPane from '../../components/common/TabsPane/TabsPane'; import TabsPane from '../../components/common/TabsPane/TabsPane';
import { CustomPropertyTable } from '../../components/CustomEntityDetail/CustomPropertyTable'; import { CustomPropertyTable } from '../../components/CustomEntityDetail/CustomPropertyTable';
import Loader from '../../components/Loader/Loader'; import Loader from '../../components/Loader/Loader';
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
import SchemaEditor from '../../components/schema-editor/SchemaEditor'; import SchemaEditor from '../../components/schema-editor/SchemaEditor';
import { getAddCustomPropertyPath } from '../../constants/constants'; import { getAddCustomPropertyPath } from '../../constants/constants';
import { customAttributesPath } from '../../constants/globalSettings.constants'; import { customAttributesPath } from '../../constants/globalSettings.constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import { Operation } from '../../generated/entity/policies/policy';
import { Type } from '../../generated/entity/type'; import { Type } from '../../generated/entity/type';
import jsonData from '../../jsons/en'; import jsonData from '../../jsons/en';
import { getResourceEntityFromCustomProperty } from '../../utils/CustomPropertyUtils';
import { checkPermission } from '../../utils/PermissionsUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
import './CustomPropertiesPageV1.less'; import './CustomPropertiesPageV1.less';
@ -44,6 +49,19 @@ const CustomEntityDetailV1 = () => {
const tabAttributePath = const tabAttributePath =
customAttributesPath[tab as keyof typeof customAttributesPath]; customAttributesPath[tab as keyof typeof customAttributesPath];
const { permissions } = usePermissionProvider();
const viewPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(
Operation.ViewAll,
getResourceEntityFromCustomProperty(tab),
permissions
)
);
}, [permissions, tab]);
const fetchTypeDetail = async (typeFQN: string) => { const fetchTypeDetail = async (typeFQN: string) => {
setIsLoading(true); setIsLoading(true);
try { try {
@ -120,7 +138,7 @@ const CustomEntityDetailV1 = () => {
); );
} }
return ( return viewPermission ? (
<Row <Row
className="tw-my-2" className="tw-my-2"
data-testid="custom-entity-container" data-testid="custom-entity-container"
@ -162,6 +180,12 @@ const CustomEntityDetailV1 = () => {
)} )}
</Col> </Col>
</Row> </Row>
) : (
<Row>
<Col span={24}>
<Empty description={NO_PERMISSION_TO_VIEW} />
</Col>
</Row>
); );
}; };

View File

@ -41,15 +41,18 @@ import Description from '../../../components/common/description/Description';
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component'; import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
import Loader from '../../../components/Loader/Loader'; import Loader from '../../../components/Loader/Loader';
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 { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { Rule } from '../../../generated/api/policies/createPolicy'; import { Rule } from '../../../generated/api/policies/createPolicy';
import { Policy } from '../../../generated/entity/policies/policy'; import { Operation, Policy } from '../../../generated/entity/policies/policy';
import { EntityReference } from '../../../generated/type/entityReference'; import { EntityReference } from '../../../generated/type/entityReference';
import { getEntityName } from '../../../utils/CommonUtils'; import { getEntityName } from '../../../utils/CommonUtils';
import { checkPermission } from '../../../utils/PermissionsUtils';
import { import {
getAddPolicyRulePath, getAddPolicyRulePath,
getEditPolicyRulePath, getEditPolicyRulePath,
@ -147,6 +150,7 @@ const List = ({
const PoliciesDetailPage = () => { const PoliciesDetailPage = () => {
const history = useHistory(); const history = useHistory();
const { fqn } = useParams<{ fqn: string }>(); const { fqn } = useParams<{ fqn: string }>();
const { permissions } = usePermissionProvider();
const [policy, setPolicy] = useState<Policy>({} as Policy); const [policy, setPolicy] = useState<Policy>({} as Policy);
const [isLoading, setLoading] = useState<boolean>(false); const [isLoading, setLoading] = useState<boolean>(false);
@ -159,6 +163,17 @@ const PoliciesDetailPage = () => {
GlobalSettingOptions.POLICIES GlobalSettingOptions.POLICIES
); );
const editDescriptionPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(
Operation.EditDescription,
ResourceEntity.ROLE,
permissions
)
);
}, [permissions]);
const breadcrumb = useMemo( const breadcrumb = useMemo(
() => [ () => [
{ {
@ -393,6 +408,7 @@ const PoliciesDetailPage = () => {
entityFqn={policy.fullyQualifiedName} entityFqn={policy.fullyQualifiedName}
entityName={getEntityName(policy)} entityName={getEntityName(policy)}
entityType={EntityType.POLICY} entityType={EntityType.POLICY}
hasEditAccess={editDescriptionPermission}
isEdit={editDescription} isEdit={editDescription}
onCancel={() => setEditDescription(false)} onCancel={() => setEditDescription(false)}
onDescriptionEdit={() => setEditDescription(true)} onDescriptionEdit={() => setEditDescription(true)}

View File

@ -11,18 +11,24 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Popover, Space, Table, Tag } from 'antd'; import { Button, Popover, Space, Table, Tag, Tooltip } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { isUndefined, uniqueId } from 'lodash'; import { isEmpty, isUndefined, uniqueId } from 'lodash';
import React, { FC, useMemo, useState } from 'react'; import React, { FC, useMemo, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import DeleteWidgetModal from '../../../components/common/DeleteWidget/DeleteWidgetModal'; import DeleteWidgetModal from '../../../components/common/DeleteWidget/DeleteWidgetModal';
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../components/PermissionProvider/PermissionProvider.interface';
import {
NO_PERMISSION_FOR_ACTION,
NO_PERMISSION_TO_VIEW,
} from '../../../constants/HelperTextUtil';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { Policy } from '../../../generated/entity/policies/policy'; import { Operation, Policy } from '../../../generated/entity/policies/policy';
import { Paging } from '../../../generated/type/paging'; import { Paging } from '../../../generated/type/paging';
import { getEntityName } from '../../../utils/CommonUtils'; import { getEntityName } from '../../../utils/CommonUtils';
import { LIST_CAP } from '../../../utils/PermissionsUtils'; import { checkPermission, LIST_CAP } from '../../../utils/PermissionsUtils';
import { import {
getPolicyWithFqnPath, getPolicyWithFqnPath,
getRoleWithFqnPath, getRoleWithFqnPath,
@ -36,6 +42,23 @@ interface PolicyListProps {
const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => { const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => {
const [selectedPolicy, setSelectedPolicy] = useState<Policy>(); const [selectedPolicy, setSelectedPolicy] = useState<Policy>();
const { permissions } = usePermissionProvider();
const deletePolicyPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(Operation.Delete, ResourceEntity.POLICY, permissions)
);
}, [permissions]);
const viewRolePermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(Operation.ViewAll, ResourceEntity.ROLE, permissions)
);
}, [permissions]);
const columns: ColumnsType<Policy> = useMemo(() => { const columns: ColumnsType<Policy> = useMemo(() => {
return [ return [
{ {
@ -71,27 +94,39 @@ const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => {
return record.roles?.length ? ( return record.roles?.length ? (
<Space wrap data-testid="role-link" size={4}> <Space wrap data-testid="role-link" size={4}>
{record.roles.slice(0, LIST_CAP).map((role) => ( {record.roles.slice(0, LIST_CAP).map((role) =>
<Link viewRolePermission ? (
key={uniqueId()} <Link
to={getRoleWithFqnPath(role.fullyQualifiedName || '')}> key={uniqueId()}
{getEntityName(role)} to={getRoleWithFqnPath(role.fullyQualifiedName || '')}>
</Link> {getEntityName(role)}
))} </Link>
) : (
<Tooltip title={NO_PERMISSION_TO_VIEW}>
{getEntityName(role)}
</Tooltip>
)
)}
{hasMore && ( {hasMore && (
<Popover <Popover
className="tw-cursor-pointer" className="tw-cursor-pointer"
content={ content={
<Space wrap size={4}> <Space wrap size={4}>
{record.roles.slice(LIST_CAP).map((role) => ( {record.roles.slice(LIST_CAP).map((role) =>
<Link viewRolePermission ? (
key={uniqueId()} <Link
to={getRoleWithFqnPath( key={uniqueId()}
role.fullyQualifiedName || '' to={getRoleWithFqnPath(
)}> role.fullyQualifiedName || ''
{getEntityName(role)} )}>
</Link> {getEntityName(role)}
))} </Link>
) : (
<Tooltip title={NO_PERMISSION_TO_VIEW}>
{getEntityName(role)}
</Tooltip>
)
)}
</Space> </Space>
} }
overlayClassName="tw-w-40 tw-text-center" overlayClassName="tw-w-40 tw-text-center"
@ -114,12 +149,19 @@ const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => {
key: 'actions', key: 'actions',
render: (_, record) => { render: (_, record) => {
return ( return (
<Button <Tooltip
data-testid={`delete-action-${getEntityName(record)}`} placement="left"
type="text" title={
onClick={() => setSelectedPolicy(record)}> deletePolicyPermission ? 'Delete' : NO_PERMISSION_FOR_ACTION
<SVGIcons alt="delete" icon={Icons.DELETE} width="18px" /> }>
</Button> <Button
data-testid={`delete-action-${getEntityName(record)}`}
disabled={!deletePolicyPermission}
type="text"
onClick={() => setSelectedPolicy(record)}>
<SVGIcons alt="delete" icon={Icons.DELETE} width="18px" />
</Button>
</Tooltip>
); );
}, },
}, },
@ -135,7 +177,7 @@ const PoliciesList: FC<PolicyListProps> = ({ policies, fetchPolicies }) => {
pagination={false} pagination={false}
size="middle" size="middle"
/> />
{selectedPolicy && ( {selectedPolicy && deletePolicyPermission && (
<DeleteWidgetModal <DeleteWidgetModal
afterDeleteAction={fetchPolicies} afterDeleteAction={fetchPolicies}
allowSoftDelete={false} allowSoftDelete={false}

View File

@ -11,20 +11,25 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Col, Row, Space } from 'antd'; import { Button, Col, Row, Space, Tooltip } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import React, { useEffect, useState } from 'react'; import { isEmpty } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { getPolicies } from '../../../axiosAPIs/rolesAPIV1'; import { getPolicies } from '../../../axiosAPIs/rolesAPIV1';
import NextPrevious from '../../../components/common/next-previous/NextPrevious'; import NextPrevious from '../../../components/common/next-previous/NextPrevious';
import Loader from '../../../components/Loader/Loader'; import Loader from '../../../components/Loader/Loader';
import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../components/PermissionProvider/PermissionProvider.interface';
import { import {
INITIAL_PAGING_VALUE, INITIAL_PAGING_VALUE,
PAGE_SIZE, PAGE_SIZE,
ROUTES, ROUTES,
} from '../../../constants/constants'; } from '../../../constants/constants';
import { Policy } from '../../../generated/entity/policies/policy'; import { NO_PERMISSION_FOR_ACTION } from '../../../constants/HelperTextUtil';
import { Operation, Policy } from '../../../generated/entity/policies/policy';
import { Paging } from '../../../generated/type/paging'; import { Paging } from '../../../generated/type/paging';
import { checkPermission } from '../../../utils/PermissionsUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import PoliciesList from './PoliciesList'; import PoliciesList from './PoliciesList';
import './PoliciesList.less'; import './PoliciesList.less';
@ -36,6 +41,15 @@ const PoliciesListPage = () => {
const [paging, setPaging] = useState<Paging>(); const [paging, setPaging] = useState<Paging>();
const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE); const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE);
const { permissions } = usePermissionProvider();
const addPolicyPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(Operation.Create, ResourceEntity.POLICY, permissions)
);
}, [permissions]);
const fetchPolicies = async (paging?: Paging) => { const fetchPolicies = async (paging?: Paging) => {
setIsLoading(true); setIsLoading(true);
try { try {
@ -73,12 +87,19 @@ const PoliciesListPage = () => {
<Row className="policies-list-container" gutter={[16, 16]}> <Row className="policies-list-container" gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<Space align="center" className="tw-w-full tw-justify-end" size={16}> <Space align="center" className="tw-w-full tw-justify-end" size={16}>
<Button <Tooltip
data-testid="add-policy" placement="left"
type="primary" title={
onClick={handleAddPolicy}> addPolicyPermission ? 'Add Policy' : NO_PERMISSION_FOR_ACTION
Add Policy }>
</Button> <Button
data-testid="add-policy"
disabled={!addPolicyPermission}
type="primary"
onClick={handleAddPolicy}>
Add Policy
</Button>
</Tooltip>
</Space> </Space>
</Col> </Col>
<Col span={24}> <Col span={24}>

View File

@ -26,14 +26,18 @@ import Description from '../../../components/common/description/Description';
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component'; import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
import Loader from '../../../components/Loader/Loader'; import Loader from '../../../components/Loader/Loader';
import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../components/PermissionProvider/PermissionProvider.interface';
import { getUserPath } from '../../../constants/constants'; import { getUserPath } from '../../../constants/constants';
import { import {
GlobalSettingOptions, GlobalSettingOptions,
GlobalSettingsMenuCategory, GlobalSettingsMenuCategory,
} from '../../../constants/globalSettings.constants'; } from '../../../constants/globalSettings.constants';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { Operation } from '../../../generated/entity/policies/policy';
import { Role } from '../../../generated/entity/teams/role'; import { Role } from '../../../generated/entity/teams/role';
import { getEntityName } from '../../../utils/CommonUtils'; import { getEntityName } from '../../../utils/CommonUtils';
import { checkPermission } from '../../../utils/PermissionsUtils';
import { import {
getPolicyWithFqnPath, getPolicyWithFqnPath,
getSettingPath, getSettingPath,
@ -139,6 +143,7 @@ const List = ({
const RolesDetailPage = () => { const RolesDetailPage = () => {
const history = useHistory(); const history = useHistory();
const { permissions } = usePermissionProvider();
const { fqn } = useParams<{ fqn: string }>(); const { fqn } = useParams<{ fqn: string }>();
const [role, setRole] = useState<Role>({} as Role); const [role, setRole] = useState<Role>({} as Role);
@ -154,6 +159,17 @@ const RolesDetailPage = () => {
GlobalSettingOptions.ROLES GlobalSettingOptions.ROLES
); );
const editDescriptionPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(
Operation.EditDescription,
ResourceEntity.ROLE,
permissions
)
);
}, [permissions]);
const breadcrumb = useMemo( const breadcrumb = useMemo(
() => [ () => [
{ {
@ -319,6 +335,7 @@ const RolesDetailPage = () => {
entityFqn={role.fullyQualifiedName} entityFqn={role.fullyQualifiedName}
entityName={getEntityName(role)} entityName={getEntityName(role)}
entityType={EntityType.ROLE} entityType={EntityType.ROLE}
hasEditAccess={editDescriptionPermission}
isEdit={editDescription} isEdit={editDescription}
onCancel={() => setEditDescription(false)} onCancel={() => setEditDescription(false)}
onDescriptionEdit={() => setEditDescription(true)} onDescriptionEdit={() => setEditDescription(true)}

View File

@ -11,18 +11,25 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Popover, Space, Table, Tag } from 'antd'; import { Button, Popover, Space, Table, Tag, Tooltip } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { isUndefined, uniqueId } from 'lodash'; import { isEmpty, isUndefined, uniqueId } from 'lodash';
import React, { FC, useMemo, useState } from 'react'; import React, { FC, useMemo, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import DeleteWidgetModal from '../../../components/common/DeleteWidget/DeleteWidgetModal'; import DeleteWidgetModal from '../../../components/common/DeleteWidget/DeleteWidgetModal';
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../components/PermissionProvider/PermissionProvider.interface';
import {
NO_PERMISSION_FOR_ACTION,
NO_PERMISSION_TO_VIEW,
} from '../../../constants/HelperTextUtil';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
import { Operation } from '../../../generated/entity/policies/policy';
import { Role } from '../../../generated/entity/teams/role'; import { Role } from '../../../generated/entity/teams/role';
import { Paging } from '../../../generated/type/paging'; import { Paging } from '../../../generated/type/paging';
import { getEntityName } from '../../../utils/CommonUtils'; import { getEntityName } from '../../../utils/CommonUtils';
import { LIST_CAP } from '../../../utils/PermissionsUtils'; import { checkPermission, LIST_CAP } from '../../../utils/PermissionsUtils';
import { import {
getPolicyWithFqnPath, getPolicyWithFqnPath,
getRoleWithFqnPath, getRoleWithFqnPath,
@ -37,6 +44,22 @@ interface RolesListProps {
const RolesList: FC<RolesListProps> = ({ roles, fetchRoles }) => { const RolesList: FC<RolesListProps> = ({ roles, fetchRoles }) => {
const [selectedRole, setSelectedRole] = useState<Role>(); const [selectedRole, setSelectedRole] = useState<Role>();
const { permissions } = usePermissionProvider();
const viewPolicyPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(Operation.ViewAll, ResourceEntity.POLICY, permissions)
);
}, [permissions]);
const deleteRolePermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(Operation.Delete, ResourceEntity.ROLE, permissions)
);
}, [permissions]);
const columns: ColumnsType<Role> = useMemo(() => { const columns: ColumnsType<Role> = useMemo(() => {
return [ return [
{ {
@ -72,27 +95,39 @@ const RolesList: FC<RolesListProps> = ({ roles, fetchRoles }) => {
return record.policies?.length ? ( return record.policies?.length ? (
<Space wrap data-testid="policy-link" size={4}> <Space wrap data-testid="policy-link" size={4}>
{record.policies.slice(0, LIST_CAP).map((policy) => ( {record.policies.slice(0, LIST_CAP).map((policy) =>
<Link viewPolicyPermission ? (
key={uniqueId()} <Link
to={getPolicyWithFqnPath(policy.fullyQualifiedName || '')}> key={uniqueId()}
{getEntityName(policy)} to={getPolicyWithFqnPath(policy.fullyQualifiedName || '')}>
</Link> {getEntityName(policy)}
))} </Link>
) : (
<Tooltip title={NO_PERMISSION_TO_VIEW}>
{getEntityName(policy)}
</Tooltip>
)
)}
{hasMore && ( {hasMore && (
<Popover <Popover
className="tw-cursor-pointer" className="tw-cursor-pointer"
content={ content={
<Space wrap size={4}> <Space wrap size={4}>
{record.policies.slice(LIST_CAP).map((policy) => ( {record.policies.slice(LIST_CAP).map((policy) =>
<Link viewPolicyPermission ? (
key={uniqueId()} <Link
to={getPolicyWithFqnPath( key={uniqueId()}
policy.fullyQualifiedName || '' to={getPolicyWithFqnPath(
)}> policy.fullyQualifiedName || ''
{getEntityName(policy)} )}>
</Link> {getEntityName(policy)}
))} </Link>
) : (
<Tooltip title={NO_PERMISSION_TO_VIEW}>
{getEntityName(policy)}
</Tooltip>
)
)}
</Space> </Space>
} }
overlayClassName="tw-w-40 tw-text-center" overlayClassName="tw-w-40 tw-text-center"
@ -115,12 +150,21 @@ const RolesList: FC<RolesListProps> = ({ roles, fetchRoles }) => {
key: 'actions', key: 'actions',
render: (_, record) => { render: (_, record) => {
return ( return (
<Button <Tooltip
data-testid={`delete-action-${getEntityName(record)}`} placement="left"
type="text" title={
onClick={() => setSelectedRole(record)}> deleteRolePermission ? 'Delete' : NO_PERMISSION_FOR_ACTION
<SVGIcons alt="delete" icon={Icons.DELETE} width="18px" /> }>
</Button> <Button
data-testid={`delete-action-${getEntityName(record)}`}
disabled={!deleteRolePermission}
icon={
<SVGIcons alt="delete" icon={Icons.DELETE} width="18px" />
}
type="text"
onClick={() => setSelectedRole(record)}
/>
</Tooltip>
); );
}, },
}, },

View File

@ -42,7 +42,27 @@ jest.mock('./RolesList', () =>
jest.fn().mockReturnValue(<div data-testid="roles-list">RolesList</div>) jest.fn().mockReturnValue(<div data-testid="roles-list">RolesList</div>)
); );
describe('Test Roled List Page', () => { jest.mock('../../../utils/PermissionsUtils', () => ({
checkPermission: jest.fn().mockReturnValue(true),
}));
jest.mock('../../../components/PermissionProvider/PermissionProvider', () => ({
usePermissionProvider: jest.fn().mockReturnValue({
permissions: {
role: {
Create: true,
Delete: true,
ViewAll: true,
EditAll: true,
EditDescription: true,
EditDisplayName: true,
EditCustomFields: true,
},
},
}),
}));
describe('Test Roles List Page', () => {
it('Should render the list component', async () => { it('Should render the list component', async () => {
render(<RolesListPage />); render(<RolesListPage />);

View File

@ -11,20 +11,26 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Col, Row, Space } from 'antd'; import { Button, Col, Row, Space, Tooltip } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import React, { useEffect, useState } from 'react'; import { isEmpty } from 'lodash';
import React, { useEffect, useMemo, useState } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { getRoles } from '../../../axiosAPIs/rolesAPIV1'; import { getRoles } from '../../../axiosAPIs/rolesAPIV1';
import NextPrevious from '../../../components/common/next-previous/NextPrevious'; import NextPrevious from '../../../components/common/next-previous/NextPrevious';
import Loader from '../../../components/Loader/Loader'; import Loader from '../../../components/Loader/Loader';
import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../components/PermissionProvider/PermissionProvider.interface';
import { import {
INITIAL_PAGING_VALUE, INITIAL_PAGING_VALUE,
PAGE_SIZE, PAGE_SIZE,
ROUTES, ROUTES,
} from '../../../constants/constants'; } from '../../../constants/constants';
import { NO_PERMISSION_FOR_ACTION } from '../../../constants/HelperTextUtil';
import { Operation } from '../../../generated/entity/policies/policy';
import { Role } from '../../../generated/entity/teams/role'; import { Role } from '../../../generated/entity/teams/role';
import { Paging } from '../../../generated/type/paging'; import { Paging } from '../../../generated/type/paging';
import { checkPermission } from '../../../utils/PermissionsUtils';
import { showErrorToast } from '../../../utils/ToastUtils'; import { showErrorToast } from '../../../utils/ToastUtils';
import RolesList from './RolesList'; import RolesList from './RolesList';
import './RolesList.less'; import './RolesList.less';
@ -37,6 +43,15 @@ const RolesListPage = () => {
const [paging, setPaging] = useState<Paging>(); const [paging, setPaging] = useState<Paging>();
const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE); const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGING_VALUE);
const { permissions } = usePermissionProvider();
const addRolePermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(Operation.Create, ResourceEntity.ROLE, permissions)
);
}, [permissions]);
const fetchRoles = async (paging?: Paging) => { const fetchRoles = async (paging?: Paging) => {
setIsLoading(true); setIsLoading(true);
try { try {
@ -77,9 +92,17 @@ const RolesListPage = () => {
gutter={[16, 16]}> gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<Space align="center" className="tw-w-full tw-justify-end" size={16}> <Space align="center" className="tw-w-full tw-justify-end" size={16}>
<Button data-testid="add-role" type="primary" onClick={handleAddRole}> <Tooltip
Add Role placement="left"
</Button> title={addRolePermission ? 'Add Role' : NO_PERMISSION_FOR_ACTION}>
<Button
data-testid="add-role"
disabled={!addRolePermission}
type="primary"
onClick={handleAddRole}>
Add Role
</Button>
</Tooltip>
</Space> </Space>
</Col> </Col>
<Col span={24}> <Col span={24}>

View File

@ -887,7 +887,7 @@ const ServicePage: FunctionComponent = () => {
}; };
return ( return (
<Row> <Row className="tw-my-4" gutter={[16, 16]}>
{isLoading ? ( {isLoading ? (
<Loader /> <Loader />
) : isError ? ( ) : isError ? (

View File

@ -11,19 +11,26 @@
* limitations under the License. * limitations under the License.
*/ */
import { Col, Empty, Row } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import { ServiceCategory } from 'Models'; import { ServiceCategory } from 'Models';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { getServices } from '../../axiosAPIs/serviceAPI'; import { getServices } from '../../axiosAPIs/serviceAPI';
import Loader from '../../components/Loader/Loader'; import Loader from '../../components/Loader/Loader';
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
import Services from '../../components/Services/Services'; import Services from '../../components/Services/Services';
import { pagingObject } from '../../constants/constants'; import { pagingObject } from '../../constants/constants';
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
import { SERVICE_CATEGORY } from '../../constants/services.const'; import { SERVICE_CATEGORY } from '../../constants/services.const';
import { ServiceCategory as Category } from '../../enums/service.enum'; import { ServiceCategory as Category } from '../../enums/service.enum';
import { Operation } from '../../generated/entity/policies/policy';
import { Paging } from '../../generated/type/paging'; import { Paging } from '../../generated/type/paging';
import { ServicesType } from '../../interface/service.interface'; import { ServicesType } from '../../interface/service.interface';
import jsonData from '../../jsons/en'; import jsonData from '../../jsons/en';
import { checkPermission } from '../../utils/PermissionsUtils';
import { getResourceEntityFromServiceCategory } from '../../utils/ServiceUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
const ServicesPage = () => { const ServicesPage = () => {
@ -37,6 +44,19 @@ const ServicesPage = () => {
); );
const [currentPage, setCurrentPage] = useState<number>(1); const [currentPage, setCurrentPage] = useState<number>(1);
const { permissions } = usePermissionProvider();
const viewAllPermission = useMemo(() => {
return (
!isEmpty(permissions) &&
checkPermission(
Operation.ViewAll,
getResourceEntityFromServiceCategory(tab),
permissions
)
);
}, [permissions]);
const getServiceDetails = async (type: string) => { const getServiceDetails = async (type: string) => {
setIsLoading(true); setIsLoading(true);
try { try {
@ -78,7 +98,7 @@ const ServicesPage = () => {
return <Loader />; return <Loader />;
} }
return ( return viewAllPermission ? (
<Services <Services
currentPage={currentPage} currentPage={currentPage}
paging={paging} paging={paging}
@ -86,6 +106,12 @@ const ServicesPage = () => {
serviceName={serviceName} serviceName={serviceName}
onPageChange={handlePageChange} onPageChange={handlePageChange}
/> />
) : (
<Row>
<Col span={24}>
<Empty description={NO_PERMISSION_TO_VIEW} />
</Col>
</Row>
); );
}; };

View File

@ -15,7 +15,12 @@ import { isEmpty } from 'lodash';
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from 'react';
import { Redirect, Route, Switch } from 'react-router-dom'; import { Redirect, Route, Switch } from 'react-router-dom';
import AppState from '../AppState'; import AppState from '../AppState';
import { usePermissionProvider } from '../components/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { ROUTES } from '../constants/constants'; import { ROUTES } from '../constants/constants';
import { Operation } from '../generated/entity/policies/policy';
import { checkPermission } from '../utils/PermissionsUtils';
import AdminProtectedRoute from './AdminProtectedRoute';
import withSuspenseFallback from './withSuspenseFallback'; import withSuspenseFallback from './withSuspenseFallback';
const GlobalSettingPage = withSuspenseFallback( const GlobalSettingPage = withSuspenseFallback(
@ -70,9 +75,6 @@ const TourPageComponent = withSuspenseFallback(
const UserPage = withSuspenseFallback( const UserPage = withSuspenseFallback(
React.lazy(() => import('../pages/UserPage/UserPage.component')) React.lazy(() => import('../pages/UserPage/UserPage.component'))
); );
const AdminProtectedRoute = withSuspenseFallback(
React.lazy(() => import('./AdminProtectedRoute'))
);
const AddGlossaryPage = withSuspenseFallback( const AddGlossaryPage = withSuspenseFallback(
React.lazy(() => import('../pages/AddGlossary/AddGlossaryPage.component')) React.lazy(() => import('../pages/AddGlossary/AddGlossaryPage.component'))
@ -190,6 +192,8 @@ const EditRulePage = withSuspenseFallback(
); );
const AuthenticatedAppRouter: FunctionComponent = () => { const AuthenticatedAppRouter: FunctionComponent = () => {
const { permissions } = usePermissionProvider();
return ( return (
<Switch> <Switch>
<Route exact component={MyDataPage} path={ROUTES.MY_DATA} /> <Route exact component={MyDataPage} path={ROUTES.MY_DATA} />
@ -208,11 +212,21 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddIngestionPage} component={AddIngestionPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.INGESTION_PIPELINE,
permissions
)}
path={ROUTES.ADD_INGESTION} path={ROUTES.ADD_INGESTION}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={EditIngestionPage} component={EditIngestionPage}
hasPermission={checkPermission(
Operation.EditAll,
ResourceEntity.INGESTION_PIPELINE,
permissions
)}
path={ROUTES.EDIT_INGESTION} path={ROUTES.EDIT_INGESTION}
/> />
<Route exact component={SignupPage} path={ROUTES.SIGNUP}> <Route exact component={SignupPage} path={ROUTES.SIGNUP}>
@ -290,47 +304,92 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddGlossaryPage} component={AddGlossaryPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.GLOSSARY,
permissions
)}
path={ROUTES.ADD_GLOSSARY} path={ROUTES.ADD_GLOSSARY}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddGlossaryTermPage} component={AddGlossaryTermPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.GLOSSARY_TERM,
permissions
)}
path={ROUTES.ADD_GLOSSARY_TERMS_CHILD} path={ROUTES.ADD_GLOSSARY_TERMS_CHILD}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddGlossaryTermPage} component={AddGlossaryTermPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.GLOSSARY_TERM,
permissions
)}
path={ROUTES.ADD_GLOSSARY_TERMS} path={ROUTES.ADD_GLOSSARY_TERMS}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddWebhookPage} component={AddWebhookPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.WEBHOOK,
permissions
)}
path={ROUTES.ADD_WEBHOOK_WITH_TYPE} path={ROUTES.ADD_WEBHOOK_WITH_TYPE}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddWebhookPage} component={AddWebhookPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.WEBHOOK,
permissions
)}
path={ROUTES.ADD_WEBHOOK} path={ROUTES.ADD_WEBHOOK}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={CreateUserPage} component={CreateUserPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.USER,
permissions
)}
path={ROUTES.CREATE_USER} path={ROUTES.CREATE_USER}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={CreateUserPage} component={CreateUserPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.BOT,
permissions
)}
path={ROUTES.CREATE_USER_WITH_BOT} path={ROUTES.CREATE_USER_WITH_BOT}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={BotDetailsPage} component={BotDetailsPage}
hasPermission={checkPermission(
Operation.ViewAll,
ResourceEntity.BOT,
permissions
)}
path={ROUTES.BOTS_PROFILE} path={ROUTES.BOTS_PROFILE}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddCustomProperty} component={AddCustomProperty}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.TYPE,
permissions
)}
path={ROUTES.ADD_CUSTOM_PROPERTY} path={ROUTES.ADD_CUSTOM_PROPERTY}
/> />
<Route <Route
@ -352,21 +411,41 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddRolePage} component={AddRolePage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.ROLE,
permissions
)}
path={ROUTES.ADD_ROLE} path={ROUTES.ADD_ROLE}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddPolicyPage} component={AddPolicyPage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.POLICY,
permissions
)}
path={ROUTES.ADD_POLICY} path={ROUTES.ADD_POLICY}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={AddRulePage} component={AddRulePage}
hasPermission={checkPermission(
Operation.Create,
ResourceEntity.POLICY,
permissions
)}
path={ROUTES.ADD_POLICY_RULE} path={ROUTES.ADD_POLICY_RULE}
/> />
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={EditRulePage} component={EditRulePage}
hasPermission={checkPermission(
Operation.EditAll,
ResourceEntity.POLICY,
permissions
)}
path={ROUTES.EDIT_POLICY_RULE} path={ROUTES.EDIT_POLICY_RULE}
/> />

View File

@ -21,7 +21,7 @@ import {
} from '../constants/globalSettings.constants'; } from '../constants/globalSettings.constants';
import { Operation } from '../generated/entity/policies/policy'; 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 { checkPermission } 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';
@ -103,7 +103,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={RolesListPage} component={RolesListPage}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.ROLE, ResourceEntity.ROLE,
permissions permissions
@ -117,7 +117,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={RolesDetailPage} component={RolesDetailPage}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.ROLE, ResourceEntity.ROLE,
permissions permissions
@ -135,7 +135,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={PoliciesListPage} component={PoliciesListPage}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.POLICY, ResourceEntity.POLICY,
permissions permissions
@ -148,7 +148,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={PoliciesDetailPage} component={PoliciesDetailPage}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.POLICY, ResourceEntity.POLICY,
permissions permissions
@ -162,7 +162,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={UserListPageV1} component={UserListPageV1}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.USER, ResourceEntity.USER,
permissions permissions
@ -173,7 +173,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={WebhooksPageV1} component={WebhooksPageV1}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.WEBHOOK, ResourceEntity.WEBHOOK,
permissions permissions
@ -186,7 +186,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={BotsPageV1} component={BotsPageV1}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.BOT, ResourceEntity.BOT,
permissions permissions
@ -200,7 +200,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={SlackSettingsPage} component={SlackSettingsPage}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.WEBHOOK, ResourceEntity.WEBHOOK,
permissions permissions
@ -220,7 +220,7 @@ const GlobalSettingRouter = () => {
<AdminProtectedRoute <AdminProtectedRoute
exact exact
component={CustomPropertiesPageV1} component={CustomPropertiesPageV1}
hasPermission={checkPemission( hasPermission={checkPermission(
Operation.ViewAll, Operation.ViewAll,
ResourceEntity.ALL, ResourceEntity.ALL,
permissions permissions

View File

@ -29,3 +29,4 @@
@import url('./components/table.less'); @import url('./components/table.less');
@import url('./components/toggle-switch.less'); @import url('./components/toggle-switch.less');
@import url('./components/button.less');

View File

@ -0,0 +1,7 @@
.ant-btn {
&:disabled {
img {
opacity: 0.5;
}
}
}

View File

@ -0,0 +1,22 @@
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
export const getResourceEntityFromCustomProperty = (property: string) => {
switch (property) {
case 'tables':
return ResourceEntity.TABLE;
case 'topics':
return ResourceEntity.TOPIC;
case 'dashboards':
return ResourceEntity.DASHBOARD;
case 'pipelines':
return ResourceEntity.PIPELINE;
case 'mlModels':
return ResourceEntity.ML_MODEL;
}
return ResourceEntity.TABLE;
};

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import AppState from '../AppState';
import { import {
OperationPermission, OperationPermission,
ResourceEntity, ResourceEntity,
@ -25,6 +26,7 @@ import {
import { Operation } from '../generated/entity/policies/policy'; import { Operation } from '../generated/entity/policies/policy';
/** /**
* @deprecated
* TODO: Remove this method once we have new permission structure everywhere * TODO: Remove this method once we have new permission structure everywhere
*/ */
export const hasPemission = ( export const hasPemission = (
@ -50,22 +52,26 @@ export const hasPemission = (
* @param permissions UIPermission * @param permissions UIPermission
* @returns boolean - true/false * @returns boolean - true/false
*/ */
export const checkPemission = ( export const checkPermission = (
operation: Operation, operation: Operation,
resourceType: ResourceEntity, resourceType: ResourceEntity,
permissions: UIPermission permissions: UIPermission
) => { ) => {
const allResource = permissions.all; const isAuthDisabled = AppState.authDisabled;
const entityResource = permissions[resourceType]; const allResource = permissions?.all;
const entityResource = permissions?.[resourceType];
let hasPemission = isAuthDisabled;
/** /**
* If allresource is present then check for permission and return it * If allresource is present then check for permission and return it
*/ */
if (allResource) { if (allResource && !hasPemission) {
return allResource.All || allResource[operation]; hasPemission = allResource.All || allResource[operation];
} }
return entityResource[operation]; hasPemission = hasPemission || (entityResource && entityResource[operation]);
return hasPemission;
}; };
/** /**

View File

@ -22,6 +22,7 @@ import {
} from 'Models'; } from 'Models';
import React from 'react'; import React from 'react';
import { getEntityCount } from '../axiosAPIs/miscAPI'; import { getEntityCount } from '../axiosAPIs/miscAPI';
import { ResourceEntity } from '../components/PermissionProvider/PermissionProvider.interface';
import { GlobalSettingOptions } from '../constants/globalSettings.constants'; import { GlobalSettingOptions } from '../constants/globalSettings.constants';
import { import {
addLineageIngestionGuide, addLineageIngestionGuide,
@ -743,3 +744,31 @@ export const getServiceRouteFromServiceType = (type: ServiceTypes) => {
return GlobalSettingOptions.DATABASES; return GlobalSettingOptions.DATABASES;
}; };
export const getResourceEntityFromServiceCategory = (
category: string | ServiceCategory
) => {
switch (category) {
case 'dashboards':
case ServiceCategory.DASHBOARD_SERVICES:
return ResourceEntity.DASHBOARD_SERVICE;
case 'databases':
case ServiceCategory.DATABASE_SERVICES:
return ResourceEntity.DATABASE_SERVICE;
case 'mlModels':
case ServiceCategory.ML_MODAL_SERVICES:
return ResourceEntity.ML_MODEL_SERVICE;
case 'messaging':
case ServiceCategory.MESSAGING_SERVICES:
return ResourceEntity.MESSAGING_SERVICE;
case 'pipelines':
case ServiceCategory.PIPELINE_SERVICES:
return ResourceEntity.PIPELINE_SERVICE;
}
return ResourceEntity.DATABASE_SERVICE;
};