mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-31 20:51:26 +00:00
* fix(#12954): restrict roles and policy in settings for admins only * chore: address comments
This commit is contained in:
parent
011eaf8ad5
commit
80c11ab4f5
@ -19,15 +19,16 @@ import { ROUTES } from '../../constants/constants';
|
|||||||
import { useAuth } from '../../hooks/authHooks';
|
import { useAuth } from '../../hooks/authHooks';
|
||||||
|
|
||||||
interface AdminProtectedRouteProps extends RouteProps {
|
interface AdminProtectedRouteProps extends RouteProps {
|
||||||
hasPermission: boolean;
|
hasPermission?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AdminProtectedRoute = (routeProps: AdminProtectedRouteProps) => {
|
const AdminProtectedRoute = (routeProps: AdminProtectedRouteProps) => {
|
||||||
const { isAdminUser } = useAuth();
|
const { isAdminUser } = useAuth();
|
||||||
|
const hasPermission = Boolean(routeProps.hasPermission);
|
||||||
|
|
||||||
if (isAdminUser || routeProps.hasPermission) {
|
if (isAdminUser || hasPermission) {
|
||||||
return <Route {...routeProps} />;
|
return <Route {...routeProps} />;
|
||||||
} else if (!routeProps.hasPermission) {
|
} else if (!hasPermission) {
|
||||||
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
|
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
|
||||||
} else {
|
} else {
|
||||||
return <Redirect to={ROUTES.SIGNIN} />;
|
return <Redirect to={ROUTES.SIGNIN} />;
|
||||||
|
@ -175,17 +175,13 @@ const GlobalSettingRouter = () => {
|
|||||||
<AdminProtectedRoute
|
<AdminProtectedRoute
|
||||||
exact
|
exact
|
||||||
component={RolesListPage}
|
component={RolesListPage}
|
||||||
hasPermission={userPermissions.hasViewPermissions(
|
|
||||||
ResourceEntity.ROLE,
|
|
||||||
permissions
|
|
||||||
)}
|
|
||||||
path={getSettingPath(
|
path={getSettingPath(
|
||||||
GlobalSettingsMenuCategory.ACCESS,
|
GlobalSettingsMenuCategory.ACCESS,
|
||||||
GlobalSettingOptions.ROLES
|
GlobalSettingOptions.ROLES
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<AdminProtectedRoute
|
||||||
exact
|
exact
|
||||||
component={RolesDetailPage}
|
component={RolesDetailPage}
|
||||||
path={getSettingPath(
|
path={getSettingPath(
|
||||||
@ -201,16 +197,12 @@ const GlobalSettingRouter = () => {
|
|||||||
<AdminProtectedRoute
|
<AdminProtectedRoute
|
||||||
exact
|
exact
|
||||||
component={PoliciesListPage}
|
component={PoliciesListPage}
|
||||||
hasPermission={userPermissions.hasViewPermissions(
|
|
||||||
ResourceEntity.POLICY,
|
|
||||||
permissions
|
|
||||||
)}
|
|
||||||
path={getSettingPath(
|
path={getSettingPath(
|
||||||
GlobalSettingsMenuCategory.ACCESS,
|
GlobalSettingsMenuCategory.ACCESS,
|
||||||
GlobalSettingOptions.POLICIES
|
GlobalSettingOptions.POLICIES
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Route
|
<AdminProtectedRoute
|
||||||
exact
|
exact
|
||||||
component={PoliciesDetailPage}
|
component={PoliciesDetailPage}
|
||||||
path={getSettingPath(
|
path={getSettingPath(
|
||||||
|
@ -57,20 +57,6 @@ jest.mock('components/Loader/Loader', () =>
|
|||||||
jest.fn().mockReturnValue(<div>Loader</div>)
|
jest.fn().mockReturnValue(<div>Loader</div>)
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.mock('components/PermissionProvider/PermissionProvider', () => ({
|
|
||||||
usePermissionProvider: jest.fn().mockReturnValue({
|
|
||||||
getEntityPermissionByFqn: jest.fn().mockReturnValue({
|
|
||||||
Create: true,
|
|
||||||
Delete: true,
|
|
||||||
ViewAll: true,
|
|
||||||
EditAll: true,
|
|
||||||
EditDescription: true,
|
|
||||||
EditDisplayName: true,
|
|
||||||
EditCustomFields: true,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../constants/HelperTextUtil', () => ({
|
jest.mock('../../../constants/HelperTextUtil', () => ({
|
||||||
NO_PERMISSION_FOR_ACTION: '',
|
NO_PERMISSION_FOR_ACTION: '',
|
||||||
NO_PERMISSION_TO_VIEW: '',
|
NO_PERMISSION_TO_VIEW: '',
|
||||||
@ -80,18 +66,6 @@ jest.mock('../../../utils/CommonUtils', () => ({
|
|||||||
getEntityName: jest.fn().mockReturnValue(''),
|
getEntityName: jest.fn().mockReturnValue(''),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../../utils/PermissionsUtils', () => ({
|
|
||||||
DEFAULT_ENTITY_PERMISSION: {
|
|
||||||
Create: true,
|
|
||||||
Delete: true,
|
|
||||||
ViewAll: true,
|
|
||||||
EditAll: true,
|
|
||||||
EditDescription: true,
|
|
||||||
EditDisplayName: true,
|
|
||||||
EditCustomFields: true,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../utils/RouterUtils', () => ({
|
jest.mock('../../../utils/RouterUtils', () => ({
|
||||||
getAddPolicyRulePath: jest.fn(),
|
getAddPolicyRulePath: jest.fn(),
|
||||||
getEditPolicyRulePath: jest.fn(),
|
getEditPolicyRulePath: jest.fn(),
|
||||||
|
@ -22,7 +22,6 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
Space,
|
Space,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tooltip,
|
|
||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
|
||||||
@ -32,12 +31,6 @@ import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlac
|
|||||||
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 {
|
|
||||||
OperationPermission,
|
|
||||||
ResourceEntity,
|
|
||||||
} from 'components/PermissionProvider/PermissionProvider.interface';
|
|
||||||
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare } from 'fast-json-patch';
|
||||||
import { isEmpty, isUndefined, startCase } from 'lodash';
|
import { isEmpty, isUndefined, startCase } from 'lodash';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
@ -59,7 +52,6 @@ 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 { Policy } from '../../../generated/entity/policies/policy';
|
||||||
import { EntityReference } from '../../../generated/type/entityReference';
|
import { EntityReference } from '../../../generated/type/entityReference';
|
||||||
import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
|
|
||||||
import {
|
import {
|
||||||
getAddPolicyRulePath,
|
getAddPolicyRulePath,
|
||||||
getEditPolicyRulePath,
|
getEditPolicyRulePath,
|
||||||
@ -78,7 +70,6 @@ const PoliciesDetailPage = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { fqn } = useParams<{ fqn: string }>();
|
const { fqn } = useParams<{ fqn: string }>();
|
||||||
const { getEntityPermissionByFqn } = 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);
|
||||||
@ -87,10 +78,6 @@ const PoliciesDetailPage = () => {
|
|||||||
const [selectedEntity, setEntity] =
|
const [selectedEntity, setEntity] =
|
||||||
useState<{ attribute: Attribute; record: EntityReference }>();
|
useState<{ attribute: Attribute; record: EntityReference }>();
|
||||||
|
|
||||||
const [policyPermission, setPolicyPermission] = useState<OperationPermission>(
|
|
||||||
DEFAULT_ENTITY_PERMISSION
|
|
||||||
);
|
|
||||||
|
|
||||||
const policiesPath = getSettingPath(
|
const policiesPath = getSettingPath(
|
||||||
GlobalSettingsMenuCategory.ACCESS,
|
GlobalSettingsMenuCategory.ACCESS,
|
||||||
GlobalSettingOptions.POLICIES
|
GlobalSettingOptions.POLICIES
|
||||||
@ -110,21 +97,6 @@ const PoliciesDetailPage = () => {
|
|||||||
[policy]
|
[policy]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchPolicyPermission = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await getEntityPermissionByFqn(
|
|
||||||
ResourceEntity.POLICY,
|
|
||||||
fqn
|
|
||||||
);
|
|
||||||
setPolicyPermission(response);
|
|
||||||
} catch (error) {
|
|
||||||
showErrorToast(error as AxiosError);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchPolicy = async () => {
|
const fetchPolicy = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@ -256,7 +228,6 @@ const PoliciesDetailPage = () => {
|
|||||||
(rule: Rule) => {
|
(rule: Rule) => {
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
disabled={!policyPermission.EditAll}
|
|
||||||
overlay={
|
overlay={
|
||||||
<Menu
|
<Menu
|
||||||
items={[
|
items={[
|
||||||
@ -307,39 +278,25 @@ const PoliciesDetailPage = () => {
|
|||||||
}
|
}
|
||||||
placement="bottomRight"
|
placement="bottomRight"
|
||||||
trigger={['click']}>
|
trigger={['click']}>
|
||||||
<Tooltip
|
<Button
|
||||||
title={
|
data-testid={`manage-button-${rule.name}`}
|
||||||
policyPermission.EditAll
|
icon={<EllipsisOutlined className="text-grey-body" rotate={90} />}
|
||||||
? t('label.manage-rule')
|
size="small"
|
||||||
: t('message.no-permission-for-action')
|
type="text"
|
||||||
}>
|
onClick={(e) => {
|
||||||
<Button
|
e.stopPropagation();
|
||||||
data-testid={`manage-button-${rule.name}`}
|
}}
|
||||||
disabled={!policyPermission.EditAll}
|
/>
|
||||||
icon={<EllipsisOutlined className="text-grey-body" rotate={90} />}
|
|
||||||
size="small"
|
|
||||||
type="text"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[policy, policyPermission]
|
[policy]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPolicyPermission();
|
fetchPolicy();
|
||||||
}, [fqn]);
|
}, [fqn]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (policyPermission.ViewAll || policyPermission.ViewBasic) {
|
|
||||||
fetchPolicy();
|
|
||||||
}
|
|
||||||
}, [policyPermission, fqn]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
@ -347,198 +304,178 @@ const PoliciesDetailPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div data-testid="policy-details-container">
|
<div data-testid="policy-details-container">
|
||||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||||
{policyPermission.ViewAll || policyPermission.ViewBasic ? (
|
|
||||||
<>
|
|
||||||
{isEmpty(policy) ? (
|
|
||||||
<ErrorPlaceHolder>
|
|
||||||
<div className="text-center">
|
|
||||||
<p>
|
|
||||||
{t('message.no-entity-found-for-name', {
|
|
||||||
entity: t('label.policy-lowercase'),
|
|
||||||
name: fqn,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
onClick={() => history.push(policiesPath)}>
|
|
||||||
{t('label.go-back')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</ErrorPlaceHolder>
|
|
||||||
) : (
|
|
||||||
<div className="policies-detail" data-testid="policy-details">
|
|
||||||
<Typography.Title
|
|
||||||
className="m-b-0 m-t-xs"
|
|
||||||
data-testid="heading"
|
|
||||||
level={5}>
|
|
||||||
{getEntityName(policy)}
|
|
||||||
</Typography.Title>
|
|
||||||
<Description
|
|
||||||
className="m-b-md"
|
|
||||||
description={policy.description || ''}
|
|
||||||
entityFqn={policy.fullyQualifiedName}
|
|
||||||
entityName={getEntityName(policy)}
|
|
||||||
entityType={EntityType.POLICY}
|
|
||||||
hasEditAccess={
|
|
||||||
policyPermission.EditAll || policyPermission.EditDescription
|
|
||||||
}
|
|
||||||
isEdit={editDescription}
|
|
||||||
onCancel={() => setEditDescription(false)}
|
|
||||||
onDescriptionEdit={() => setEditDescription(true)}
|
|
||||||
onDescriptionUpdate={handleDescriptionUpdate}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tabs defaultActiveKey="rules">
|
<>
|
||||||
<TabPane key="rules" tab={t('label.rule-plural')}>
|
{isEmpty(policy) ? (
|
||||||
{isEmpty(policy.rules) ? (
|
<ErrorPlaceHolder>
|
||||||
<ErrorPlaceHolder />
|
<div className="text-center">
|
||||||
) : (
|
<p>
|
||||||
<Space
|
{t('message.no-entity-found-for-name', {
|
||||||
className="w-full tabpane-space"
|
entity: t('label.policy-lowercase'),
|
||||||
direction="vertical">
|
name: fqn,
|
||||||
<Tooltip
|
})}
|
||||||
title={
|
</p>
|
||||||
policyPermission.EditAll
|
<Button
|
||||||
? t('label.add-entity', {
|
size="small"
|
||||||
entity: t('label.rule'),
|
type="primary"
|
||||||
})
|
onClick={() => history.push(policiesPath)}>
|
||||||
: t('message.no-permission-for-action')
|
{t('label.go-back')}
|
||||||
}>
|
</Button>
|
||||||
<Button
|
|
||||||
data-testid="add-rule"
|
|
||||||
disabled={!policyPermission.EditAll}
|
|
||||||
type="primary"
|
|
||||||
onClick={() =>
|
|
||||||
history.push(getAddPolicyRulePath(fqn))
|
|
||||||
}>
|
|
||||||
{t('label.add-entity', {
|
|
||||||
entity: t('label.rule'),
|
|
||||||
})}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Space className="w-full" direction="vertical" size={20}>
|
|
||||||
{policy.rules.map((rule) => (
|
|
||||||
<Card
|
|
||||||
data-testid="rule-card"
|
|
||||||
key={rule.name || 'rule'}>
|
|
||||||
<Space
|
|
||||||
align="baseline"
|
|
||||||
className="w-full justify-between p-b-lg"
|
|
||||||
direction="horizontal">
|
|
||||||
<Typography.Text
|
|
||||||
className="font-medium text-base text-grey-body"
|
|
||||||
data-testid="rule-name">
|
|
||||||
{rule.name}
|
|
||||||
</Typography.Text>
|
|
||||||
{getRuleActionElement(rule)}
|
|
||||||
</Space>
|
|
||||||
|
|
||||||
<Space
|
|
||||||
className="w-full"
|
|
||||||
direction="vertical"
|
|
||||||
size={12}>
|
|
||||||
{rule.description && (
|
|
||||||
<Row data-testid="description">
|
|
||||||
<Col span={2}>
|
|
||||||
<Typography.Text className="text-grey-muted">
|
|
||||||
{`${t('label.description')}:`}
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
<Col span={22}>
|
|
||||||
<RichTextEditorPreviewer
|
|
||||||
markdown={rule.description || ''}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Row data-testid="resources">
|
|
||||||
<Col span={2}>
|
|
||||||
<Typography.Text className="text-grey-muted m-b-0">
|
|
||||||
{`${t('label.resource-plural')}:`}
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
<Col span={22}>
|
|
||||||
<Typography.Text className="text-grey-body">
|
|
||||||
{rule.resources
|
|
||||||
?.map((resource) => startCase(resource))
|
|
||||||
?.join(', ')}
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row data-testid="operations">
|
|
||||||
<Col span={2}>
|
|
||||||
<Typography.Text className="text-grey-muted">
|
|
||||||
{`${t('label.operation-plural')}:`}
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
<Col span={22}>
|
|
||||||
<Typography.Text className="text-grey-body">
|
|
||||||
{rule.operations?.join(', ')}
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row data-testid="effect">
|
|
||||||
<Col span={2}>
|
|
||||||
<Typography.Text className="text-grey-muted">
|
|
||||||
{`${t('label.effect')}:`}
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
<Col span={22}>
|
|
||||||
<Typography.Text className="text-grey-body">
|
|
||||||
{startCase(rule.effect)}
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{rule.condition && (
|
|
||||||
<Row data-testid="condition">
|
|
||||||
<Col span={2}>
|
|
||||||
<Typography.Text className="text-grey-muted">
|
|
||||||
{`${t('label.condition')}:`}
|
|
||||||
</Typography.Text>
|
|
||||||
</Col>
|
|
||||||
<Col span={22}>
|
|
||||||
<code>{rule.condition}</code>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
</TabPane>
|
|
||||||
<TabPane key="roles" tab={t('label.role-plural')}>
|
|
||||||
<PoliciesDetailsList
|
|
||||||
hasAccess={policyPermission.EditAll}
|
|
||||||
list={policy.roles ?? []}
|
|
||||||
type="role"
|
|
||||||
onDelete={(record) =>
|
|
||||||
setEntity({ record, attribute: 'roles' })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
<TabPane key="teams" tab={t('label.team-plural')}>
|
|
||||||
<PoliciesDetailsList
|
|
||||||
hasAccess={policyPermission.EditAll}
|
|
||||||
list={policy.teams ?? []}
|
|
||||||
type="team"
|
|
||||||
onDelete={(record) =>
|
|
||||||
setEntity({ record, attribute: 'teams' })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</ErrorPlaceHolder>
|
||||||
</>
|
) : (
|
||||||
) : (
|
<div className="policies-detail" data-testid="policy-details">
|
||||||
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />
|
<Typography.Title
|
||||||
)}
|
className="m-b-0 m-t-xs"
|
||||||
|
data-testid="heading"
|
||||||
|
level={5}>
|
||||||
|
{getEntityName(policy)}
|
||||||
|
</Typography.Title>
|
||||||
|
<Description
|
||||||
|
hasEditAccess
|
||||||
|
className="m-b-md"
|
||||||
|
description={policy.description || ''}
|
||||||
|
entityFqn={policy.fullyQualifiedName}
|
||||||
|
entityName={getEntityName(policy)}
|
||||||
|
entityType={EntityType.POLICY}
|
||||||
|
isEdit={editDescription}
|
||||||
|
onCancel={() => setEditDescription(false)}
|
||||||
|
onDescriptionEdit={() => setEditDescription(true)}
|
||||||
|
onDescriptionUpdate={handleDescriptionUpdate}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Tabs defaultActiveKey="rules">
|
||||||
|
<TabPane key="rules" tab={t('label.rule-plural')}>
|
||||||
|
{isEmpty(policy.rules) ? (
|
||||||
|
<ErrorPlaceHolder />
|
||||||
|
) : (
|
||||||
|
<Space className="w-full tabpane-space" direction="vertical">
|
||||||
|
<Button
|
||||||
|
data-testid="add-rule"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => history.push(getAddPolicyRulePath(fqn))}>
|
||||||
|
{t('label.add-entity', {
|
||||||
|
entity: t('label.rule'),
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Space className="w-full" direction="vertical" size={20}>
|
||||||
|
{policy.rules.map((rule) => (
|
||||||
|
<Card data-testid="rule-card" key={rule.name || 'rule'}>
|
||||||
|
<Space
|
||||||
|
align="baseline"
|
||||||
|
className="w-full justify-between p-b-lg"
|
||||||
|
direction="horizontal">
|
||||||
|
<Typography.Text
|
||||||
|
className="font-medium text-base text-grey-body"
|
||||||
|
data-testid="rule-name">
|
||||||
|
{rule.name}
|
||||||
|
</Typography.Text>
|
||||||
|
{getRuleActionElement(rule)}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<Space
|
||||||
|
className="w-full"
|
||||||
|
direction="vertical"
|
||||||
|
size={12}>
|
||||||
|
{rule.description && (
|
||||||
|
<Row data-testid="description">
|
||||||
|
<Col span={2}>
|
||||||
|
<Typography.Text className="text-grey-muted">
|
||||||
|
{`${t('label.description')}:`}
|
||||||
|
</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
markdown={rule.description || ''}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Row data-testid="resources">
|
||||||
|
<Col span={2}>
|
||||||
|
<Typography.Text className="text-grey-muted m-b-0">
|
||||||
|
{`${t('label.resource-plural')}:`}
|
||||||
|
</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<Typography.Text className="text-grey-body">
|
||||||
|
{rule.resources
|
||||||
|
?.map((resource) => startCase(resource))
|
||||||
|
?.join(', ')}
|
||||||
|
</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row data-testid="operations">
|
||||||
|
<Col span={2}>
|
||||||
|
<Typography.Text className="text-grey-muted">
|
||||||
|
{`${t('label.operation-plural')}:`}
|
||||||
|
</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<Typography.Text className="text-grey-body">
|
||||||
|
{rule.operations?.join(', ')}
|
||||||
|
</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row data-testid="effect">
|
||||||
|
<Col span={2}>
|
||||||
|
<Typography.Text className="text-grey-muted">
|
||||||
|
{`${t('label.effect')}:`}
|
||||||
|
</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<Typography.Text className="text-grey-body">
|
||||||
|
{startCase(rule.effect)}
|
||||||
|
</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{rule.condition && (
|
||||||
|
<Row data-testid="condition">
|
||||||
|
<Col span={2}>
|
||||||
|
<Typography.Text className="text-grey-muted">
|
||||||
|
{`${t('label.condition')}:`}
|
||||||
|
</Typography.Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<code>{rule.condition}</code>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</TabPane>
|
||||||
|
<TabPane key="roles" tab={t('label.role-plural')}>
|
||||||
|
<PoliciesDetailsList
|
||||||
|
hasAccess
|
||||||
|
list={policy.roles ?? []}
|
||||||
|
type="role"
|
||||||
|
onDelete={(record) =>
|
||||||
|
setEntity({ record, attribute: 'roles' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane key="teams" tab={t('label.team-plural')}>
|
||||||
|
<PoliciesDetailsList
|
||||||
|
hasAccess
|
||||||
|
list={policy.teams ?? []}
|
||||||
|
type="team"
|
||||||
|
onDelete={(record) =>
|
||||||
|
setEntity({ record, attribute: 'teams' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
|
||||||
{selectedEntity && (
|
{selectedEntity && (
|
||||||
<Modal
|
<Modal
|
||||||
centered
|
centered
|
||||||
|
@ -60,20 +60,6 @@ jest.mock('../../../utils/RouterUtils', () => ({
|
|||||||
getTeamsWithFqnPath: jest.fn(),
|
getTeamsWithFqnPath: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('components/PermissionProvider/PermissionProvider', () => ({
|
|
||||||
usePermissionProvider: jest.fn().mockReturnValue({
|
|
||||||
getEntityPermissionByFqn: jest.fn().mockReturnValue({
|
|
||||||
Create: true,
|
|
||||||
Delete: true,
|
|
||||||
ViewAll: true,
|
|
||||||
EditAll: true,
|
|
||||||
EditDescription: true,
|
|
||||||
EditDisplayName: true,
|
|
||||||
EditCustomFields: true,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('Test Roles Details Page', () => {
|
describe('Test Roles Details Page', () => {
|
||||||
it('Should render the detail component', async () => {
|
it('Should render the detail component', async () => {
|
||||||
render(<RolesDetailPage />);
|
render(<RolesDetailPage />);
|
||||||
|
@ -11,17 +11,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button, Modal, Space, Tabs, Tooltip, Typography } from 'antd';
|
import { Button, Modal, Space, Tabs, Typography } from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import Description from 'components/common/description/Description';
|
import Description from 'components/common/description/Description';
|
||||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||||
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 {
|
|
||||||
OperationPermission,
|
|
||||||
ResourceEntity,
|
|
||||||
} from 'components/PermissionProvider/PermissionProvider.interface';
|
|
||||||
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare } from 'fast-json-patch';
|
||||||
import { isEmpty, isUndefined } from 'lodash';
|
import { isEmpty, isUndefined } from 'lodash';
|
||||||
@ -39,7 +34,6 @@ import {
|
|||||||
import { EntityType } from '../../../enums/entity.enum';
|
import { EntityType } from '../../../enums/entity.enum';
|
||||||
import { Role } from '../../../generated/entity/teams/role';
|
import { Role } from '../../../generated/entity/teams/role';
|
||||||
import { EntityReference } from '../../../generated/type/entityReference';
|
import { EntityReference } from '../../../generated/type/entityReference';
|
||||||
import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
|
|
||||||
import { getSettingPath } from '../../../utils/RouterUtils';
|
import { getSettingPath } from '../../../utils/RouterUtils';
|
||||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||||
import AddAttributeModal from '../AddAttributeModal/AddAttributeModal';
|
import AddAttributeModal from '../AddAttributeModal/AddAttributeModal';
|
||||||
@ -58,7 +52,6 @@ interface AddAttribute {
|
|||||||
const RolesDetailPage = () => {
|
const RolesDetailPage = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { getEntityPermissionByFqn } = 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);
|
||||||
@ -70,10 +63,6 @@ const RolesDetailPage = () => {
|
|||||||
|
|
||||||
const [addAttribute, setAddAttribute] = useState<AddAttribute>();
|
const [addAttribute, setAddAttribute] = useState<AddAttribute>();
|
||||||
|
|
||||||
const [rolePermission, setRolePermission] = useState<OperationPermission>(
|
|
||||||
DEFAULT_ENTITY_PERMISSION
|
|
||||||
);
|
|
||||||
|
|
||||||
const rolesPath = getSettingPath(
|
const rolesPath = getSettingPath(
|
||||||
GlobalSettingsMenuCategory.ACCESS,
|
GlobalSettingsMenuCategory.ACCESS,
|
||||||
GlobalSettingOptions.ROLES
|
GlobalSettingOptions.ROLES
|
||||||
@ -93,18 +82,6 @@ const RolesDetailPage = () => {
|
|||||||
[role]
|
[role]
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchRolePermission = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const response = await getEntityPermissionByFqn(ResourceEntity.ROLE, fqn);
|
|
||||||
setRolePermission(response);
|
|
||||||
} catch (error) {
|
|
||||||
showErrorToast(error as AxiosError);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchRole = async () => {
|
const fetchRole = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@ -238,15 +215,9 @@ const RolesDetailPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchRolePermission();
|
fetchRole();
|
||||||
}, [fqn]);
|
}, [fqn]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (rolePermission.ViewAll || rolePermission.ViewBasic) {
|
|
||||||
fetchRole();
|
|
||||||
}
|
|
||||||
}, [rolePermission, fqn]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
@ -254,112 +225,99 @@ const RolesDetailPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div data-testid="role-details-container">
|
<div data-testid="role-details-container">
|
||||||
<TitleBreadcrumb titleLinks={breadcrumb} />
|
<TitleBreadcrumb titleLinks={breadcrumb} />
|
||||||
{rolePermission.ViewAll || rolePermission.ViewBasic ? (
|
|
||||||
<>
|
|
||||||
{isEmpty(role) ? (
|
|
||||||
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.CUSTOM}>
|
|
||||||
<div className="text-center">
|
|
||||||
<p>
|
|
||||||
{t('message.no-entity-found-for-name', {
|
|
||||||
entity: t('label.role'),
|
|
||||||
name: fqn,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
<Button
|
|
||||||
ghost
|
|
||||||
className="m-t-sm"
|
|
||||||
type="primary"
|
|
||||||
onClick={() => history.push(rolesPath)}>
|
|
||||||
{t('label.go-back')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</ErrorPlaceHolder>
|
|
||||||
) : (
|
|
||||||
<div className="roles-detail" data-testid="role-details">
|
|
||||||
<Typography.Title
|
|
||||||
className="m-b-0 m-t-xs"
|
|
||||||
data-testid="heading"
|
|
||||||
level={5}>
|
|
||||||
{getEntityName(role)}
|
|
||||||
</Typography.Title>
|
|
||||||
<Description
|
|
||||||
className="m-b-md"
|
|
||||||
description={role.description || ''}
|
|
||||||
entityFqn={role.fullyQualifiedName}
|
|
||||||
entityName={getEntityName(role)}
|
|
||||||
entityType={EntityType.ROLE}
|
|
||||||
hasEditAccess={
|
|
||||||
rolePermission.EditAll || rolePermission.EditDescription
|
|
||||||
}
|
|
||||||
isEdit={editDescription}
|
|
||||||
onCancel={() => setEditDescription(false)}
|
|
||||||
onDescriptionEdit={() => setEditDescription(true)}
|
|
||||||
onDescriptionUpdate={handleDescriptionUpdate}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tabs data-testid="tabs" defaultActiveKey="policies">
|
<>
|
||||||
<TabPane key="policies" tab={t('label.policy-plural')}>
|
{isEmpty(role) ? (
|
||||||
<Space className="w-full" direction="vertical">
|
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.CUSTOM}>
|
||||||
<Tooltip
|
<div className="text-center">
|
||||||
title={
|
<p>
|
||||||
rolePermission.EditAll
|
{t('message.no-entity-found-for-name', {
|
||||||
? t('label.add-entity', {
|
entity: t('label.role'),
|
||||||
entity: t('label.policy'),
|
name: fqn,
|
||||||
})
|
})}
|
||||||
: t('message.no-permission-for-action')
|
</p>
|
||||||
}>
|
<Button
|
||||||
<Button
|
ghost
|
||||||
data-testid="add-policy"
|
className="m-t-sm"
|
||||||
disabled={!rolePermission.EditAll}
|
type="primary"
|
||||||
type="primary"
|
onClick={() => history.push(rolesPath)}>
|
||||||
onClick={() =>
|
{t('label.go-back')}
|
||||||
setAddAttribute({
|
</Button>
|
||||||
type: EntityType.POLICY,
|
|
||||||
selectedData: role.policies || [],
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
{t('label.add-entity', {
|
|
||||||
entity: t('label.policy'),
|
|
||||||
})}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
<RolesDetailPageList
|
|
||||||
hasAccess={rolePermission.EditAll}
|
|
||||||
list={role.policies ?? []}
|
|
||||||
type="policy"
|
|
||||||
onDelete={(record) =>
|
|
||||||
setEntity({ record, attribute: 'policies' })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</TabPane>
|
|
||||||
<TabPane key="teams" tab={t('label.team-plural')}>
|
|
||||||
<RolesDetailPageList
|
|
||||||
hasAccess={rolePermission.EditAll}
|
|
||||||
list={role.teams ?? []}
|
|
||||||
type="team"
|
|
||||||
onDelete={(record) =>
|
|
||||||
setEntity({ record, attribute: 'teams' })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
<TabPane key="users" tab={t('label.user-plural')}>
|
|
||||||
<RolesDetailPageList
|
|
||||||
hasAccess={rolePermission.EditAll}
|
|
||||||
list={role.users ?? []}
|
|
||||||
type="user"
|
|
||||||
onDelete={(record) =>
|
|
||||||
setEntity({ record, attribute: 'users' })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</ErrorPlaceHolder>
|
||||||
</>
|
) : (
|
||||||
) : (
|
<div className="roles-detail" data-testid="role-details">
|
||||||
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />
|
<Typography.Title
|
||||||
)}
|
className="m-b-0 m-t-xs"
|
||||||
|
data-testid="heading"
|
||||||
|
level={5}>
|
||||||
|
{getEntityName(role)}
|
||||||
|
</Typography.Title>
|
||||||
|
<Description
|
||||||
|
hasEditAccess
|
||||||
|
className="m-b-md"
|
||||||
|
description={role.description || ''}
|
||||||
|
entityFqn={role.fullyQualifiedName}
|
||||||
|
entityName={getEntityName(role)}
|
||||||
|
entityType={EntityType.ROLE}
|
||||||
|
isEdit={editDescription}
|
||||||
|
onCancel={() => setEditDescription(false)}
|
||||||
|
onDescriptionEdit={() => setEditDescription(true)}
|
||||||
|
onDescriptionUpdate={handleDescriptionUpdate}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Tabs data-testid="tabs" defaultActiveKey="policies">
|
||||||
|
<TabPane key="policies" tab={t('label.policy-plural')}>
|
||||||
|
<Space className="w-full" direction="vertical">
|
||||||
|
<Button
|
||||||
|
data-testid="add-policy"
|
||||||
|
type="primary"
|
||||||
|
onClick={() =>
|
||||||
|
setAddAttribute({
|
||||||
|
type: EntityType.POLICY,
|
||||||
|
selectedData: role.policies || [],
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{t('label.add-entity', {
|
||||||
|
entity: t('label.policy'),
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<RolesDetailPageList
|
||||||
|
hasAccess
|
||||||
|
list={role.policies ?? []}
|
||||||
|
type="policy"
|
||||||
|
onDelete={(record) =>
|
||||||
|
setEntity({ record, attribute: 'policies' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane key="teams" tab={t('label.team-plural')}>
|
||||||
|
<RolesDetailPageList
|
||||||
|
hasAccess
|
||||||
|
list={role.teams ?? []}
|
||||||
|
type="team"
|
||||||
|
onDelete={(record) =>
|
||||||
|
setEntity({ record, attribute: 'teams' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane key="users" tab={t('label.user-plural')}>
|
||||||
|
<RolesDetailPageList
|
||||||
|
hasAccess
|
||||||
|
list={role.users ?? []}
|
||||||
|
type="user"
|
||||||
|
onDelete={(record) =>
|
||||||
|
setEntity({ record, attribute: 'users' })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
|
||||||
{selectedEntity && (
|
{selectedEntity && (
|
||||||
<Modal
|
<Modal
|
||||||
centered
|
centered
|
||||||
|
@ -102,19 +102,13 @@ export const getGlobalSettingsMenuWithPermission = (
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: i18next.t('label.role-plural'),
|
label: i18next.t('label.role-plural'),
|
||||||
isProtected: userPermissions.hasViewPermissions(
|
isProtected: Boolean(isAdminUser),
|
||||||
ResourceEntity.ROLE,
|
|
||||||
permissions
|
|
||||||
),
|
|
||||||
key: 'access.roles',
|
key: 'access.roles',
|
||||||
icon: <RolesIcon className="side-panel-icons" />,
|
icon: <RolesIcon className="side-panel-icons" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18next.t('label.policy-plural'),
|
label: i18next.t('label.policy-plural'),
|
||||||
isProtected: userPermissions.hasViewPermissions(
|
isProtected: Boolean(isAdminUser),
|
||||||
ResourceEntity.POLICY,
|
|
||||||
permissions
|
|
||||||
),
|
|
||||||
key: 'access.policies',
|
key: 'access.policies',
|
||||||
icon: <PoliciesIcon className="side-panel-icons" />,
|
icon: <PoliciesIcon className="side-panel-icons" />,
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user