mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Explain why org role selection is disabled for externally synced users (#72274)
* Move builtin role selector to separate component * Show message if basic roles picker disabled * Show explanation in OSS
This commit is contained in:
parent
783a8527c5
commit
d6e43a44bd
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { Icon, RadioButtonGroup, Tooltip, useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
|
import { OrgRole } from 'app/types';
|
||||||
|
|
||||||
|
import { getStyles } from './styles';
|
||||||
|
|
||||||
|
const BasicRoles = Object.values(OrgRole).filter((r) => r !== OrgRole.None);
|
||||||
|
const BasicRoleOption: Array<SelectableValue<OrgRole>> = BasicRoles.map((r) => ({
|
||||||
|
label: r,
|
||||||
|
value: r,
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
value?: OrgRole;
|
||||||
|
onChange: (value: OrgRole) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
disabledMesssage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BuiltinRoleSelector = ({ value, onChange, disabled, disabledMesssage }: Props) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
const theme = useTheme2();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.groupHeader}>
|
||||||
|
<span style={{ marginRight: theme.spacing(1) }}>Basic roles</span>
|
||||||
|
{disabled && disabledMesssage && (
|
||||||
|
<Tooltip placement="right-end" interactive={true} content={<div>{disabledMesssage}</div>}>
|
||||||
|
<Icon name="question-circle" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<RadioButtonGroup
|
||||||
|
className={styles.basicRoleSelector}
|
||||||
|
options={BasicRoleOption}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
fullWidth={true}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -14,6 +14,7 @@ export interface Props {
|
|||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
basicRoleDisabled?: boolean;
|
basicRoleDisabled?: boolean;
|
||||||
|
basicRoleDisabledMessage?: string;
|
||||||
showBasicRole?: boolean;
|
showBasicRole?: boolean;
|
||||||
onRolesChange: (newRoles: Role[]) => void;
|
onRolesChange: (newRoles: Role[]) => void;
|
||||||
onBasicRoleChange?: (newRole: OrgRole) => void;
|
onBasicRoleChange?: (newRole: OrgRole) => void;
|
||||||
@ -32,6 +33,7 @@ export const RolePicker = ({
|
|||||||
disabled,
|
disabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
basicRoleDisabled,
|
basicRoleDisabled,
|
||||||
|
basicRoleDisabledMessage,
|
||||||
showBasicRole,
|
showBasicRole,
|
||||||
onRolesChange,
|
onRolesChange,
|
||||||
onBasicRoleChange,
|
onBasicRoleChange,
|
||||||
@ -184,6 +186,7 @@ export const RolePicker = ({
|
|||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
showGroups={query.length === 0 || query.trim() === ''}
|
showGroups={query.length === 0 || query.trim() === ''}
|
||||||
basicRoleDisabled={basicRoleDisabled}
|
basicRoleDisabled={basicRoleDisabled}
|
||||||
|
disabledMessage={basicRoleDisabledMessage}
|
||||||
showBasicRole={showBasicRole}
|
showBasicRole={showBasicRole}
|
||||||
updateDisabled={basicRoleDisabled && !canUpdateRoles}
|
updateDisabled={basicRoleDisabled && !canUpdateRoles}
|
||||||
apply={apply}
|
apply={apply}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { Button, CustomScrollbar, HorizontalGroup, useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
import { Button, CustomScrollbar, HorizontalGroup, RadioButtonGroup, useStyles2, useTheme2 } from '@grafana/ui';
|
|
||||||
import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles';
|
import { getSelectStyles } from '@grafana/ui/src/components/Select/getSelectStyles';
|
||||||
import { OrgRole, Role } from 'app/types';
|
import { OrgRole, Role } from 'app/types';
|
||||||
|
|
||||||
|
import { BuiltinRoleSelector } from './BuiltinRoleSelector';
|
||||||
import { RoleMenuGroupsSection } from './RoleMenuGroupsSection';
|
import { RoleMenuGroupsSection } from './RoleMenuGroupsSection';
|
||||||
import { MENU_MAX_HEIGHT } from './constants';
|
import { MENU_MAX_HEIGHT } from './constants';
|
||||||
import { getStyles } from './styles';
|
import { getStyles } from './styles';
|
||||||
@ -29,12 +29,6 @@ interface RolesCollectionEntry {
|
|||||||
roles: Role[];
|
roles: Role[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicRoles = Object.values(OrgRole).filter((r) => r !== OrgRole.None);
|
|
||||||
const BasicRoleOption: Array<SelectableValue<OrgRole>> = BasicRoles.map((r) => ({
|
|
||||||
label: r,
|
|
||||||
value: r,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const fixedRoleGroupNames: Record<string, string> = {
|
const fixedRoleGroupNames: Record<string, string> = {
|
||||||
ldap: 'LDAP',
|
ldap: 'LDAP',
|
||||||
current: 'Current org',
|
current: 'Current org',
|
||||||
@ -46,6 +40,7 @@ interface RolePickerMenuProps {
|
|||||||
appliedRoles: Role[];
|
appliedRoles: Role[];
|
||||||
showGroups?: boolean;
|
showGroups?: boolean;
|
||||||
basicRoleDisabled?: boolean;
|
basicRoleDisabled?: boolean;
|
||||||
|
disabledMessage?: string;
|
||||||
showBasicRole?: boolean;
|
showBasicRole?: boolean;
|
||||||
onSelect: (roles: Role[]) => void;
|
onSelect: (roles: Role[]) => void;
|
||||||
onBasicRoleSelect?: (role: OrgRole) => void;
|
onBasicRoleSelect?: (role: OrgRole) => void;
|
||||||
@ -61,6 +56,7 @@ export const RolePickerMenu = ({
|
|||||||
appliedRoles,
|
appliedRoles,
|
||||||
showGroups,
|
showGroups,
|
||||||
basicRoleDisabled,
|
basicRoleDisabled,
|
||||||
|
disabledMessage,
|
||||||
showBasicRole,
|
showBasicRole,
|
||||||
onSelect,
|
onSelect,
|
||||||
onBasicRoleSelect,
|
onBasicRoleSelect,
|
||||||
@ -206,14 +202,11 @@ export const RolePickerMenu = ({
|
|||||||
<CustomScrollbar autoHide={false} autoHeightMax={`${MENU_MAX_HEIGHT}px`} hideHorizontalTrack hideVerticalTrack>
|
<CustomScrollbar autoHide={false} autoHeightMax={`${MENU_MAX_HEIGHT}px`} hideHorizontalTrack hideVerticalTrack>
|
||||||
{showBasicRole && (
|
{showBasicRole && (
|
||||||
<div className={customStyles.menuSection}>
|
<div className={customStyles.menuSection}>
|
||||||
<div className={customStyles.groupHeader}>Basic roles</div>
|
<BuiltinRoleSelector
|
||||||
<RadioButtonGroup
|
|
||||||
className={customStyles.basicRoleSelector}
|
|
||||||
options={BasicRoleOption}
|
|
||||||
value={selectedBuiltInRole}
|
value={selectedBuiltInRole}
|
||||||
onChange={onSelectedBuiltinRoleChange}
|
onChange={onSelectedBuiltinRoleChange}
|
||||||
fullWidth={true}
|
|
||||||
disabled={basicRoleDisabled}
|
disabled={basicRoleDisabled}
|
||||||
|
disabledMesssage={disabledMessage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -15,6 +15,7 @@ export interface Props {
|
|||||||
roleOptions: Role[];
|
roleOptions: Role[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
basicRoleDisabled?: boolean;
|
basicRoleDisabled?: boolean;
|
||||||
|
basicRoleDisabledMessage?: string;
|
||||||
/**
|
/**
|
||||||
* Set whether the component should send a request with the new roles to the
|
* Set whether the component should send a request with the new roles to the
|
||||||
* backend in UserRolePicker.onRolesChange (apply=false), or call {@link onApplyRoles}
|
* backend in UserRolePicker.onRolesChange (apply=false), or call {@link onApplyRoles}
|
||||||
@ -40,6 +41,7 @@ export const UserRolePicker = ({
|
|||||||
roleOptions,
|
roleOptions,
|
||||||
disabled,
|
disabled,
|
||||||
basicRoleDisabled,
|
basicRoleDisabled,
|
||||||
|
basicRoleDisabledMessage,
|
||||||
apply = false,
|
apply = false,
|
||||||
onApplyRoles,
|
onApplyRoles,
|
||||||
pendingRoles,
|
pendingRoles,
|
||||||
@ -91,6 +93,7 @@ export const UserRolePicker = ({
|
|||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
basicRoleDisabled={basicRoleDisabled}
|
basicRoleDisabled={basicRoleDisabled}
|
||||||
|
basicRoleDisabledMessage={basicRoleDisabledMessage}
|
||||||
showBasicRole
|
showBasicRole
|
||||||
apply={apply}
|
apply={apply}
|
||||||
canUpdateRoles={canUpdateRoles}
|
canUpdateRoles={canUpdateRoles}
|
||||||
|
@ -209,6 +209,8 @@ class UnThemedOrgRow extends PureComponent<OrgRowProps> {
|
|||||||
roleOptions={this.state.roleOptions}
|
roleOptions={this.state.roleOptions}
|
||||||
onBasicRoleChange={this.onBasicRoleChange}
|
onBasicRoleChange={this.onBasicRoleChange}
|
||||||
basicRoleDisabled={rolePickerDisabled}
|
basicRoleDisabled={rolePickerDisabled}
|
||||||
|
basicRoleDisabledMessage="This user's role is not editable because it is synchronized from your auth provider.
|
||||||
|
Refer to the Grafana authentication docs for details."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isExternalUser && <ExternalUserTooltip lockMessage={lockMessage} />}
|
{isExternalUser && <ExternalUserTooltip lockMessage={lockMessage} />}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { OrgRole } from '@grafana/data';
|
import { OrgRole } from '@grafana/data';
|
||||||
import { Button, ConfirmModal } from '@grafana/ui';
|
import { Button, ConfirmModal, Icon, Tooltip } from '@grafana/ui';
|
||||||
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
import { UserRolePicker } from 'app/core/components/RolePicker/UserRolePicker';
|
||||||
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
||||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||||
@ -11,6 +11,9 @@ import { AccessControlAction, OrgUser, Role } from 'app/types';
|
|||||||
|
|
||||||
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
import { OrgRolePicker } from '../admin/OrgRolePicker';
|
||||||
|
|
||||||
|
const disabledRoleMessage = `This user's role is not editable because it is synchronized from your auth provider.
|
||||||
|
Refer to the Grafana authentication docs for details.`;
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
users: OrgUser[];
|
users: OrgUser[];
|
||||||
orgId?: number;
|
orgId?: number;
|
||||||
@ -49,6 +52,7 @@ export const UsersTable = ({ users, orgId, onRoleChange, onRemoveUser }: Props)
|
|||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Seen</th>
|
<th>Seen</th>
|
||||||
<th>Role</th>
|
<th>Role</th>
|
||||||
|
<th />
|
||||||
<th style={{ width: '34px' }} />
|
<th style={{ width: '34px' }} />
|
||||||
<th>Origin</th>
|
<th>Origin</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
@ -97,6 +101,7 @@ export const UsersTable = ({ users, orgId, onRoleChange, onRemoveUser }: Props)
|
|||||||
basicRole={user.role}
|
basicRole={user.role}
|
||||||
onBasicRoleChange={(newRole) => onRoleChange(newRole, user)}
|
onBasicRoleChange={(newRole) => onRoleChange(newRole, user)}
|
||||||
basicRoleDisabled={basicRoleDisabled}
|
basicRoleDisabled={basicRoleDisabled}
|
||||||
|
basicRoleDisabledMessage={disabledRoleMessage}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<OrgRolePicker
|
<OrgRolePicker
|
||||||
@ -108,6 +113,16 @@ export const UsersTable = ({ users, orgId, onRoleChange, onRemoveUser }: Props)
|
|||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{basicRoleDisabled && (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Tooltip content={disabledRoleMessage}>
|
||||||
|
<Icon name="question-circle" style={{ marginLeft: '8px' }} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
|
||||||
<td className="width-1 text-center">
|
<td className="width-1 text-center">
|
||||||
{user.isDisabled && <span className="label label-tag label-tag--gray">Disabled</span>}
|
{user.isDisabled && <span className="label label-tag label-tag--gray">Disabled</span>}
|
||||||
</td>
|
</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user