mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
chore: UI SCIM guard for groups (#6866)
https://linear.app/unleash/issue/2-2113/ui-should-not-allow-manual-management-of-scim-managed-groups-in Adds a UI SCIM guard when trying to manage groups. The condition for the guard is: - Enterprise - SCIM flag enabled - SCIM setting enabled - SCIM group Similar to https://github.com/Unleash/unleash/pull/6859
This commit is contained in:
parent
8b25ebf792
commit
bc0704581b
@ -6,7 +6,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import { useGroupApi } from 'hooks/api/actions/useGroupApi/useGroupApi';
|
import { useGroupApi } from 'hooks/api/actions/useGroupApi/useGroupApi';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import { Button } from '@mui/material';
|
import { Button, Tooltip } from '@mui/material';
|
||||||
import { EDIT } from 'constants/misc';
|
import { EDIT } from 'constants/misc';
|
||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { useGroup } from 'hooks/api/getters/useGroup/useGroup';
|
import { useGroup } from 'hooks/api/getters/useGroup/useGroup';
|
||||||
@ -14,6 +14,8 @@ import { UG_SAVE_BTN_ID } from 'utils/testIds';
|
|||||||
import { GO_BACK } from 'constants/navigate';
|
import { GO_BACK } from 'constants/navigate';
|
||||||
import { useGroups } from 'hooks/api/getters/useGroups/useGroups';
|
import { useGroups } from 'hooks/api/getters/useGroups/useGroups';
|
||||||
import type { IGroup } from 'interfaces/group';
|
import type { IGroup } from 'interfaces/group';
|
||||||
|
import { scimGroupTooltip } from '../group-constants';
|
||||||
|
import { useScimSettings } from 'hooks/api/getters/useScimSettings/useScimSettings';
|
||||||
|
|
||||||
export const EditGroupContainer = () => {
|
export const EditGroupContainer = () => {
|
||||||
const groupId = Number(useRequiredPathParam('groupId'));
|
const groupId = Number(useRequiredPathParam('groupId'));
|
||||||
@ -46,6 +48,11 @@ export const EditGroup = ({
|
|||||||
const { uiConfig } = useUiConfig();
|
const { uiConfig } = useUiConfig();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings: { enabled: scimEnabled },
|
||||||
|
} = useScimSettings();
|
||||||
|
const isScimGroup = scimEnabled && Boolean(group?.scimId);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
setName,
|
setName,
|
||||||
@ -143,15 +150,19 @@ export const EditGroup = ({
|
|||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
mode={EDIT}
|
mode={EDIT}
|
||||||
>
|
>
|
||||||
<Button
|
<Tooltip title={isScimGroup ? scimGroupTooltip : ''} arrow>
|
||||||
type='submit'
|
<div>
|
||||||
variant='contained'
|
<Button
|
||||||
color='primary'
|
type='submit'
|
||||||
disabled={!isValid}
|
variant='contained'
|
||||||
data-testid={UG_SAVE_BTN_ID}
|
color='primary'
|
||||||
>
|
disabled={isScimGroup || !isValid}
|
||||||
Save
|
data-testid={UG_SAVE_BTN_ID}
|
||||||
</Button>
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
</GroupForm>
|
</GroupForm>
|
||||||
</FormTemplate>
|
</FormTemplate>
|
||||||
);
|
);
|
||||||
|
@ -41,6 +41,8 @@ import {
|
|||||||
UG_EDIT_USERS_BTN_ID,
|
UG_EDIT_USERS_BTN_ID,
|
||||||
UG_REMOVE_USER_BTN_ID,
|
UG_REMOVE_USER_BTN_ID,
|
||||||
} from 'utils/testIds';
|
} from 'utils/testIds';
|
||||||
|
import { useScimSettings } from 'hooks/api/getters/useScimSettings/useScimSettings';
|
||||||
|
import { scimGroupTooltip } from '../group-constants';
|
||||||
|
|
||||||
export const groupUsersPlaceholder: IGroupUser[] = Array(15).fill({
|
export const groupUsersPlaceholder: IGroupUser[] = Array(15).fill({
|
||||||
name: 'Name of the user',
|
name: 'Name of the user',
|
||||||
@ -68,6 +70,11 @@ export const Group: VFC = () => {
|
|||||||
const [removeUserOpen, setRemoveUserOpen] = useState(false);
|
const [removeUserOpen, setRemoveUserOpen] = useState(false);
|
||||||
const [selectedUser, setSelectedUser] = useState<IGroupUser>();
|
const [selectedUser, setSelectedUser] = useState<IGroupUser>();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings: { enabled: scimEnabled },
|
||||||
|
} = useScimSettings();
|
||||||
|
const isScimGroup = scimEnabled && Boolean(group?.scimId);
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@ -127,7 +134,11 @@ export const Group: VFC = () => {
|
|||||||
Cell: ({ row: { original: rowUser } }: any) => (
|
Cell: ({ row: { original: rowUser } }: any) => (
|
||||||
<ActionCell>
|
<ActionCell>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title='Remove user from group'
|
title={
|
||||||
|
isScimGroup
|
||||||
|
? scimGroupTooltip
|
||||||
|
: 'Remove user from group'
|
||||||
|
}
|
||||||
arrow
|
arrow
|
||||||
describeChild
|
describeChild
|
||||||
>
|
>
|
||||||
@ -138,6 +149,7 @@ export const Group: VFC = () => {
|
|||||||
setSelectedUser(rowUser);
|
setSelectedUser(rowUser);
|
||||||
setRemoveUserOpen(true);
|
setRemoveUserOpen(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={isScimGroup}
|
||||||
>
|
>
|
||||||
<Delete />
|
<Delete />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -245,8 +257,11 @@ export const Group: VFC = () => {
|
|||||||
data-loading
|
data-loading
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: 'Edit group',
|
title: isScimGroup
|
||||||
|
? scimGroupTooltip
|
||||||
|
: 'Edit group',
|
||||||
}}
|
}}
|
||||||
|
disabled={isScimGroup}
|
||||||
>
|
>
|
||||||
<Edit />
|
<Edit />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
@ -256,8 +271,11 @@ export const Group: VFC = () => {
|
|||||||
onClick={() => setRemoveOpen(true)}
|
onClick={() => setRemoveOpen(true)}
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: 'Delete group',
|
title: isScimGroup
|
||||||
|
? scimGroupTooltip
|
||||||
|
: 'Delete group',
|
||||||
}}
|
}}
|
||||||
|
disabled={isScimGroup}
|
||||||
>
|
>
|
||||||
<Delete />
|
<Delete />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
@ -304,6 +322,12 @@ export const Group: VFC = () => {
|
|||||||
maxWidth='700px'
|
maxWidth='700px'
|
||||||
Icon={Add}
|
Icon={Add}
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
|
disabled={isScimGroup}
|
||||||
|
tooltipProps={{
|
||||||
|
title: isScimGroup
|
||||||
|
? scimGroupTooltip
|
||||||
|
: '',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Edit users
|
Edit users
|
||||||
</ResponsiveButton>
|
</ResponsiveButton>
|
||||||
|
@ -7,6 +7,7 @@ import { Badge } from 'component/common/Badge/Badge';
|
|||||||
import { GroupCardActions } from './GroupCardActions/GroupCardActions';
|
import { GroupCardActions } from './GroupCardActions/GroupCardActions';
|
||||||
import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined';
|
import TopicOutlinedIcon from '@mui/icons-material/TopicOutlined';
|
||||||
import { RoleBadge } from 'component/common/RoleBadge/RoleBadge';
|
import { RoleBadge } from 'component/common/RoleBadge/RoleBadge';
|
||||||
|
import { useScimSettings } from 'hooks/api/getters/useScimSettings/useScimSettings';
|
||||||
|
|
||||||
const StyledLink = styled(Link)(({ theme }) => ({
|
const StyledLink = styled(Link)(({ theme }) => ({
|
||||||
textDecoration: 'none',
|
textDecoration: 'none',
|
||||||
@ -96,6 +97,12 @@ export const GroupCard = ({
|
|||||||
onRemoveGroup,
|
onRemoveGroup,
|
||||||
}: IGroupCardProps) => {
|
}: IGroupCardProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const {
|
||||||
|
settings: { enabled: scimEnabled },
|
||||||
|
} = useScimSettings();
|
||||||
|
const isScimGroup = scimEnabled && Boolean(group.scimId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledLink key={group.id} to={`/admin/groups/${group.id}`}>
|
<StyledLink key={group.id} to={`/admin/groups/${group.id}`}>
|
||||||
@ -107,6 +114,7 @@ export const GroupCard = ({
|
|||||||
groupId={group.id}
|
groupId={group.id}
|
||||||
onEditUsers={() => onEditUsers(group)}
|
onEditUsers={() => onEditUsers(group)}
|
||||||
onRemove={() => onRemoveGroup(group)}
|
onRemove={() => onRemoveGroup(group)}
|
||||||
|
isScimGroup={isScimGroup}
|
||||||
/>
|
/>
|
||||||
</StyledHeaderActions>
|
</StyledHeaderActions>
|
||||||
</StyledTitleRow>
|
</StyledTitleRow>
|
||||||
|
@ -15,6 +15,7 @@ import Edit from '@mui/icons-material/Edit';
|
|||||||
import GroupRounded from '@mui/icons-material/GroupRounded';
|
import GroupRounded from '@mui/icons-material/GroupRounded';
|
||||||
import MoreVert from '@mui/icons-material/MoreVert';
|
import MoreVert from '@mui/icons-material/MoreVert';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { scimGroupTooltip } from 'component/admin/groups/group-constants';
|
||||||
|
|
||||||
const StyledActions = styled('div')(({ theme }) => ({
|
const StyledActions = styled('div')(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -31,12 +32,14 @@ interface IGroupCardActions {
|
|||||||
groupId: number;
|
groupId: number;
|
||||||
onEditUsers: () => void;
|
onEditUsers: () => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
|
isScimGroup?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GroupCardActions: FC<IGroupCardActions> = ({
|
export const GroupCardActions: FC<IGroupCardActions> = ({
|
||||||
groupId,
|
groupId,
|
||||||
onEditUsers,
|
onEditUsers,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
isScimGroup,
|
||||||
}) => {
|
}) => {
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
@ -58,17 +61,24 @@ export const GroupCardActions: FC<IGroupCardActions> = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title='Group actions' arrow describeChild>
|
<Tooltip
|
||||||
<IconButton
|
title={isScimGroup ? scimGroupTooltip : 'Group actions'}
|
||||||
id={id}
|
arrow
|
||||||
aria-controls={open ? menuId : undefined}
|
describeChild
|
||||||
aria-haspopup='true'
|
>
|
||||||
aria-expanded={open ? 'true' : undefined}
|
<div>
|
||||||
onClick={handleClick}
|
<IconButton
|
||||||
type='button'
|
id={id}
|
||||||
>
|
aria-controls={open ? menuId : undefined}
|
||||||
<MoreVert />
|
aria-haspopup='true'
|
||||||
</IconButton>
|
aria-expanded={open ? 'true' : undefined}
|
||||||
|
onClick={handleClick}
|
||||||
|
type='button'
|
||||||
|
disabled={isScimGroup}
|
||||||
|
>
|
||||||
|
<MoreVert />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<StyledPopover
|
<StyledPopover
|
||||||
id={menuId}
|
id={menuId}
|
||||||
|
2
frontend/src/component/admin/groups/group-constants.ts
Normal file
2
frontend/src/component/admin/groups/group-constants.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const scimGroupTooltip =
|
||||||
|
'This group is managed by your SCIM provider and cannot be changed manually';
|
@ -20,7 +20,7 @@ interface IUsersActionsCellProps {
|
|||||||
onChangePassword: (event: React.SyntheticEvent) => void;
|
onChangePassword: (event: React.SyntheticEvent) => void;
|
||||||
onResetPassword: (event: React.SyntheticEvent) => void;
|
onResetPassword: (event: React.SyntheticEvent) => void;
|
||||||
onDelete: (event: React.SyntheticEvent) => void;
|
onDelete: (event: React.SyntheticEvent) => void;
|
||||||
scimEnabled?: boolean;
|
isScimUser?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UsersActionsCell: VFC<IUsersActionsCellProps> = ({
|
export const UsersActionsCell: VFC<IUsersActionsCellProps> = ({
|
||||||
@ -29,7 +29,7 @@ export const UsersActionsCell: VFC<IUsersActionsCellProps> = ({
|
|||||||
onChangePassword,
|
onChangePassword,
|
||||||
onResetPassword,
|
onResetPassword,
|
||||||
onDelete,
|
onDelete,
|
||||||
scimEnabled,
|
isScimUser,
|
||||||
}) => {
|
}) => {
|
||||||
const scimTooltip =
|
const scimTooltip =
|
||||||
'This user is managed by your SCIM provider and cannot be changed manually';
|
'This user is managed by your SCIM provider and cannot be changed manually';
|
||||||
@ -41,9 +41,9 @@ export const UsersActionsCell: VFC<IUsersActionsCellProps> = ({
|
|||||||
onClick={onEdit}
|
onClick={onEdit}
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: scimEnabled ? scimTooltip : 'Edit user',
|
title: isScimUser ? scimTooltip : 'Edit user',
|
||||||
}}
|
}}
|
||||||
disabled={scimEnabled}
|
disabled={isScimUser}
|
||||||
>
|
>
|
||||||
<Edit />
|
<Edit />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
@ -69,9 +69,9 @@ export const UsersActionsCell: VFC<IUsersActionsCellProps> = ({
|
|||||||
onClick={onChangePassword}
|
onClick={onChangePassword}
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: scimEnabled ? scimTooltip : 'Change password',
|
title: isScimUser ? scimTooltip : 'Change password',
|
||||||
}}
|
}}
|
||||||
disabled={scimEnabled}
|
disabled={isScimUser}
|
||||||
>
|
>
|
||||||
<Lock />
|
<Lock />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
@ -80,9 +80,9 @@ export const UsersActionsCell: VFC<IUsersActionsCellProps> = ({
|
|||||||
onClick={onResetPassword}
|
onClick={onResetPassword}
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: scimEnabled ? scimTooltip : 'Reset password',
|
title: isScimUser ? scimTooltip : 'Reset password',
|
||||||
}}
|
}}
|
||||||
disabled={scimEnabled}
|
disabled={isScimUser}
|
||||||
>
|
>
|
||||||
<LockReset />
|
<LockReset />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
@ -91,9 +91,9 @@ export const UsersActionsCell: VFC<IUsersActionsCellProps> = ({
|
|||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
permission={ADMIN}
|
permission={ADMIN}
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
title: scimEnabled ? scimTooltip : 'Remove user',
|
title: isScimUser ? scimTooltip : 'Remove user',
|
||||||
}}
|
}}
|
||||||
disabled={scimEnabled}
|
disabled={isScimUser}
|
||||||
>
|
>
|
||||||
<Delete />
|
<Delete />
|
||||||
</PermissionIconButton>
|
</PermissionIconButton>
|
||||||
|
@ -58,10 +58,8 @@ const UsersList = () => {
|
|||||||
});
|
});
|
||||||
const userAccessUIEnabled = useUiFlag('userAccessUIEnabled');
|
const userAccessUIEnabled = useUiFlag('userAccessUIEnabled');
|
||||||
const {
|
const {
|
||||||
settings: { enabled: scimSettingEnabled },
|
settings: { enabled: scimEnabled },
|
||||||
} = useScimSettings();
|
} = useScimSettings();
|
||||||
const scimFlagEnabled = useUiFlag('scimApi');
|
|
||||||
const scimEnabled = isEnterprise() && scimSettingEnabled && scimFlagEnabled;
|
|
||||||
const [delDialog, setDelDialog] = useState(false);
|
const [delDialog, setDelDialog] = useState(false);
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
const [emailSent, setEmailSent] = useState(false);
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
@ -218,7 +216,7 @@ const UsersList = () => {
|
|||||||
onChangePassword={openPwDialog(user)}
|
onChangePassword={openPwDialog(user)}
|
||||||
onResetPassword={openResetPwDialog(user)}
|
onResetPassword={openResetPwDialog(user)}
|
||||||
onDelete={openDelDialog(user)}
|
onDelete={openDelDialog(user)}
|
||||||
scimEnabled={scimEnabled && Boolean(user.scimId)}
|
isScimUser={scimEnabled && Boolean(user.scimId)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
width: userAccessUIEnabled ? 240 : 200,
|
width: userAccessUIEnabled ? 240 : 200,
|
||||||
|
Loading…
Reference in New Issue
Block a user