mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-14 09:51:13 +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,
|
selectActiveGlossary,
|
||||||
selectActiveGlossaryTerm,
|
selectActiveGlossaryTerm,
|
||||||
selectColumns,
|
selectColumns,
|
||||||
|
setupGlossaryDenyPermissionTest,
|
||||||
toggleBulkActionColumnsSelection,
|
toggleBulkActionColumnsSelection,
|
||||||
updateGlossaryReviewer,
|
updateGlossaryReviewer,
|
||||||
updateGlossaryTermDataFromTree,
|
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 }) => {
|
test.afterAll(async ({ browser }) => {
|
||||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||||
await user1.delete(apiContext);
|
await user1.delete(apiContext);
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import { expect, Page } from '@playwright/test';
|
|||||||
import { get, isUndefined } from 'lodash';
|
import { get, isUndefined } from 'lodash';
|
||||||
import { SidebarItem } from '../constant/sidebar';
|
import { SidebarItem } from '../constant/sidebar';
|
||||||
import { GLOSSARY_TERM_PATCH_PAYLOAD } from '../constant/version';
|
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 { DashboardClass } from '../support/entity/DashboardClass';
|
||||||
import { EntityTypeEndpoint } from '../support/entity/Entity.interface';
|
import { EntityTypeEndpoint } from '../support/entity/Entity.interface';
|
||||||
import { TableClass } from '../support/entity/TableClass';
|
import { TableClass } from '../support/entity/TableClass';
|
||||||
@ -25,6 +27,9 @@ import {
|
|||||||
UserTeamRef,
|
UserTeamRef,
|
||||||
} from '../support/glossary/Glossary.interface';
|
} from '../support/glossary/Glossary.interface';
|
||||||
import { GlossaryTerm } from '../support/glossary/GlossaryTerm';
|
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 { UserClass } from '../support/user/UserClass';
|
||||||
import {
|
import {
|
||||||
clickOutside,
|
clickOutside,
|
||||||
@ -36,6 +41,7 @@ import {
|
|||||||
NAME_VALIDATION_ERROR,
|
NAME_VALIDATION_ERROR,
|
||||||
redirectToHomePage,
|
redirectToHomePage,
|
||||||
toastNotification,
|
toastNotification,
|
||||||
|
uuid,
|
||||||
} from './common';
|
} from './common';
|
||||||
import { addMultiOwner } from './entity';
|
import { addMultiOwner } from './entity';
|
||||||
import { sidebarClick } from './sidebar';
|
import { sidebarClick } from './sidebar';
|
||||||
@ -55,16 +61,21 @@ export const checkName = async (page: Page, name: string) => {
|
|||||||
|
|
||||||
export const selectActiveGlossary = async (
|
export const selectActiveGlossary = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
glossaryName: string
|
glossaryName: string,
|
||||||
|
bWaitForResponse = true
|
||||||
) => {
|
) => {
|
||||||
const menuItem = page.getByRole('menuitem', { name: glossaryName });
|
const menuItem = page.getByRole('menuitem', { name: glossaryName });
|
||||||
const isSelected = await menuItem.evaluate((element) => {
|
const isSelected = await menuItem.evaluate((element) => {
|
||||||
return element.classList.contains('ant-menu-item-selected');
|
return element.classList.contains('ant-menu-item-selected');
|
||||||
});
|
});
|
||||||
if (!isSelected) {
|
if (!isSelected) {
|
||||||
const glossaryResponse = page.waitForResponse('/api/v1/glossaryTerms*');
|
if (bWaitForResponse) {
|
||||||
await menuItem.click();
|
const glossaryResponse = page.waitForResponse('/api/v1/glossaryTerms*');
|
||||||
await glossaryResponse;
|
await menuItem.click();
|
||||||
|
await glossaryResponse;
|
||||||
|
} else {
|
||||||
|
await menuItem.click();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await page.waitForSelector('[data-testid="loader"]', {
|
await page.waitForSelector('[data-testid="loader"]', {
|
||||||
state: 'detached',
|
state: 'detached',
|
||||||
@ -1478,3 +1489,90 @@ export const checkGlossaryTermDetails = async (
|
|||||||
)
|
)
|
||||||
).toContainText(reviewer.responseData.displayName);
|
).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 { AxiosError } from 'axios';
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare } from 'fast-json-patch';
|
||||||
import { cloneDeep, isEmpty } from 'lodash';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import { withActivityFeed } from '../../components/AppRouter/withActivityFeed';
|
import { withActivityFeed } from '../../components/AppRouter/withActivityFeed';
|
||||||
@ -23,6 +23,7 @@ import {
|
|||||||
OperationPermission,
|
OperationPermission,
|
||||||
ResourceEntity,
|
ResourceEntity,
|
||||||
} from '../../context/PermissionProvider/PermissionProvider.interface';
|
} from '../../context/PermissionProvider/PermissionProvider.interface';
|
||||||
|
import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../../enums/common.enum';
|
||||||
import { EntityAction, EntityTabs, EntityType } from '../../enums/entity.enum';
|
import { EntityAction, EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||||
import { Glossary } from '../../generated/entity/data/glossary';
|
import { Glossary } from '../../generated/entity/data/glossary';
|
||||||
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
|
import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm';
|
||||||
@ -40,6 +41,7 @@ import { updateGlossaryTermByFqn } from '../../utils/GlossaryUtils';
|
|||||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||||
import { getGlossaryTermDetailsPath } from '../../utils/RouterUtils';
|
import { getGlossaryTermDetailsPath } from '../../utils/RouterUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
|
import ErrorPlaceHolder from '../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||||
import Loader from '../common/Loader/Loader';
|
import Loader from '../common/Loader/Loader';
|
||||||
import { GenericProvider } from '../Customization/GenericProvider/GenericProvider';
|
import { GenericProvider } from '../Customization/GenericProvider/GenericProvider';
|
||||||
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
|
import EntityDeleteModal from '../Modals/EntityDeleteModal/EntityDeleteModal';
|
||||||
@ -126,8 +128,12 @@ const GlossaryV1 = ({
|
|||||||
selectedData?.id as string
|
selectedData?.id as string
|
||||||
);
|
);
|
||||||
setGlossaryPermission(response);
|
setGlossaryPermission(response);
|
||||||
|
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(error as AxiosError);
|
showErrorToast(error as AxiosError);
|
||||||
|
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,8 +144,12 @@ const GlossaryV1 = ({
|
|||||||
selectedData?.id as string
|
selectedData?.id as string
|
||||||
);
|
);
|
||||||
setGlossaryTermPermission(response);
|
setGlossaryTermPermission(response);
|
||||||
|
|
||||||
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorToast(error as AxiosError);
|
showErrorToast(error as AxiosError);
|
||||||
|
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -298,21 +308,31 @@ const GlossaryV1 = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (isVersionsView) {
|
if (isVersionsView) {
|
||||||
isGlossaryActive
|
const permission = VERSION_VIEW_GLOSSARY_PERMISSION;
|
||||||
? setGlossaryPermission(VERSION_VIEW_GLOSSARY_PERMISSION)
|
setGlossaryPermission(permission);
|
||||||
: setGlossaryTermPermission(VERSION_VIEW_GLOSSARY_PERMISSION);
|
setGlossaryTermPermission(permission);
|
||||||
|
|
||||||
|
return permission;
|
||||||
} else {
|
} else {
|
||||||
await permissionFetch();
|
return await permissionFetch();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsPermissionLoading(false);
|
setIsPermissionLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initializeGlossary = async () => {
|
||||||
|
const permission = await initPermissions();
|
||||||
|
if (permission?.ViewAll || permission?.ViewBasic) {
|
||||||
|
loadGlossaryTerms();
|
||||||
|
} else {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id && !action) {
|
if (id && !action) {
|
||||||
loadGlossaryTerms();
|
initializeGlossary();
|
||||||
initPermissions();
|
|
||||||
}
|
}
|
||||||
}, [id, isGlossaryActive, isVersionsView, action]);
|
}, [id, isGlossaryActive, isVersionsView, action]);
|
||||||
|
|
||||||
@ -330,6 +350,43 @@ const GlossaryV1 = ({
|
|||||||
setIsTabExpanded(!isTabExpanded);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{(isLoading || isPermissionLoading) && <Loader />}
|
{(isLoading || isPermissionLoading) && <Loader />}
|
||||||
@ -349,15 +406,7 @@ const GlossaryV1 = ({
|
|||||||
!isPermissionLoading &&
|
!isPermissionLoading &&
|
||||||
!isEmpty(selectedData) &&
|
!isEmpty(selectedData) &&
|
||||||
(isGlossaryActive ? (
|
(isGlossaryActive ? (
|
||||||
<GlossaryDetails
|
glossaryContent
|
||||||
handleGlossaryDelete={onGlossaryDelete}
|
|
||||||
isTabExpanded={isTabExpanded}
|
|
||||||
isVersionView={isVersionsView}
|
|
||||||
permissions={glossaryPermission}
|
|
||||||
toggleTabExpanded={toggleTabExpanded}
|
|
||||||
updateGlossary={handleGlossaryUpdate}
|
|
||||||
updateVote={updateVote}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<GlossaryTermsV1
|
<GlossaryTermsV1
|
||||||
glossaryTerm={selectedData as GlossaryTerm}
|
glossaryTerm={selectedData as GlossaryTerm}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user