mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
GAS: Show mapped roles in role picker (#96681)
* add group mapping UID returned mapped roles * request mapped roles from the frontend, but don't attempt to update mapped roles * lock mapped roles and show a pop-up message about why a role is locked * update role selectors to not allow deselecting a mapped role * swagger gen * simplify and set mapped as bool instead of mapping UID array * swagger gen
This commit is contained in:
parent
e06ad2a6ef
commit
a60953c8f9
@ -81,6 +81,7 @@ type RoleDTO struct {
|
||||
Group string `xorm:"group_name" json:"group"`
|
||||
Permissions []Permission `json:"permissions,omitempty"`
|
||||
Delegatable *bool `json:"delegatable,omitempty"`
|
||||
Mapped bool `json:"mapped,omitempty"`
|
||||
Hidden bool `json:"hidden,omitempty"`
|
||||
|
||||
ID int64 `json:"-" xorm:"pk autoincr 'id'"`
|
||||
|
@ -7123,6 +7123,9 @@
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mapped": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -19793,6 +19793,9 @@
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mapped": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -78,7 +78,10 @@ export const RoleMenuGroupsSection = forwardRef<HTMLDivElement, RoleMenuGroupsSe
|
||||
value={groupOption.value}
|
||||
isSelected={groupSelected(groupOption.value) || groupPartiallySelected(groupOption.value)}
|
||||
partiallySelected={groupPartiallySelected(groupOption.value)}
|
||||
disabled={groupOption.options?.every(isNotDelegatable)}
|
||||
disabled={groupOption.options?.every(
|
||||
(option) =>
|
||||
isNotDelegatable(option) || selectedOptions.find((opt) => opt.uid === option.uid && opt.mapped)
|
||||
)}
|
||||
onChange={onGroupChange}
|
||||
onOpenSubMenu={onOpenSubMenu}
|
||||
onCloseSubMenu={onCloseSubMenu}
|
||||
@ -102,6 +105,7 @@ export const RoleMenuGroupsSection = forwardRef<HTMLDivElement, RoleMenuGroupsSe
|
||||
key={option.uid}
|
||||
isSelected={!!(option.uid && !!selectedOptions.find((opt) => opt.uid === option.uid))}
|
||||
disabled={isNotDelegatable(option)}
|
||||
mapped={!!(option.uid && selectedOptions.find((opt) => opt.uid === option.uid && opt.mapped))}
|
||||
onChange={onRoleChange}
|
||||
hideDescription
|
||||
/>
|
||||
|
@ -13,14 +13,23 @@ interface RoleMenuOptionProps {
|
||||
isSelected?: boolean;
|
||||
isFocused?: boolean;
|
||||
disabled?: boolean;
|
||||
mapped?: boolean;
|
||||
hideDescription?: boolean;
|
||||
}
|
||||
|
||||
export const RoleMenuOption = forwardRef<HTMLDivElement, React.PropsWithChildren<RoleMenuOptionProps>>(
|
||||
({ data, isFocused, isSelected, disabled, onChange, hideDescription }, ref) => {
|
||||
({ data, isFocused, isSelected, disabled, mapped, onChange, hideDescription }, ref) => {
|
||||
const theme = useTheme2();
|
||||
const styles = getSelectStyles(theme);
|
||||
const customStyles = useStyles2(getStyles);
|
||||
disabled = disabled || mapped;
|
||||
let disabledMessage = '';
|
||||
if (disabled) {
|
||||
disabledMessage = 'You do not have permissions to assign this role.';
|
||||
if (mapped) {
|
||||
disabledMessage = 'Role assignment cannot be removed because the role is mapped through group sync.';
|
||||
}
|
||||
}
|
||||
|
||||
const wrapperClassName = cx(
|
||||
styles.option,
|
||||
@ -51,6 +60,11 @@ export const RoleMenuOption = forwardRef<HTMLDivElement, React.PropsWithChildren
|
||||
<span>{data.displayName || data.name}</span>
|
||||
{!hideDescription && data.description && <div className={styles.optionDescription}>{data.description}</div>}
|
||||
</div>
|
||||
{disabledMessage && (
|
||||
<Tooltip content={disabledMessage}>
|
||||
<Icon name="lock" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{data.description && (
|
||||
<Tooltip content={data.description}>
|
||||
<Icon name="info-circle" className={customStyles.menuOptionInfoSign} />
|
||||
|
@ -157,8 +157,15 @@ export const RolePickerMenu = ({
|
||||
return selectedGroupOptions.length > 0 && selectedGroupOptions.length < groupOptions!.options.length;
|
||||
};
|
||||
|
||||
const changeableGroupRolesSelected = (groupType: GroupType, group: string) => {
|
||||
const selectedGroupOptions = getSelectedGroupOptions(group);
|
||||
const changeableGroupOptions = selectedGroupOptions.filter((role) => role.delegatable && !role.mapped);
|
||||
const groupOptions = rolesCollection[groupType]?.optionGroup.find((g) => g.value === group);
|
||||
return changeableGroupOptions.length > 0 && changeableGroupOptions.length < groupOptions!.options.length;
|
||||
};
|
||||
|
||||
const onChange = (option: Role) => {
|
||||
if (selectedOptions.find((role) => role.uid === option.uid)) {
|
||||
if (selectedOptions.find((role) => role.uid === option.uid && !role.mapped)) {
|
||||
setSelectedOptions(selectedOptions.filter((role) => role.uid !== option.uid));
|
||||
} else {
|
||||
setSelectedOptions([...selectedOptions, option]);
|
||||
@ -174,12 +181,21 @@ export const RolePickerMenu = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (groupSelected(groupType, value) || groupPartiallySelected(groupType, value)) {
|
||||
setSelectedOptions(selectedOptions.filter((role) => !group.options.find((option) => role.uid === option.uid)));
|
||||
} else {
|
||||
const groupOptions = group.options.filter((role) => role.delegatable);
|
||||
if (groupSelected(groupType, value) || changeableGroupRolesSelected(groupType, value)) {
|
||||
const mappedGroupOptions = selectedOptions.filter((option) =>
|
||||
group.options.find((role) => role.uid === option.uid && option.mapped)
|
||||
);
|
||||
const restOptions = selectedOptions.filter((role) => !group.options.find((option) => role.uid === option.uid));
|
||||
setSelectedOptions([...restOptions, ...groupOptions]);
|
||||
setSelectedOptions([...restOptions, ...mappedGroupOptions]);
|
||||
} else {
|
||||
const mappedGroupOptions = selectedOptions.filter((option) =>
|
||||
group.options.find((role) => role.uid === option.uid && role.delegatable)
|
||||
);
|
||||
const groupOptions = group.options.filter(
|
||||
(role) => role.delegatable && !selectedOptions.find((option) => role.uid === option.uid && option.mapped)
|
||||
);
|
||||
const restOptions = selectedOptions.filter((role) => !group.options.find((option) => role.uid === option.uid));
|
||||
setSelectedOptions([...restOptions, ...groupOptions, ...mappedGroupOptions]);
|
||||
}
|
||||
};
|
||||
|
||||
@ -188,13 +204,17 @@ export const RolePickerMenu = ({
|
||||
};
|
||||
|
||||
const onClearInternal = async () => {
|
||||
setSelectedOptions([]);
|
||||
const mappedRoles = selectedOptions.filter((role) => role.mapped);
|
||||
const nonDelegatableRoles = options.filter((role) =>
|
||||
selectedOptions.find((option) => role.uid === option.uid && !role.delegatable)
|
||||
);
|
||||
setSelectedOptions([...mappedRoles, ...nonDelegatableRoles]);
|
||||
};
|
||||
|
||||
const onClearSubMenu = (group: string) => {
|
||||
const options = selectedOptions.filter((role) => {
|
||||
const roleGroup = getRoleGroup(role);
|
||||
return roleGroup !== group;
|
||||
return roleGroup !== group || role.mapped;
|
||||
});
|
||||
setSelectedOptions(options);
|
||||
};
|
||||
|
@ -57,6 +57,7 @@ export const RolePickerSubMenu = ({
|
||||
disabled={
|
||||
!!(option.uid && disabledOptions?.find((opt) => opt.uid === option.uid)) || isNotDelegatable(option)
|
||||
}
|
||||
mapped={!!(option.uid && selectedOptions.find((opt) => opt.uid === option.uid && opt.mapped))}
|
||||
onChange={onSelect}
|
||||
hideDescription
|
||||
/>
|
||||
|
@ -16,9 +16,9 @@ export const fetchRoleOptions = async (orgId?: number): Promise<Role[]> => {
|
||||
};
|
||||
|
||||
export const fetchUserRoles = async (userId: number, orgId?: number): Promise<Role[]> => {
|
||||
let userRolesUrl = `/api/access-control/users/${userId}/roles`;
|
||||
let userRolesUrl = `/api/access-control/users/${userId}/roles?includeMapped=true`;
|
||||
if (orgId) {
|
||||
userRolesUrl += `?targetOrgId=${orgId}`;
|
||||
userRolesUrl += `&targetOrgId=${orgId}`;
|
||||
}
|
||||
try {
|
||||
const roles = await getBackendSrv().get(userRolesUrl);
|
||||
@ -39,7 +39,8 @@ export const updateUserRoles = (roles: Role[], userId: number, orgId?: number) =
|
||||
if (orgId) {
|
||||
userRolesUrl += `?targetOrgId=${orgId}`;
|
||||
}
|
||||
const roleUids = roles.flatMap((x) => x.uid);
|
||||
const filteredRoles = roles.filter((role) => !role.mapped);
|
||||
const roleUids = filteredRoles.flatMap((x) => x.uid);
|
||||
return getBackendSrv().put(userRolesUrl, {
|
||||
orgId,
|
||||
roleUids,
|
||||
|
@ -19,7 +19,10 @@ export const getOrgUsers = async (orgId: UrlQueryValue, page: number) => {
|
||||
|
||||
export const getUsersRoles = async (orgId: number, users: OrgUser[]) => {
|
||||
const userIds = users.map((u) => u.userId);
|
||||
const roles = await getBackendSrv().post(`/api/access-control/users/roles/search`, { userIds, orgId });
|
||||
const roles = await getBackendSrv().post(`/api/access-control/users/roles/search?includeMapped=true`, {
|
||||
userIds,
|
||||
orgId,
|
||||
});
|
||||
users.forEach((u) => {
|
||||
u.roles = roles ? roles[u.userId] || [] : [];
|
||||
});
|
||||
|
@ -36,7 +36,10 @@ export function loadUsers(): ThunkResult<void> {
|
||||
dispatch(rolesFetchBegin());
|
||||
const orgId = contextSrv.user.orgId;
|
||||
const userIds = users?.orgUsers.map((u: OrgUser) => u.userId);
|
||||
const roles = await getBackendSrv().post(`/api/access-control/users/roles/search`, { userIds, orgId });
|
||||
const roles = await getBackendSrv().post(`/api/access-control/users/roles/search?includeMapped=true`, {
|
||||
userIds,
|
||||
orgId,
|
||||
});
|
||||
users.orgUsers.forEach((u: OrgUser) => {
|
||||
u.roles = roles ? roles[u.userId] || [] : [];
|
||||
});
|
||||
|
@ -167,6 +167,7 @@ export interface Role {
|
||||
group: string;
|
||||
global: boolean;
|
||||
delegatable?: boolean;
|
||||
mapped?: boolean;
|
||||
version: number;
|
||||
created: string;
|
||||
updated: string;
|
||||
|
@ -9751,6 +9751,9 @@
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mapped": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user