mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-02 11:39:12 +00:00
fix glossary permission error (#21548)
* fix glossary permission error * add permission tests * fix sonar lint
This commit is contained in:
parent
8540884ab1
commit
f0f1debb6d
@ -63,6 +63,7 @@ import {
|
||||
selectActiveGlossary,
|
||||
selectActiveGlossaryTerm,
|
||||
selectColumns,
|
||||
setupGlossaryDenyPermissionTest,
|
||||
toggleBulkActionColumnsSelection,
|
||||
updateGlossaryReviewer,
|
||||
updateGlossaryTermDataFromTree,
|
||||
@ -1434,6 +1435,38 @@ test.describe('Glossary tests', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('Verify Glossary Deny Permission', async ({ browser }) => {
|
||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||
|
||||
const { dataConsumerUser, glossary1, cleanup } =
|
||||
await setupGlossaryDenyPermissionTest(apiContext);
|
||||
|
||||
const { page: dataConsumerPage, afterAction: consumerAfterAction } =
|
||||
await performUserLogin(browser, dataConsumerUser);
|
||||
|
||||
await redirectToHomePage(dataConsumerPage);
|
||||
await sidebarClick(dataConsumerPage, SidebarItem.GLOSSARY);
|
||||
await selectActiveGlossary(
|
||||
dataConsumerPage,
|
||||
glossary1.data.displayName,
|
||||
false
|
||||
);
|
||||
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('permission-error-placeholder')
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('permission-error-placeholder')
|
||||
).toHaveText(
|
||||
"You don't have necessary permissions. Please check with the admin to get the View Glossary permission."
|
||||
);
|
||||
|
||||
await consumerAfterAction();
|
||||
await cleanup(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.afterAll(async ({ browser }) => {
|
||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||
await user1.delete(apiContext);
|
||||
|
||||
@ -14,6 +14,8 @@ import { expect, Page } from '@playwright/test';
|
||||
import { get, isUndefined } from 'lodash';
|
||||
import { SidebarItem } from '../constant/sidebar';
|
||||
import { GLOSSARY_TERM_PATCH_PAYLOAD } from '../constant/version';
|
||||
import { PolicyClass } from '../support/access-control/PoliciesClass';
|
||||
import { RolesClass } from '../support/access-control/RolesClass';
|
||||
import { DashboardClass } from '../support/entity/DashboardClass';
|
||||
import { EntityTypeEndpoint } from '../support/entity/Entity.interface';
|
||||
import { TableClass } from '../support/entity/TableClass';
|
||||
@ -25,6 +27,9 @@ import {
|
||||
UserTeamRef,
|
||||
} from '../support/glossary/Glossary.interface';
|
||||
import { GlossaryTerm } from '../support/glossary/GlossaryTerm';
|
||||
import { ClassificationClass } from '../support/tag/ClassificationClass';
|
||||
import { TagClass } from '../support/tag/TagClass';
|
||||
import { TeamClass } from '../support/team/TeamClass';
|
||||
import { UserClass } from '../support/user/UserClass';
|
||||
import {
|
||||
clickOutside,
|
||||
@ -36,6 +41,7 @@ import {
|
||||
NAME_VALIDATION_ERROR,
|
||||
redirectToHomePage,
|
||||
toastNotification,
|
||||
uuid,
|
||||
} from './common';
|
||||
import { addMultiOwner } from './entity';
|
||||
import { sidebarClick } from './sidebar';
|
||||
@ -55,16 +61,21 @@ export const checkName = async (page: Page, name: string) => {
|
||||
|
||||
export const selectActiveGlossary = async (
|
||||
page: Page,
|
||||
glossaryName: string
|
||||
glossaryName: string,
|
||||
bWaitForResponse = true
|
||||
) => {
|
||||
const menuItem = page.getByRole('menuitem', { name: glossaryName });
|
||||
const isSelected = await menuItem.evaluate((element) => {
|
||||
return element.classList.contains('ant-menu-item-selected');
|
||||
});
|
||||
if (!isSelected) {
|
||||
const glossaryResponse = page.waitForResponse('/api/v1/glossaryTerms*');
|
||||
await menuItem.click();
|
||||
await glossaryResponse;
|
||||
if (bWaitForResponse) {
|
||||
const glossaryResponse = page.waitForResponse('/api/v1/glossaryTerms*');
|
||||
await menuItem.click();
|
||||
await glossaryResponse;
|
||||
} else {
|
||||
await menuItem.click();
|
||||
}
|
||||
} else {
|
||||
await page.waitForSelector('[data-testid="loader"]', {
|
||||
state: 'detached',
|
||||
@ -1478,3 +1489,90 @@ export const checkGlossaryTermDetails = async (
|
||||
)
|
||||
).toContainText(reviewer.responseData.displayName);
|
||||
};
|
||||
|
||||
export const setupGlossaryDenyPermissionTest = async (apiContext: any) => {
|
||||
// Create all necessary resources
|
||||
const dataConsumerUser = new UserClass();
|
||||
const id = uuid();
|
||||
const glossary1 = new Glossary();
|
||||
const glossaryTerm1 = new GlossaryTerm(glossary1);
|
||||
await glossary1.create(apiContext);
|
||||
await glossaryTerm1.create(apiContext);
|
||||
|
||||
const classification = new ClassificationClass({
|
||||
provider: 'system',
|
||||
mutuallyExclusive: true,
|
||||
});
|
||||
const tag = new TagClass({
|
||||
classification: classification.data.name,
|
||||
});
|
||||
|
||||
await dataConsumerUser.create(apiContext);
|
||||
await classification.create(apiContext);
|
||||
await tag.create(apiContext);
|
||||
|
||||
// Setup permissions
|
||||
const dataConsumerPolicy = new PolicyClass();
|
||||
const dataConsumerRole = new RolesClass();
|
||||
|
||||
// Create domain access policy
|
||||
const matchTagRule = [
|
||||
{
|
||||
name: 'Hidden from Non Admins',
|
||||
description: '',
|
||||
resources: ['All'],
|
||||
operations: ['All'],
|
||||
effect: 'deny',
|
||||
condition: `matchAllTags('${tag.responseData.fullyQualifiedName}')`,
|
||||
},
|
||||
];
|
||||
|
||||
await dataConsumerPolicy.create(apiContext, matchTagRule);
|
||||
await dataConsumerRole.create(apiContext, [
|
||||
dataConsumerPolicy.responseData.name,
|
||||
]);
|
||||
|
||||
// Create team for the user
|
||||
const dataConsumerTeam = new TeamClass({
|
||||
name: `PW_data_consumer_team-${id}`,
|
||||
displayName: `PW Data Consumer Team ${id}`,
|
||||
description: 'playwright data consumer team description',
|
||||
teamType: 'Group',
|
||||
users: [dataConsumerUser.responseData.id ?? ''],
|
||||
defaultRoles: [dataConsumerRole.responseData.id ?? ''],
|
||||
});
|
||||
|
||||
await dataConsumerTeam.create(apiContext);
|
||||
|
||||
// Set domain ownership
|
||||
await glossary1.patch(apiContext, [
|
||||
{
|
||||
op: 'add',
|
||||
path: '/tags/0',
|
||||
value: {
|
||||
tagFQN: tag.responseData.fullyQualifiedName,
|
||||
source: 'Classification',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Return cleanup function and all created resources
|
||||
const cleanup = async (apiContext1: APIRequestContext) => {
|
||||
await glossaryTerm1.delete(apiContext);
|
||||
await glossary1.delete(apiContext);
|
||||
await dataConsumerUser.delete(apiContext1);
|
||||
await dataConsumerTeam.delete(apiContext1);
|
||||
await dataConsumerPolicy.delete(apiContext1);
|
||||
await dataConsumerRole.delete(apiContext1);
|
||||
};
|
||||
|
||||
return {
|
||||
dataConsumerUser,
|
||||
glossary1,
|
||||
glossaryTerm1,
|
||||
dataConsumerTeam,
|
||||
dataConsumerPolicy,
|
||||
dataConsumerRole,
|
||||
cleanup,
|
||||
};
|
||||
};
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
import { AxiosError } from 'axios';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { cloneDeep, isEmpty } from 'lodash';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { withActivityFeed } from '../../components/AppRouter/withActivityFeed';
|
||||
@ -23,6 +23,7 @@ import {
|
||||
OperationPermission,
|
||||
ResourceEntity,
|
||||
} from '../../context/PermissionProvider/PermissionProvider.interface';
|
||||
import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../../enums/common.enum';
|
||||
import { EntityAction, EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||
import { Glossary } from '../../generated/entity/data/glossary';
|
||||
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
|
||||
@ -40,6 +41,7 @@ import { updateGlossaryTermByFqn } from '../../utils/GlossaryUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { getGlossaryTermDetailsPath } from '../../utils/RouterUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import ErrorPlaceHolder from '../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||
import Loader from '../common/Loader/Loader';
|
||||
import { GenericProvider } from '../Customization/GenericProvider/GenericProvider';
|
||||
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
|
||||
@ -126,8 +128,12 @@ const GlossaryV1 = ({
|
||||
selectedData?.id as string
|
||||
);
|
||||
setGlossaryPermission(response);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@ -138,8 +144,12 @@ const GlossaryV1 = ({
|
||||
selectedData?.id as string
|
||||
);
|
||||
setGlossaryTermPermission(response);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@ -298,21 +308,31 @@ const GlossaryV1 = ({
|
||||
|
||||
try {
|
||||
if (isVersionsView) {
|
||||
isGlossaryActive
|
||||
? setGlossaryPermission(VERSION_VIEW_GLOSSARY_PERMISSION)
|
||||
: setGlossaryTermPermission(VERSION_VIEW_GLOSSARY_PERMISSION);
|
||||
const permission = VERSION_VIEW_GLOSSARY_PERMISSION;
|
||||
setGlossaryPermission(permission);
|
||||
setGlossaryTermPermission(permission);
|
||||
|
||||
return permission;
|
||||
} else {
|
||||
await permissionFetch();
|
||||
return await permissionFetch();
|
||||
}
|
||||
} finally {
|
||||
setIsPermissionLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const initializeGlossary = async () => {
|
||||
const permission = await initPermissions();
|
||||
if (permission?.ViewAll || permission?.ViewBasic) {
|
||||
loadGlossaryTerms();
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (id && !action) {
|
||||
loadGlossaryTerms();
|
||||
initPermissions();
|
||||
initializeGlossary();
|
||||
}
|
||||
}, [id, isGlossaryActive, isVersionsView, action]);
|
||||
|
||||
@ -330,6 +350,43 @@ const GlossaryV1 = ({
|
||||
setIsTabExpanded(!isTabExpanded);
|
||||
};
|
||||
|
||||
const glossaryContent = useMemo(() => {
|
||||
if (!(glossaryPermission.ViewAll || glossaryPermission.ViewBasic)) {
|
||||
return (
|
||||
<div className="d-flex justify-center items-center full-height">
|
||||
<ErrorPlaceHolder
|
||||
className="mt-0-important border-none"
|
||||
permissionValue={t('label.view-entity', {
|
||||
entity: t('label.glossary'),
|
||||
})}
|
||||
size={SIZE.X_LARGE}
|
||||
type={ERROR_PLACEHOLDER_TYPE.PERMISSION}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GlossaryDetails
|
||||
handleGlossaryDelete={onGlossaryDelete}
|
||||
isTabExpanded={isTabExpanded}
|
||||
isVersionView={isVersionsView}
|
||||
permissions={glossaryPermission}
|
||||
toggleTabExpanded={toggleTabExpanded}
|
||||
updateGlossary={handleGlossaryUpdate}
|
||||
updateVote={updateVote}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
glossaryPermission.ViewAll,
|
||||
glossaryPermission.ViewBasic,
|
||||
isTabExpanded,
|
||||
isVersionsView,
|
||||
onGlossaryDelete,
|
||||
handleGlossaryUpdate,
|
||||
updateVote,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(isLoading || isPermissionLoading) && <Loader />}
|
||||
@ -349,15 +406,7 @@ const GlossaryV1 = ({
|
||||
!isPermissionLoading &&
|
||||
!isEmpty(selectedData) &&
|
||||
(isGlossaryActive ? (
|
||||
<GlossaryDetails
|
||||
handleGlossaryDelete={onGlossaryDelete}
|
||||
isTabExpanded={isTabExpanded}
|
||||
isVersionView={isVersionsView}
|
||||
permissions={glossaryPermission}
|
||||
toggleTabExpanded={toggleTabExpanded}
|
||||
updateGlossary={handleGlossaryUpdate}
|
||||
updateVote={updateVote}
|
||||
/>
|
||||
glossaryContent
|
||||
) : (
|
||||
<GlossaryTermsV1
|
||||
glossaryTerm={selectedData as GlossaryTerm}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user