mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-01 19:18:05 +00:00
supported editUser permission in user tab for team page (#18987)
* supported editUser permission in user tab for team page * remove edit all permission check in teams add/remove user api * added playwright test for the editUser permission * Added playwright test for data consumer user and remove no used field from the advance api call --------- Co-authored-by: sonikashah <sonikashah94@gmail.com> Co-authored-by: sonika-shah <58761340+sonika-shah@users.noreply.github.com>
This commit is contained in:
parent
ea5a246a44
commit
9e6078f654
@ -72,7 +72,6 @@ import org.openmetadata.service.limits.Limits;
|
||||
import org.openmetadata.service.resources.Collection;
|
||||
import org.openmetadata.service.resources.EntityResource;
|
||||
import org.openmetadata.service.security.Authorizer;
|
||||
import org.openmetadata.service.security.policyevaluator.OperationContext;
|
||||
import org.openmetadata.service.util.CSVExportResponse;
|
||||
import org.openmetadata.service.util.EntityUtil;
|
||||
import org.openmetadata.service.util.JsonUtils;
|
||||
@ -687,10 +686,6 @@ public class TeamResource extends EntityResource<Team, TeamRepository> {
|
||||
@Context SecurityContext securityContext,
|
||||
@PathParam("teamId") UUID teamId,
|
||||
List<EntityReference> users) {
|
||||
|
||||
OperationContext operationContext =
|
||||
new OperationContext(entityType, MetadataOperation.EDIT_ALL);
|
||||
authorizer.authorize(securityContext, operationContext, getResourceContextById(teamId));
|
||||
return repository
|
||||
.updateTeamUsers(securityContext.getUserPrincipal().getName(), teamId, users)
|
||||
.toResponse();
|
||||
@ -721,10 +716,6 @@ public class TeamResource extends EntityResource<Team, TeamRepository> {
|
||||
@Parameter(description = "Id of the user being removed", schema = @Schema(type = "string"))
|
||||
@PathParam("userId")
|
||||
String userId) {
|
||||
|
||||
OperationContext operationContext =
|
||||
new OperationContext(entityType, MetadataOperation.EDIT_ALL);
|
||||
authorizer.authorize(securityContext, operationContext, getResourceContextById(teamId));
|
||||
return repository
|
||||
.deleteTeamUser(
|
||||
securityContext.getUserPrincipal().getName(), teamId, UUID.fromString(userId))
|
||||
|
||||
@ -77,6 +77,15 @@ export const DATA_CONSUMER_RULES: PolicyRulesType[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const EDIT_USER_FOR_TEAM_RULES: PolicyRulesType[] = [
|
||||
{
|
||||
name: 'EditUserTeams-EditRule',
|
||||
resources: ['team'],
|
||||
operations: ['EditUsers'],
|
||||
effect: 'allow',
|
||||
},
|
||||
];
|
||||
|
||||
export const ORGANIZATION_POLICY_RULES: PolicyRulesType[] = [
|
||||
{
|
||||
name: 'OrganizationPolicy-NoOwner-Rule',
|
||||
|
||||
@ -10,12 +10,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import test, { expect } from '@playwright/test';
|
||||
import { expect, Page, test as base } from '@playwright/test';
|
||||
import { EDIT_USER_FOR_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 { EntityTypeEndpoint } from '../../support/entity/Entity.interface';
|
||||
import { TableClass } from '../../support/entity/TableClass';
|
||||
import { TeamClass } from '../../support/team/TeamClass';
|
||||
import { UserClass } from '../../support/user/UserClass';
|
||||
import { performAdminLogin } from '../../utils/admin';
|
||||
import {
|
||||
createNewPage,
|
||||
descriptionBox,
|
||||
@ -28,6 +32,7 @@ import { addMultiOwner } from '../../utils/entity';
|
||||
import { settingClick } from '../../utils/sidebar';
|
||||
import {
|
||||
addTeamOwnerToEntity,
|
||||
addUserInTeam,
|
||||
createTeam,
|
||||
hardDeleteTeam,
|
||||
searchTeam,
|
||||
@ -35,10 +40,15 @@ import {
|
||||
verifyAssetsInTeamsPage,
|
||||
} from '../../utils/team';
|
||||
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
const id = uuid();
|
||||
const dataConsumerUser = new UserClass();
|
||||
const editOnlyUser = new UserClass(); // this user will have only editUser permission in team
|
||||
let team = new TeamClass();
|
||||
const team2 = 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];
|
||||
|
||||
let teamDetails: {
|
||||
@ -55,7 +65,28 @@ let teamDetails: {
|
||||
updatedEmail: `pwteamUpdated${uuid()}@example.com`,
|
||||
};
|
||||
|
||||
const test = base.extend<{
|
||||
editOnlyUserPage: Page;
|
||||
dataConsumerPage: Page;
|
||||
}>({
|
||||
editOnlyUserPage: async ({ browser }, use) => {
|
||||
const page = await browser.newPage();
|
||||
await editOnlyUser.login(page);
|
||||
await use(page);
|
||||
await page.close();
|
||||
},
|
||||
dataConsumerPage: async ({ browser }, use) => {
|
||||
const page = await browser.newPage();
|
||||
await dataConsumerUser.login(page);
|
||||
await use(page);
|
||||
await page.close();
|
||||
},
|
||||
});
|
||||
|
||||
test.describe('Teams Page', () => {
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
test.slow(true);
|
||||
|
||||
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||
@ -636,3 +667,199 @@ test.describe('Teams Page', () => {
|
||||
await afterAction();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Teams Page with EditUser Permission', () => {
|
||||
test.slow(true);
|
||||
|
||||
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await editOnlyUser.create(apiContext);
|
||||
|
||||
const id = uuid();
|
||||
await policy.create(apiContext, EDIT_USER_FOR_TEAM_RULES);
|
||||
await role.create(apiContext, [policy.responseData.name]);
|
||||
|
||||
team = new TeamClass({
|
||||
name: `PW%edit-user-team-${id}`,
|
||||
displayName: `PW Edit User Team ${id}`,
|
||||
description: 'playwright edit user team description',
|
||||
teamType: 'Group',
|
||||
users: [editOnlyUser.responseData.id],
|
||||
defaultRoles: role.responseData.id ? [role.responseData.id] : [],
|
||||
});
|
||||
await team.create(apiContext);
|
||||
await team2.create(apiContext);
|
||||
await user.create(apiContext);
|
||||
await user2.create(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.afterAll('Cleanup', async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await user.delete(apiContext);
|
||||
await user2.delete(apiContext);
|
||||
await editOnlyUser.delete(apiContext);
|
||||
await team.delete(apiContext);
|
||||
await team2.delete(apiContext);
|
||||
await policy.delete(apiContext);
|
||||
await role.delete(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.beforeEach('Visit Home Page', async ({ editOnlyUserPage }) => {
|
||||
await redirectToHomePage(editOnlyUserPage);
|
||||
await team2.visitTeamPage(editOnlyUserPage);
|
||||
});
|
||||
|
||||
test('Add and Remove User for Team', async ({ editOnlyUserPage }) => {
|
||||
await test.step('Add user in Team from the placeholder', async () => {
|
||||
await addUserInTeam(editOnlyUserPage, user);
|
||||
});
|
||||
|
||||
await test.step('Add user in Team for the header manage area', async () => {
|
||||
await addUserInTeam(editOnlyUserPage, user2);
|
||||
});
|
||||
|
||||
await test.step('Remove user from Team', async () => {
|
||||
await editOnlyUserPage
|
||||
.getByRole('row', {
|
||||
name: `${user.data.firstName.slice(0, 1).toUpperCase()} ${
|
||||
user.data.firstName
|
||||
}.`,
|
||||
})
|
||||
.getByTestId('remove-user-btn')
|
||||
.click();
|
||||
|
||||
const userResponse = editOnlyUserPage.waitForResponse(
|
||||
'/api/v1/users?fields=**'
|
||||
);
|
||||
await editOnlyUserPage.getByRole('button', { name: 'Confirm' }).click();
|
||||
await userResponse;
|
||||
|
||||
await expect(
|
||||
editOnlyUserPage.locator(`[data-testid="${userName.toLowerCase()}"]`)
|
||||
).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Teams Page with Data Consumer User', () => {
|
||||
test.slow(true);
|
||||
|
||||
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await dataConsumerUser.create(apiContext);
|
||||
await user.create(apiContext);
|
||||
await policy.create(apiContext, EDIT_USER_FOR_TEAM_RULES);
|
||||
await role.create(apiContext, [policy.responseData.name]);
|
||||
|
||||
team = new TeamClass({
|
||||
name: `PW%-data-consumer-team-${id}`,
|
||||
displayName: `PW Data Consumer Team ${id}`,
|
||||
description: 'playwright data consumer team description',
|
||||
teamType: 'Group',
|
||||
users: [user.responseData.id],
|
||||
defaultRoles: role.responseData.id ? [role.responseData.id] : [],
|
||||
});
|
||||
await team.create(apiContext);
|
||||
await team2.create(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.afterAll('Cleanup', async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await dataConsumerUser.delete(apiContext);
|
||||
await user.delete(apiContext);
|
||||
await team.delete(apiContext);
|
||||
await team2.delete(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.beforeEach('Visit Home Page', async ({ dataConsumerPage }) => {
|
||||
await redirectToHomePage(dataConsumerPage);
|
||||
});
|
||||
|
||||
test('Should not have edit access on team page with no data available', async ({
|
||||
dataConsumerPage,
|
||||
}) => {
|
||||
await team2.visitTeamPage(dataConsumerPage);
|
||||
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('edit-team-name')
|
||||
).not.toBeVisible();
|
||||
await expect(dataConsumerPage.getByTestId('add-domain')).not.toBeVisible();
|
||||
await expect(dataConsumerPage.getByTestId('edit-owner')).not.toBeVisible();
|
||||
await expect(dataConsumerPage.getByTestId('edit-email')).not.toBeVisible();
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('edit-team-subscription')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('manage-button')
|
||||
).not.toBeVisible();
|
||||
|
||||
await expect(dataConsumerPage.getByTestId('join-teams')).toBeVisible();
|
||||
|
||||
// User Tab
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('add-new-user')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('permission-error-placeholder')
|
||||
).toBeVisible();
|
||||
|
||||
// Asset Tab
|
||||
const assetResponse = dataConsumerPage.waitForResponse(
|
||||
'/api/v1/search/query?**'
|
||||
);
|
||||
await dataConsumerPage.getByTestId('assets').click();
|
||||
await assetResponse;
|
||||
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('add-placeholder-button')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('no-data-placeholder')
|
||||
).toBeVisible();
|
||||
|
||||
// Role Tab
|
||||
await dataConsumerPage.getByTestId('roles').click();
|
||||
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('add-placeholder-button')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('permission-error-placeholder')
|
||||
).toBeVisible();
|
||||
|
||||
// Policies Tab
|
||||
await dataConsumerPage.getByTestId('policies').click();
|
||||
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('add-placeholder-button')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('permission-error-placeholder')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('Should not have edit access on team page with data available', async ({
|
||||
dataConsumerPage,
|
||||
}) => {
|
||||
await team.visitTeamPage(dataConsumerPage);
|
||||
|
||||
// User Tab
|
||||
await expect(
|
||||
dataConsumerPage.getByTestId('add-new-user')
|
||||
).not.toBeVisible();
|
||||
|
||||
// Role Tab
|
||||
await dataConsumerPage.getByTestId('roles').click();
|
||||
|
||||
await expect(dataConsumerPage.getByTestId('add-role')).not.toBeVisible();
|
||||
|
||||
// Policies Tab
|
||||
await dataConsumerPage.getByTestId('policies').click();
|
||||
|
||||
await expect(dataConsumerPage.getByTestId('add-policy')).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@ -10,8 +10,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { APIRequestContext } from '@playwright/test';
|
||||
import { APIRequestContext, expect, Page } from '@playwright/test';
|
||||
import { GlobalSettingOptions } from '../../constant/settings';
|
||||
import { uuid } from '../../utils/common';
|
||||
import { settingClick } from '../../utils/sidebar';
|
||||
import { searchTeam } from '../../utils/team';
|
||||
type ResponseDataType = {
|
||||
name: string;
|
||||
displayName: string;
|
||||
@ -46,6 +49,28 @@ export class TeamClass {
|
||||
return this.responseData;
|
||||
}
|
||||
|
||||
async visitTeamPage(page: Page) {
|
||||
// complete url since we are making basic and advance call to get the details of the team
|
||||
const fetchOrganizationResponse = page.waitForResponse(
|
||||
`/api/v1/teams/name/Organization?fields=users%2CdefaultRoles%2Cpolicies%2CchildrenCount%2Cdomains&include=all`
|
||||
);
|
||||
await settingClick(page, GlobalSettingOptions.TEAMS);
|
||||
await fetchOrganizationResponse;
|
||||
|
||||
await searchTeam(page, this.responseData?.['displayName']);
|
||||
|
||||
await page
|
||||
.locator(`[data-row-key="${this.data.name}"]`)
|
||||
.getByRole('link')
|
||||
.click();
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await expect(page.getByTestId('team-heading')).toHaveText(
|
||||
this.data.displayName
|
||||
);
|
||||
}
|
||||
|
||||
async create(apiContext: APIRequestContext) {
|
||||
const response = await apiContext.post('/api/v1/teams', {
|
||||
data: this.data,
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
import { APIRequestContext, expect, Page } from '@playwright/test';
|
||||
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 { addOwner } from './entity';
|
||||
import { validateFormNameFieldInput } from './form';
|
||||
@ -316,3 +317,39 @@ export const verifyAssetsInTeamsPage = async (
|
||||
page.getByTestId('assets').getByTestId('filter-count')
|
||||
).toContainText(assetCount.toString());
|
||||
};
|
||||
|
||||
export const addUserInTeam = async (page: Page, user: UserClass) => {
|
||||
const userName = user.data.email.split('@')[0];
|
||||
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();
|
||||
};
|
||||
|
||||
@ -105,6 +105,11 @@ export const UserTab = ({
|
||||
[currentTeam.teamType]
|
||||
);
|
||||
|
||||
const editUserPermission = useMemo(
|
||||
() => permission.EditAll || permission.EditUsers,
|
||||
[permission.EditAll, permission.EditUsers]
|
||||
);
|
||||
|
||||
/**
|
||||
* Make API call to fetch current team user data
|
||||
*/
|
||||
@ -213,13 +218,13 @@ export const UserTab = ({
|
||||
<Tooltip
|
||||
placement="left"
|
||||
title={
|
||||
permission.EditAll
|
||||
editUserPermission
|
||||
? t('label.remove')
|
||||
: t('message.no-permission-for-action')
|
||||
}>
|
||||
<Button
|
||||
data-testid="remove-user-btn"
|
||||
disabled={!permission.EditAll}
|
||||
disabled={!editUserPermission}
|
||||
icon={
|
||||
<IconRemove height={16} name={t('label.remove')} width={16} />
|
||||
}
|
||||
@ -235,7 +240,7 @@ export const UserTab = ({
|
||||
return tabColumns.filter((column) =>
|
||||
column.key === 'actions' ? !isTeamDeleted : true
|
||||
);
|
||||
}, [handleRemoveClick, permission, isTeamDeleted]);
|
||||
}, [handleRemoveClick, editUserPermission, isTeamDeleted]);
|
||||
|
||||
const sortedUser = useMemo(() => orderBy(users, ['name'], 'asc'), [users]);
|
||||
|
||||
@ -329,10 +334,10 @@ export const UserTab = ({
|
||||
<Button
|
||||
ghost
|
||||
className={classNames({
|
||||
'p-x-lg': permission.EditAll && !isTeamDeleted,
|
||||
'p-x-lg': editUserPermission && !isTeamDeleted,
|
||||
})}
|
||||
data-testid="add-new-user"
|
||||
disabled={!permission.EditAll || isTeamDeleted}
|
||||
disabled={!editUserPermission || isTeamDeleted}
|
||||
icon={<PlusOutlined />}
|
||||
type="primary">
|
||||
{t('label.add')}
|
||||
@ -352,7 +357,7 @@ export const UserTab = ({
|
||||
}
|
||||
className="mt-0-important"
|
||||
heading={t('label.user')}
|
||||
permission={permission.EditAll}
|
||||
permission={editUserPermission}
|
||||
type={ERROR_PLACEHOLDER_TYPE.ASSIGN}
|
||||
/>
|
||||
) : (
|
||||
@ -382,7 +387,7 @@ export const UserTab = ({
|
||||
{!currentTeam.deleted && isGroupType && (
|
||||
<Col>
|
||||
<Space>
|
||||
{users.length > 0 && permission.EditAll && (
|
||||
{users.length > 0 && editUserPermission && (
|
||||
<UserSelectableList
|
||||
hasPermission
|
||||
selectedUsers={currentTeam?.users ?? []}
|
||||
|
||||
@ -164,7 +164,6 @@ describe('Test Teams Page', () => {
|
||||
{
|
||||
fields: [
|
||||
'users',
|
||||
'userCount',
|
||||
'defaultRoles',
|
||||
'policies',
|
||||
'childrenCount',
|
||||
|
||||
@ -244,7 +244,6 @@ const TeamsPage = () => {
|
||||
const data = await getTeamByName(name, {
|
||||
fields: [
|
||||
TabSpecificField.USERS,
|
||||
TabSpecificField.USER_COUNT,
|
||||
TabSpecificField.DEFAULT_ROLES,
|
||||
TabSpecificField.POLICIES,
|
||||
TabSpecificField.CHILDREN_COUNT,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user