Skip to content

Commit

Permalink
Display View button if user can list roles (#50889)
Browse files Browse the repository at this point in the history
This fixes a regression that caused users who could list/read roles but
NOT edit them to be unable to view the details of the role.

This PR will change the text to "view details" if the user can _only_
view and not edit.

The edit button in the role editor already has logic to prevent and tell
the user they do not have permissions to edit, so no further changes
need to be made there.
  • Loading branch information
avatus authored Jan 9, 2025
1 parent d50ba0a commit 36b85dd
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 10 deletions.
11 changes: 9 additions & 2 deletions web/packages/teleport/src/Roles/RoleList/RoleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function RoleList({
serversidePagination: SeversidePagination<RoleResource>;
rolesAcl: Access;
}) {
const canView = rolesAcl.list && rolesAcl.read;
const canEdit = rolesAcl.edit;
const canDelete = rolesAcl.remove;

Expand Down Expand Up @@ -74,6 +75,7 @@ export function RoleList({
altKey: 'options-btn',
render: (role: RoleResource) => (
<ActionCell
canView={canView}
canDelete={canDelete}
canEdit={canEdit}
onEdit={() => onEdit(role.id)}
Expand All @@ -89,18 +91,23 @@ export function RoleList({
}

const ActionCell = (props: {
canView: boolean;
canEdit: boolean;
canDelete: boolean;
onEdit(): void;
onDelete(): void;
}) => {
if (!(props.canEdit || props.canDelete)) {
if (!(props.canView || props.canDelete)) {
return <Cell align="right" />;
}
return (
<Cell align="right">
<MenuButton>
{props.canEdit && <MenuItem onClick={props.onEdit}>Edit</MenuItem>}
{props.canView && (
<MenuItem onClick={props.onEdit}>
{props.canEdit ? 'Edit' : 'View Details'}
</MenuItem>
)}
{props.canDelete && (
<MenuItem onClick={props.onDelete}>Delete</MenuItem>
)}
Expand Down
50 changes: 42 additions & 8 deletions web/packages/teleport/src/Roles/Roles.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ describe('Roles list', () => {
expect(menuItems).toHaveLength(2);
});

test('hides edit button if no access', async () => {
test('hides view/edit button if no access', async () => {
const ctx = createTeleportContext();
const testState = {
...defaultState,
rolesAcl: {
...defaultState.rolesAcl,
edit: false,
list: false,
},
};

Expand All @@ -146,12 +146,15 @@ describe('Roles list', () => {
fireEvent.click(optionsButton);
const menuItems = screen.queryAllByRole('menuitem');
expect(menuItems).toHaveLength(1);
expect(menuItems.every(item => item.textContent.includes('Edit'))).not.toBe(
true
);
expect(
menuItems.every(
item =>
item.textContent.includes('View') || item.textContent.includes('Edit')
)
).not.toBe(true);
});

test('hides delete button if no access', async () => {
test('hides delete button if user does not have permission to delete', async () => {
const ctx = createTeleportContext();
const testState = {
...defaultState,
Expand Down Expand Up @@ -183,12 +186,14 @@ describe('Roles list', () => {
).not.toBe(true);
});

test('hides Options button if no permissions to edit or delete', async () => {
test('displays Options button if user has permission to list/read roles', async () => {
const ctx = createTeleportContext();
const testState = {
...defaultState,
rolesAcl: {
...defaultState.rolesAcl,
list: true,
read: true,
create: false,
remove: false,
edit: false,
},
Expand All @@ -202,6 +207,35 @@ describe('Roles list', () => {
</MemoryRouter>
);

await waitFor(() => {
expect(screen.getByText('cool-role')).toBeInTheDocument();
});
const optionsButton = screen.getByRole('button', { name: /options/i });
fireEvent.click(optionsButton);
const menuItems = screen.queryAllByRole('menuitem');
expect(menuItems).toHaveLength(1);
expect(menuItems[0]).toHaveTextContent('View');
});

test('hides Options button if no permissions to view or delete', async () => {
const ctx = createTeleportContext();
const testState = {
...defaultState,
rolesAcl: {
...defaultState.rolesAcl,
remove: false,
list: false,
},
};

render(
<MemoryRouter>
<ContextProvider ctx={ctx}>
<Roles {...testState} />
</ContextProvider>
</MemoryRouter>
);

await waitFor(() => {
expect(screen.getByText('cool-role')).toBeInTheDocument();
});
Expand Down

0 comments on commit 36b85dd

Please sign in to comment.