fix(security): added restrictions to see user names [EE-5825] (#10296)
* fix(security): added restrictions to see user names [EE-5825] * use pluralize methodpull/10300/head
parent
60477ae287
commit
4c16594a25
|
@ -4,6 +4,7 @@ import { createMockTeams, createMockUsers } from '@/react-tools/test-mocks';
|
||||||
import { renderWithQueryClient } from '@/react-tools/test-utils';
|
import { renderWithQueryClient } from '@/react-tools/test-utils';
|
||||||
import { rest, server } from '@/setup-tests/server';
|
import { rest, server } from '@/setup-tests/server';
|
||||||
import { Role } from '@/portainer/users/types';
|
import { Role } from '@/portainer/users/types';
|
||||||
|
import { withUserProvider } from '@/react/test-utils/withUserProvider';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ResourceControlOwnership,
|
ResourceControlOwnership,
|
||||||
|
@ -143,11 +144,9 @@ async function renderComponent(
|
||||||
resourceType: ResourceControlType = ResourceControlType.Container,
|
resourceType: ResourceControlType = ResourceControlType.Container,
|
||||||
resourceControl?: ResourceControlViewModel
|
resourceControl?: ResourceControlViewModel
|
||||||
) {
|
) {
|
||||||
|
const WithUser = withUserProvider(AccessControlPanelDetails);
|
||||||
const queries = renderWithQueryClient(
|
const queries = renderWithQueryClient(
|
||||||
<AccessControlPanelDetails
|
<WithUser resourceControl={resourceControl} resourceType={resourceType} />
|
||||||
resourceControl={resourceControl}
|
|
||||||
resourceType={resourceType}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
await expect(queries.findByText('Ownership')).resolves.toBeVisible();
|
await expect(queries.findByText('Ownership')).resolves.toBeVisible();
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { UserId } from '@/portainer/users/types';
|
||||||
import { TeamId } from '@/react/portainer/users/teams/types';
|
import { TeamId } from '@/react/portainer/users/teams/types';
|
||||||
import { useTeams } from '@/react/portainer/users/teams/queries';
|
import { useTeams } from '@/react/portainer/users/teams/queries';
|
||||||
import { useUsers } from '@/portainer/users/queries';
|
import { useUsers } from '@/portainer/users/queries';
|
||||||
|
import { useCurrentUser } from '@/react/hooks/useUser';
|
||||||
|
import { pluralize } from '@/portainer/helpers/strings';
|
||||||
|
|
||||||
import { Link } from '@@/Link';
|
import { Link } from '@@/Link';
|
||||||
import { Tooltip } from '@@/Tip/Tooltip';
|
import { Tooltip } from '@@/Tip/Tooltip';
|
||||||
|
@ -29,6 +31,8 @@ export function AccessControlPanelDetails({
|
||||||
resourceControl,
|
resourceControl,
|
||||||
resourceType,
|
resourceType,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const { isAdmin } = useCurrentUser();
|
||||||
|
|
||||||
const inheritanceMessage = getInheritanceMessage(
|
const inheritanceMessage = getInheritanceMessage(
|
||||||
resourceType,
|
resourceType,
|
||||||
resourceControl
|
resourceControl
|
||||||
|
@ -40,9 +44,31 @@ export function AccessControlPanelDetails({
|
||||||
TeamAccesses: restrictedToTeams = [],
|
TeamAccesses: restrictedToTeams = [],
|
||||||
} = resourceControl || {};
|
} = resourceControl || {};
|
||||||
|
|
||||||
const users = useAuthorizedUsers(restrictedToUsers.map((ra) => ra.UserId));
|
const users = useAuthorizedUsers(
|
||||||
|
restrictedToUsers.map((ra) => ra.UserId),
|
||||||
|
isAdmin
|
||||||
|
);
|
||||||
const teams = useAuthorizedTeams(restrictedToTeams.map((ra) => ra.TeamId));
|
const teams = useAuthorizedTeams(restrictedToTeams.map((ra) => ra.TeamId));
|
||||||
|
|
||||||
|
const teamsLength = teams.data ? teams.data.length : 0;
|
||||||
|
const unauthoisedTeams = restrictedToTeams.length - teamsLength;
|
||||||
|
|
||||||
|
let teamsMessage = teams.data && teams.data.join(', ');
|
||||||
|
if (unauthoisedTeams > 0 && teams.isFetched) {
|
||||||
|
teamsMessage += teamsLength > 0 ? ' and' : '';
|
||||||
|
teamsMessage += ` ${unauthoisedTeams} ${pluralize(
|
||||||
|
unauthoisedTeams,
|
||||||
|
'team'
|
||||||
|
)} you are not part of`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userMessage = isAdmin
|
||||||
|
? (users.data && users.data.join(', ')) || ''
|
||||||
|
: `${restrictedToUsers.length} ${pluralize(
|
||||||
|
restrictedToUsers.length,
|
||||||
|
'user'
|
||||||
|
)}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -62,17 +88,13 @@ export function AccessControlPanelDetails({
|
||||||
{restrictedToUsers.length > 0 && (
|
{restrictedToUsers.length > 0 && (
|
||||||
<tr data-cy="access-authorisedUsers">
|
<tr data-cy="access-authorisedUsers">
|
||||||
<td>Authorized users</td>
|
<td>Authorized users</td>
|
||||||
<td aria-label="authorized-users">
|
<td aria-label="authorized-users">{userMessage}</td>
|
||||||
{users.data && users.data.join(', ')}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
{restrictedToTeams.length > 0 && (
|
{restrictedToTeams.length > 0 && (
|
||||||
<tr data-cy="access-authorisedTeams">
|
<tr data-cy="access-authorisedTeams">
|
||||||
<td>Authorized teams</td>
|
<td>Authorized teams</td>
|
||||||
<td aria-label="authorized-teams">
|
<td aria-label="authorized-teams">{teamsMessage}</td>
|
||||||
{teams.data && teams.data.join(', ')}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -197,8 +219,12 @@ function useAuthorizedTeams(authorizedTeamIds: TeamId[]) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function useAuthorizedUsers(authorizedUserIds: UserId[]) {
|
function useAuthorizedUsers(authorizedUserIds: UserId[], enabled = true) {
|
||||||
return useUsers(false, 0, authorizedUserIds.length > 0, (users) => {
|
return useUsers(
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
authorizedUserIds.length > 0 && enabled,
|
||||||
|
(users) => {
|
||||||
if (authorizedUserIds.length === 0) {
|
if (authorizedUserIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -209,5 +235,6 @@ function useAuthorizedUsers(authorizedUserIds: UserId[]) {
|
||||||
return user?.Username;
|
return user?.Username;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,8 +32,7 @@ export function EditDetails({
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { user, isAdmin } = useUser();
|
const { user, isAdmin } = useUser();
|
||||||
|
|
||||||
const { users, teams, isLoading } = useLoadState(environmentId);
|
const { users, teams, isLoading } = useLoadState(environmentId, isAdmin);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(partialValues: Partial<typeof values>) => {
|
(partialValues: Partial<typeof values>) => {
|
||||||
onChange({ ...values, ...partialValues });
|
onChange({ ...values, ...partialValues });
|
||||||
|
@ -42,7 +41,7 @@ export function EditDetails({
|
||||||
[values, onChange]
|
[values, onChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading || !teams || !users) {
|
if (isLoading || !teams || (isAdmin && !users) || !values.authorizedUsers) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +61,7 @@ export function EditDetails({
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<UsersField
|
<UsersField
|
||||||
name={withNamespace('authorizedUsers')}
|
name={withNamespace('authorizedUsers')}
|
||||||
users={users}
|
users={users || []}
|
||||||
onChange={(authorizedUsers) => handleChange({ authorizedUsers })}
|
onChange={(authorizedUsers) => handleChange({ authorizedUsers })}
|
||||||
value={values.authorizedUsers}
|
value={values.authorizedUsers}
|
||||||
errors={errors?.authorizedUsers}
|
errors={errors?.authorizedUsers}
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { useTeams } from '@/react/portainer/users/teams/queries';
|
||||||
import { useUsers } from '@/portainer/users/queries';
|
import { useUsers } from '@/portainer/users/queries';
|
||||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||||
|
|
||||||
export function useLoadState(environmentId?: EnvironmentId) {
|
export function useLoadState(environmentId?: EnvironmentId, enabled = true) {
|
||||||
const teams = useTeams(false, environmentId);
|
const teams = useTeams(false, environmentId);
|
||||||
|
|
||||||
const users = useUsers(false, environmentId);
|
const users = useUsers(false, environmentId, enabled);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
teams: teams.data,
|
teams: teams.data,
|
||||||
|
|
Loading…
Reference in New Issue