1
0
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:
Nuno Góis 2024-04-17 08:27:56 +01:00 committed by GitHub
parent 8b25ebf792
commit bc0704581b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 91 additions and 38 deletions

View File

@ -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>
); );

View File

@ -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>

View File

@ -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>

View File

@ -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}

View File

@ -0,0 +1,2 @@
export const scimGroupTooltip =
'This group is managed by your SCIM provider and cannot be changed manually';

View File

@ -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>

View File

@ -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,