diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/EntityPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/EntityPermissions.spec.ts index 9391560f10f3..c286ff83828b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/EntityPermissions.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/Permissions/EntityPermissions.spec.ts @@ -11,8 +11,11 @@ * limitations under the License. */ -import { Page, test as base } from '@playwright/test'; +import { expect, Page, test as base } from '@playwright/test'; +import { PolicyClass } from '../../../support/access-control/PoliciesClass'; +import { RolesClass } from '../../../support/access-control/RolesClass'; import { EntityClass } from '../../../support/entity/EntityClass'; +import { TableClass } from '../../../support/entity/TableClass'; import { UserClass } from '../../../support/user/UserClass'; import { performAdminLogin } from '../../../utils/admin'; import { getApiContext } from '../../../utils/common'; @@ -26,8 +29,75 @@ import { assignRoleToUser, cleanupPermissions, initializePermissions, + setupUserWithPolicy, } from '../../../utils/permission'; +const EDIT_ALL_ALLOW_SPECIFIC_DENY_RULES = [ + { + name: 'ViewAll-Rule', + resources: ['All'], + operations: ['ViewAll'], + effect: 'allow', + }, + { + name: 'EditAll-Allow-Rule', + resources: ['All'], + operations: ['EditAll'], + effect: 'allow', + }, + { + name: 'EditTier-Deny-Rule', + resources: ['All'], + operations: ['EditTier'], + effect: 'deny', + }, + { + name: 'EditOwners-Deny-Rule', + resources: ['All'], + operations: ['EditOwners'], + effect: 'deny', + }, + { + name: 'EditCertification-Deny-Rule', + resources: ['All'], + operations: ['EditCertification'], + effect: 'deny', + }, +]; + +const SPECIFIC_ALLOW_EDIT_ALL_DENY_RULES = [ + { + name: 'ViewAll-Rule', + resources: ['All'], + operations: ['ViewAll'], + effect: 'allow', + }, + { + name: 'EditAll-Deny-Rule', + resources: ['All'], + operations: ['EditAll'], + effect: 'deny', + }, + { + name: 'EditTier-Allow-Rule', + resources: ['All'], + operations: ['EditTier'], + effect: 'allow', + }, + { + name: 'EditOwners-Allow-Rule', + resources: ['All'], + operations: ['EditOwners'], + effect: 'allow', + }, + { + name: 'EditCertification-Allow-Rule', + resources: ['All'], + operations: ['EditCertification'], + effect: 'allow', + }, +]; + const adminUser = new UserClass(); const testUser = new UserClass(); @@ -70,6 +140,111 @@ test.afterAll('Cleanup pre-requests', async ({ browser }) => { await afterAction(); }); +let editAllUser: UserClass; +let editAllPolicy: PolicyClass; +let editAllRole: RolesClass; + +let specificEditsUser: UserClass; +let specificEditsPolicy: PolicyClass; +let specificEditsRole: RolesClass; + +let headerPermTable: TableClass; + +const headerPermTest = base.extend<{ + editAllPage: Page; + specificEditsPage: Page; + denyAllPage: Page; +}>({ + editAllPage: async ({ browser }, use) => { + const page = await browser.newPage(); + try { + await editAllUser.login(page); + await use(page); + } finally { + await page.close(); + } + }, + specificEditsPage: async ({ browser }, use) => { + const page = await browser.newPage(); + try { + await specificEditsUser.login(page); + await use(page); + } finally { + await page.close(); + } + }, +}); + +headerPermTest.describe( + 'DataAsset Header – EditTier / EditOwners / EditCertification permissions', + () => { + headerPermTest.beforeAll( + 'Setup users, roles, and table', + async ({ browser }) => { + editAllUser = new UserClass(); + editAllPolicy = new PolicyClass(); + editAllRole = new RolesClass(); + + specificEditsUser = new UserClass(); + specificEditsPolicy = new PolicyClass(); + specificEditsRole = new RolesClass(); + + headerPermTable = new TableClass(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + + await headerPermTable.create(apiContext); + + await setupUserWithPolicy( + apiContext, + editAllUser, + editAllPolicy, + editAllRole, + EDIT_ALL_ALLOW_SPECIFIC_DENY_RULES + ); + await setupUserWithPolicy( + apiContext, + specificEditsUser, + specificEditsPolicy, + specificEditsRole, + SPECIFIC_ALLOW_EDIT_ALL_DENY_RULES + ); + await afterAction(); + } + ); + + headerPermTest( + 'EditAll allowed but EditTier, EditOwners, EditCertification denied – edit buttons not visible', + async ({ editAllPage }) => { + await headerPermTable.visitEntityPage(editAllPage); + + await expect(editAllPage.getByTestId('edit-tier')).not.toBeVisible(); + await expect(editAllPage.getByTestId('edit-owner')).not.toBeVisible(); + await expect( + editAllPage.getByTestId('edit-certification') + ).not.toBeVisible(); + } + ); + + headerPermTest( + 'EditTier, EditOwners, EditCertification allowed but EditAll denied – edit buttons not visible', + async ({ specificEditsPage }) => { + await headerPermTable.visitEntityPage(specificEditsPage); + + await expect( + specificEditsPage.getByTestId('edit-tier') + ).not.toBeVisible(); + await expect( + specificEditsPage.getByTestId('edit-owner') + ).not.toBeVisible(); + await expect( + specificEditsPage.getByTestId('edit-certification') + ).not.toBeVisible(); + } + ); + } +); + Object.entries(entityConfig).forEach(([, config]) => { const entity = new config.class(); const entityType = entity.getType(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx index 1a4d25f89454..df9979dbcccf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx @@ -52,6 +52,7 @@ import { } from '../../../generated/entity/data/dataContract'; import { EntityStatus } from '../../../generated/entity/data/glossaryTerm'; import { Table } from '../../../generated/entity/data/table'; +import { Operation } from '../../../generated/entity/policies/policy'; import { EntityReference } from '../../../generated/type/entityReference'; import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { useCustomPages } from '../../../hooks/useCustomPages'; @@ -77,6 +78,7 @@ import { getEntityName, getEntityVoteStatus, } from '../../../utils/EntityUtils'; +import { getPrioritizedEditPermission } from '../../../utils/PermissionsUtils'; import { getEntityDetailsPath } from '../../../utils/RouterUtils'; import serviceUtilClassBase from '../../../utils/ServiceUtilClassBase'; import { getEntityTypeFromServiceCategory } from '../../../utils/ServiceUtils'; @@ -427,12 +429,16 @@ export const DataAssetsHeader = ({ () => ({ editDomainPermission: permissions.EditAll && !dataAsset.deleted, editOwnerPermission: - (permissions.EditAll || permissions.EditOwners) && !dataAsset.deleted, + getPrioritizedEditPermission(permissions, Operation.EditOwners) && + !dataAsset.deleted, editTierPermission: - (permissions.EditAll || permissions.EditTier) && !dataAsset.deleted, - editCertificationPermission: - (permissions.EditAll || permissions.EditCertification) && + getPrioritizedEditPermission(permissions, Operation.EditTier) && !dataAsset.deleted, + editCertificationPermission: + getPrioritizedEditPermission( + permissions, + Operation.EditCertification + ) && !dataAsset.deleted, }), [permissions, dataAsset] );