mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-29 09:42:23 +00:00
Modify the team entity specific permission for create and editUser (#22821)
* modify the team entity specific permission * cleanup around unused data * added playwright test in support of owner as team permisison (cherry picked from commit 54f47854f29308869d8f2c84e9e24ddbae8c69a8)
This commit is contained in:
parent
41ec8d18de
commit
16b03eca4e
@ -117,6 +117,16 @@ export const EDIT_USER_FOR_TEAM_RULES: PolicyRulesType[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const OWNER_TEAM_RULES: PolicyRulesType[] = [
|
||||
{
|
||||
name: 'Owner-EditRule',
|
||||
resources: ['team'],
|
||||
operations: ['Create', 'EditAll'],
|
||||
effect: 'allow',
|
||||
condition: 'isOwner()',
|
||||
},
|
||||
];
|
||||
|
||||
export const ORGANIZATION_POLICY_RULES: PolicyRulesType[] = [
|
||||
{
|
||||
name: 'OrganizationPolicy-NoOwner-Rule',
|
||||
|
||||
@ -11,10 +11,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { expect, Page, test as base } from '@playwright/test';
|
||||
import { EDIT_USER_FOR_TEAM_RULES } from '../../constant/permission';
|
||||
import {
|
||||
EDIT_USER_FOR_TEAM_RULES,
|
||||
OWNER_TEAM_RULES,
|
||||
} from '../../constant/permission';
|
||||
import { GlobalSettingOptions } from '../../constant/settings';
|
||||
import { PolicyClass } from '../../support/access-control/PoliciesClass';
|
||||
import { RolesClass } from '../../support/access-control/RolesClass';
|
||||
import { DataProduct } from '../../support/domain/DataProduct';
|
||||
import { Domain } from '../../support/domain/Domain';
|
||||
import { EntityTypeEndpoint } from '../../support/entity/Entity.interface';
|
||||
import { TableClass } from '../../support/entity/TableClass';
|
||||
import { TeamClass } from '../../support/team/TeamClass';
|
||||
@ -34,10 +39,14 @@ import {
|
||||
import { addMultiOwner } from '../../utils/entity';
|
||||
import { settingClick } from '../../utils/sidebar';
|
||||
import {
|
||||
addEmailTeam,
|
||||
addTeamOwnerToEntity,
|
||||
addUserInTeam,
|
||||
addUserTeam,
|
||||
checkTeamTabCount,
|
||||
createTeam,
|
||||
executionOnOwnerGroupTeam,
|
||||
executionOnOwnerTeam,
|
||||
hardDeleteTeam,
|
||||
searchTeam,
|
||||
softDeleteTeam,
|
||||
@ -47,13 +56,19 @@ import {
|
||||
const id = uuid();
|
||||
const dataConsumerUser = new UserClass();
|
||||
const editOnlyUser = new UserClass(); // this user will have only editUser permission in team
|
||||
const ownerUser = new UserClass();
|
||||
|
||||
let team = new TeamClass();
|
||||
const team2 = new TeamClass();
|
||||
let team2 = new TeamClass();
|
||||
let team3 = new TeamClass();
|
||||
let team4 = new TeamClass();
|
||||
const policy = new PolicyClass();
|
||||
const role = new RolesClass();
|
||||
const user = new UserClass();
|
||||
const user2 = new UserClass();
|
||||
const userName = user.data.email.split('@')[0];
|
||||
const domain = new Domain();
|
||||
const dataProduct = new DataProduct([domain]);
|
||||
|
||||
let teamDetails: {
|
||||
name?: string;
|
||||
@ -72,6 +87,7 @@ let teamDetails: {
|
||||
const test = base.extend<{
|
||||
editOnlyUserPage: Page;
|
||||
dataConsumerPage: Page;
|
||||
ownerUserPage: Page;
|
||||
}>({
|
||||
editOnlyUserPage: async ({ browser }, use) => {
|
||||
const page = await browser.newPage();
|
||||
@ -85,6 +101,12 @@ const test = base.extend<{
|
||||
await use(page);
|
||||
await page.close();
|
||||
},
|
||||
ownerUserPage: async ({ browser }, use) => {
|
||||
const page = await browser.newPage();
|
||||
await ownerUser.login(page);
|
||||
await use(page);
|
||||
await page.close();
|
||||
},
|
||||
});
|
||||
|
||||
test.describe('Teams Page', () => {
|
||||
@ -146,64 +168,11 @@ test.describe('Teams Page', () => {
|
||||
});
|
||||
|
||||
await test.step('Update email of created team', async () => {
|
||||
// Edit email
|
||||
await page.locator('[data-testid="edit-email"]').click();
|
||||
await page
|
||||
.locator('[data-testid="email-input"]')
|
||||
.fill(teamDetails.updatedEmail);
|
||||
|
||||
const saveEditEmailResponse = page.waitForResponse('/api/v1/teams/*');
|
||||
await page.locator('[data-testid="save-edit-email"]').click();
|
||||
await saveEditEmailResponse;
|
||||
|
||||
// Reload the page
|
||||
await page.reload();
|
||||
|
||||
// Check for updated email
|
||||
|
||||
await expect(page.locator('[data-testid="email-value"]')).toContainText(
|
||||
teamDetails.updatedEmail
|
||||
);
|
||||
await addEmailTeam(page, teamDetails.updatedEmail);
|
||||
});
|
||||
|
||||
await test.step('Add user to created team', async () => {
|
||||
// Navigate to users tab and add new user
|
||||
await page.locator('[data-testid="users"]').click();
|
||||
|
||||
const fetchUsersResponse = page.waitForResponse(
|
||||
'/api/v1/users?limit=25&isBot=false'
|
||||
);
|
||||
await page.locator('[data-testid="add-new-user"]').click();
|
||||
await fetchUsersResponse;
|
||||
|
||||
// Search and select the user
|
||||
await page
|
||||
.locator('[data-testid="selectable-list"] [data-testid="searchbar"]')
|
||||
.fill(user.getUserName());
|
||||
|
||||
await page
|
||||
.locator(
|
||||
`[data-testid="selectable-list"] [title="${user.getUserName()}"]`
|
||||
)
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.locator(
|
||||
`[data-testid="selectable-list"] [title="${user.getUserName()}"]`
|
||||
)
|
||||
).toHaveClass(/active/);
|
||||
|
||||
const updateTeamResponse = page.waitForResponse('/api/v1/users*');
|
||||
|
||||
// Update the team with the new user
|
||||
await page.locator('[data-testid="selectable-list-update-btn"]').click();
|
||||
await updateTeamResponse;
|
||||
|
||||
// Verify the user is added to the team
|
||||
|
||||
await expect(
|
||||
page.locator(`[data-testid="${userName.toLowerCase()}"]`)
|
||||
).toBeVisible();
|
||||
await addUserTeam(page, user, userName);
|
||||
});
|
||||
|
||||
await test.step('Remove added user from created team', async () => {
|
||||
@ -904,3 +873,134 @@ test.describe('Teams Page with Data Consumer User', () => {
|
||||
await expect(dataConsumerPage.getByTestId('add-policy')).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Teams Page action as Owner of Team', () => {
|
||||
test.slow(true);
|
||||
|
||||
let teamNoOwner = new TeamClass();
|
||||
|
||||
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await ownerUser.create(apiContext);
|
||||
await user.create(apiContext);
|
||||
await policy.create(apiContext, OWNER_TEAM_RULES);
|
||||
await role.create(apiContext, [policy.responseData.name]);
|
||||
|
||||
const ownerDataEntityReference = {
|
||||
displayName: ownerUser.responseData.displayName,
|
||||
fullyQualifiedName: ownerUser.responseData.fullyQualifiedName,
|
||||
id: ownerUser.responseData.id,
|
||||
name: ownerUser.responseData.name,
|
||||
type: 'user',
|
||||
};
|
||||
|
||||
const teamID = uuid();
|
||||
const team2ID = uuid();
|
||||
const team3ID = uuid();
|
||||
const team4ID = uuid();
|
||||
const teamNoOwnerId = uuid();
|
||||
|
||||
team = new TeamClass({
|
||||
name: `PW%-data-owner-team-${teamID}`,
|
||||
displayName: `PW Data Owner Team ${teamID}`,
|
||||
description: 'playwright data consumer team description',
|
||||
teamType: 'BusinessUnit',
|
||||
users: [user.responseData.id, ownerDataEntityReference.id],
|
||||
owners: [ownerDataEntityReference],
|
||||
defaultRoles: role.responseData.id ? [role.responseData.id] : [],
|
||||
});
|
||||
team2 = new TeamClass({
|
||||
name: `PW%-data-owner-team-${team2ID}`,
|
||||
displayName: `PW Data Owner Team ${team2ID}`,
|
||||
description: 'playwright data consumer team description',
|
||||
teamType: 'Department',
|
||||
users: [user.responseData.id, ownerDataEntityReference.id],
|
||||
owners: [ownerDataEntityReference],
|
||||
defaultRoles: role.responseData.id ? [role.responseData.id] : [],
|
||||
});
|
||||
team3 = new TeamClass({
|
||||
name: `PW%-data-owner-team-${team3ID}`,
|
||||
displayName: `PW Data Owner Team ${team3ID}`,
|
||||
description: 'playwright data consumer team description',
|
||||
teamType: 'Division',
|
||||
users: [user.responseData.id, ownerDataEntityReference.id],
|
||||
owners: [ownerDataEntityReference],
|
||||
defaultRoles: role.responseData.id ? [role.responseData.id] : [],
|
||||
});
|
||||
team4 = new TeamClass({
|
||||
name: `PW%-data-owner-team-${team4ID}`,
|
||||
displayName: `PW Data Owner Team ${team4ID}`,
|
||||
description: 'playwright data consumer team description',
|
||||
teamType: 'Group',
|
||||
owners: [ownerDataEntityReference],
|
||||
defaultRoles: role.responseData.id ? [role.responseData.id] : [],
|
||||
});
|
||||
teamNoOwner = new TeamClass({
|
||||
name: `PW%-data-owner-team-${teamNoOwnerId}`,
|
||||
displayName: `PW Data Owner Team ${teamNoOwnerId}`,
|
||||
description: 'playwright data consumer team description',
|
||||
teamType: 'BusinessUnit',
|
||||
});
|
||||
|
||||
await team.create(apiContext);
|
||||
await team2.create(apiContext);
|
||||
await team3.create(apiContext);
|
||||
await team4.create(apiContext);
|
||||
await teamNoOwner.create(apiContext);
|
||||
await domain.create(apiContext);
|
||||
await dataProduct.create(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.beforeEach('Visit Home Page', async ({ ownerUserPage }) => {
|
||||
await redirectToHomePage(ownerUserPage);
|
||||
});
|
||||
|
||||
test('User as not owner should not have edit/create permission on Team', async ({
|
||||
ownerUserPage,
|
||||
}) => {
|
||||
await teamNoOwner.visitTeamPage(ownerUserPage);
|
||||
|
||||
await expect(ownerUserPage.getByTestId('manage-button')).not.toBeVisible();
|
||||
|
||||
await expect(ownerUserPage.getByTestId('add-domain')).not.toBeVisible();
|
||||
|
||||
await expect(ownerUserPage.getByTestId('edit-owner')).not.toBeVisible();
|
||||
|
||||
await expect(ownerUserPage.getByTestId('edit-email')).not.toBeVisible();
|
||||
|
||||
await expect(
|
||||
ownerUserPage.getByTestId('add-placeholder-button')
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test(`Add New Team in BusinessUnit Team`, async ({ ownerUserPage }) => {
|
||||
await executionOnOwnerTeam(ownerUserPage, team, {
|
||||
domain: domain,
|
||||
email: teamDetails.updatedEmail,
|
||||
});
|
||||
});
|
||||
|
||||
test(`Add New Team in Department Team`, async ({ ownerUserPage }) => {
|
||||
await executionOnOwnerTeam(ownerUserPage, team2, {
|
||||
domain: domain,
|
||||
email: teamDetails.updatedEmail,
|
||||
});
|
||||
});
|
||||
|
||||
test(`Add New Team in Division Team`, async ({ ownerUserPage }) => {
|
||||
await executionOnOwnerTeam(ownerUserPage, team3, {
|
||||
domain: domain,
|
||||
email: teamDetails.updatedEmail,
|
||||
});
|
||||
});
|
||||
|
||||
test(`Add New User in Group Team`, async ({ ownerUserPage }) => {
|
||||
await executionOnOwnerGroupTeam(ownerUserPage, team4, {
|
||||
domain: domain,
|
||||
email: teamDetails.updatedEmail,
|
||||
user,
|
||||
userName,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -15,6 +15,7 @@ import { GlobalSettingOptions } from '../../constant/settings';
|
||||
import { uuid } from '../../utils/common';
|
||||
import { settingClick } from '../../utils/sidebar';
|
||||
import { searchTeam } from '../../utils/team';
|
||||
import { EntityReference } from '../entity/Entity.interface';
|
||||
type ResponseDataType = {
|
||||
name: string;
|
||||
displayName: string;
|
||||
@ -25,6 +26,7 @@ type ResponseDataType = {
|
||||
users?: string[];
|
||||
defaultRoles?: string[];
|
||||
policies?: string[];
|
||||
owners?: EntityReference[];
|
||||
};
|
||||
|
||||
export class TeamClass {
|
||||
|
||||
@ -12,10 +12,16 @@
|
||||
*/
|
||||
import { APIRequestContext, expect, Page } from '@playwright/test';
|
||||
import { GlobalSettingOptions } from '../constant/settings';
|
||||
import { Domain } from '../support/domain/Domain';
|
||||
import { TableClass } from '../support/entity/TableClass';
|
||||
import { TeamClass } from '../support/team/TeamClass';
|
||||
import { UserClass } from '../support/user/UserClass';
|
||||
import { descriptionBox, toastNotification, uuid } from './common';
|
||||
import {
|
||||
assignDomain,
|
||||
descriptionBox,
|
||||
toastNotification,
|
||||
uuid,
|
||||
} from './common';
|
||||
import { addOwner } from './entity';
|
||||
import { validateFormNameFieldInput } from './form';
|
||||
import { settingClick } from './sidebar';
|
||||
@ -368,3 +374,129 @@ export const checkTeamTabCount = async (page: Page) => {
|
||||
)
|
||||
).toContainText(jsonRes.childrenCount.toString());
|
||||
};
|
||||
|
||||
export const addEmailTeam = async (page: Page, email: string) => {
|
||||
// Edit email
|
||||
await page.locator('[data-testid="edit-email"]').click();
|
||||
await page.locator('[data-testid="email-input"]').fill(email);
|
||||
|
||||
const saveEditEmailResponse = page.waitForResponse('/api/v1/teams/*');
|
||||
await page.locator('[data-testid="save-edit-email"]').click();
|
||||
await saveEditEmailResponse;
|
||||
|
||||
// Reload the page
|
||||
await page.reload();
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.waitForSelector('[data-testid="loader"]', {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
// Check for updated email
|
||||
await expect(page.locator('[data-testid="email-value"]')).toContainText(
|
||||
email
|
||||
);
|
||||
};
|
||||
|
||||
export const addUserTeam = async (
|
||||
page: Page,
|
||||
user: UserClass,
|
||||
userName: string
|
||||
) => {
|
||||
// Navigate to users tab and add new user
|
||||
await page.locator('[data-testid="users"]').click();
|
||||
|
||||
const fetchUsersResponse = page.waitForResponse(
|
||||
'/api/v1/users?limit=25&isBot=false'
|
||||
);
|
||||
await page.locator('[data-testid="add-new-user"]').click();
|
||||
await fetchUsersResponse;
|
||||
|
||||
// Search and select the user
|
||||
await page
|
||||
.locator('[data-testid="selectable-list"] [data-testid="searchbar"]')
|
||||
.fill(user.getUserName());
|
||||
|
||||
await page
|
||||
.locator(`[data-testid="selectable-list"] [title="${user.getUserName()}"]`)
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.locator(
|
||||
`[data-testid="selectable-list"] [title="${user.getUserName()}"]`
|
||||
)
|
||||
).toHaveClass(/active/);
|
||||
|
||||
const updateTeamResponse = page.waitForResponse('/api/v1/users*');
|
||||
|
||||
// Update the team with the new user
|
||||
await page.locator('[data-testid="selectable-list-update-btn"]').click();
|
||||
await updateTeamResponse;
|
||||
|
||||
// Verify the user is added to the team
|
||||
|
||||
await expect(
|
||||
page.locator(`[data-testid="${userName.toLowerCase()}"]`)
|
||||
).toBeVisible();
|
||||
};
|
||||
|
||||
export const executionOnOwnerTeam = async (
|
||||
page: Page,
|
||||
team: TeamClass,
|
||||
data: {
|
||||
domain: Domain;
|
||||
email: string;
|
||||
}
|
||||
) => {
|
||||
await team.visitTeamPage(page);
|
||||
|
||||
await expect(page.getByTestId('manage-button')).toBeVisible();
|
||||
|
||||
await expect(page.getByTestId('edit-team-subscription')).toBeVisible();
|
||||
await expect(page.getByTestId('edit-team-type-icon')).toBeVisible();
|
||||
|
||||
await assignDomain(page, data.domain.responseData);
|
||||
|
||||
await addEmailTeam(page, data.email);
|
||||
|
||||
await page.getByTestId('add-placeholder-button').click();
|
||||
|
||||
const newTeamData = await createTeam(page);
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.waitForSelector('[data-testid="loader"]', {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.getByRole('cell', { name: newTeamData.displayName })
|
||||
).toBeVisible();
|
||||
};
|
||||
|
||||
export const executionOnOwnerGroupTeam = async (
|
||||
page: Page,
|
||||
team: TeamClass,
|
||||
data: {
|
||||
domain: Domain;
|
||||
email: string;
|
||||
user: UserClass;
|
||||
userName: string;
|
||||
}
|
||||
) => {
|
||||
await team.visitTeamPage(page);
|
||||
|
||||
await expect(
|
||||
page.getByTestId('team-details-collapse').getByTestId('manage-button')
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.getByTestId('edit-team-subscription')).toBeVisible();
|
||||
await expect(page.getByTestId('edit-team-type-icon')).not.toBeVisible();
|
||||
|
||||
await assignDomain(page, data.domain.responseData);
|
||||
|
||||
await addEmailTeam(page, data.email);
|
||||
|
||||
await addUserTeam(page, data.user, data.userName);
|
||||
};
|
||||
|
||||
@ -50,13 +50,10 @@ import {
|
||||
GlobalSettingOptions,
|
||||
GlobalSettingsMenuCategory,
|
||||
} from '../../../../constants/GlobalSettings.constants';
|
||||
import { usePermissionProvider } from '../../../../context/PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../../../../context/PermissionProvider/PermissionProvider.interface';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from '../../../../enums/common.enum';
|
||||
import { EntityAction, EntityType } from '../../../../enums/entity.enum';
|
||||
import { SearchIndex } from '../../../../enums/search.enum';
|
||||
import { OwnerType } from '../../../../enums/user.enum';
|
||||
import { Operation } from '../../../../generated/entity/policies/policy';
|
||||
import { Team, TeamType } from '../../../../generated/entity/teams/team';
|
||||
import {
|
||||
EntityReference as UserTeams,
|
||||
@ -73,7 +70,6 @@ import { exportTeam, restoreTeam } from '../../../../rest/teamsAPI';
|
||||
import { Transi18next } from '../../../../utils/CommonUtils';
|
||||
import { getEntityName } from '../../../../utils/EntityUtils';
|
||||
import { getSettingPageEntityBreadCrumb } from '../../../../utils/GlobalSettingsUtils';
|
||||
import { checkPermission } from '../../../../utils/PermissionsUtils';
|
||||
import {
|
||||
getSettingsPathWithFqn,
|
||||
getTeamsWithFqnPath,
|
||||
@ -153,7 +149,6 @@ const TeamDetailsV1 = ({
|
||||
state: false,
|
||||
leave: false,
|
||||
};
|
||||
const { permissions } = usePermissionProvider();
|
||||
const currentTab = useMemo(() => {
|
||||
if (activeTab) {
|
||||
return activeTab;
|
||||
@ -211,16 +206,12 @@ const TeamDetailsV1 = ({
|
||||
navigate({ search: Qs.stringify({ activeTab: key }) });
|
||||
};
|
||||
|
||||
const { createTeamPermission, editUserPermission } = useMemo(() => {
|
||||
const { editUserPermission } = useMemo(() => {
|
||||
return {
|
||||
createTeamPermission:
|
||||
!isEmpty(permissions) &&
|
||||
checkPermission(Operation.Create, ResourceEntity.TEAM, permissions),
|
||||
editUserPermission:
|
||||
checkPermission(Operation.EditAll, ResourceEntity.TEAM, permissions) ||
|
||||
checkPermission(Operation.EditUsers, ResourceEntity.TEAM, permissions),
|
||||
entityPermissions.EditAll || entityPermissions.EditUsers,
|
||||
};
|
||||
}, [permissions]);
|
||||
}, [entityPermissions]);
|
||||
|
||||
/**
|
||||
* Take user id as input to find out the user data and set it for delete
|
||||
@ -674,7 +665,7 @@ const TeamDetailsV1 = ({
|
||||
</ErrorPlaceHolder>
|
||||
) : (
|
||||
<TeamHierarchy
|
||||
createTeamPermission={createTeamPermission}
|
||||
createTeamPermission={entityPermissions.Create}
|
||||
currentTeam={currentTeam}
|
||||
data={childTeamList}
|
||||
handleAddTeamButtonClick={handleAddTeamButtonClick}
|
||||
@ -693,7 +684,7 @@ const TeamDetailsV1 = ({
|
||||
currentTeam,
|
||||
childTeamList,
|
||||
showDeletedTeam,
|
||||
createTeamPermission,
|
||||
entityPermissions.Create,
|
||||
isFetchingAllTeamAdvancedDetails,
|
||||
onTeamExpand,
|
||||
handleAddTeamButtonClick,
|
||||
@ -1112,7 +1103,7 @@ const TeamDetailsV1 = ({
|
||||
if (isEmpty(currentTeam)) {
|
||||
return fetchErrorPlaceHolder({
|
||||
onClick: () => handleAddTeam(true),
|
||||
permission: createTeamPermission,
|
||||
permission: entityPermissions.Create,
|
||||
heading: t('label.team-plural'),
|
||||
doc: TEAMS_DOCS,
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user