Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Roles] Added optional role description #183145

Merged
merged 10 commits into from
May 16, 2024
Merged
2 changes: 2 additions & 0 deletions docs/api/role-management/get-all.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The API returns the following:
[
{
"name": "my_kibana_role",
"description": "My kibana role description",
"metadata" : {
"version" : 1
},
Expand All @@ -55,6 +56,7 @@ The API returns the following:
},
{
"name": "my_admin_role",
"description": "My admin role description",
"metadata" : {
"version" : 1
},
Expand Down
1 change: 1 addition & 0 deletions docs/api/role-management/get.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The API returns the following:
--------------------------------------------------
{
"name": "my_restricted_kibana_role",
"description": "My restricted kibana role description",
"metadata" : {
"version" : 1
},
Expand Down
8 changes: 8 additions & 0 deletions docs/api/role-management/put.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ To use the create or update role API, you must have the `manage_security` cluste
[[role-management-api-response-body]]
==== Request body

`description`::
(Optional, string) Description for the role.

`metadata`::
(Optional, object) In the `metadata` object, keys that begin with `_` are reserved for system usage.

Expand Down Expand Up @@ -74,6 +77,7 @@ Grant access to various features in all spaces:
--------------------------------------------------
$ curl -X PUT api/security/role/my_kibana_role
{
"description": "my_kibana_role_description",
elena-shostak marked this conversation as resolved.
Show resolved Hide resolved
"metadata": {
"version": 1
},
Expand Down Expand Up @@ -112,6 +116,7 @@ Grant dashboard-only access to only the Marketing space:
--------------------------------------------------
$ curl -X PUT api/security/role/my_kibana_role
{
"description": "Grants dashboard-only access to only the Marketing space.",
"metadata": {
"version": 1
},
Expand All @@ -138,6 +143,7 @@ Grant full access to all features in the Default space:
--------------------------------------------------
$ curl -X PUT api/security/role/my_kibana_role
{
"description": "Grants full access to all features in the Default space.",
"metadata": {
"version": 1
},
Expand All @@ -162,6 +168,7 @@ Grant different access to different spaces:
--------------------------------------------------
$ curl -X PUT api/security/role/my_kibana_role
{
"description": "Grants full access to discover and dashboard features in the default space. Grants read access in the marketing, and sales spaces.",
"metadata": {
"version": 1
},
Expand Down Expand Up @@ -193,6 +200,7 @@ Grant access to {kib} and {es}:
--------------------------------------------------
$ curl -X PUT api/security/role/my_kibana_role
{
"description": "Grants all cluster privileges and full access to index1 and index2. Grants full access to remote_index1 and remote_index2, and the monitor_enrich cluster privilege on remote_cluster1. Grants all Kibana privileges in the default space.",
"metadata": {
"version": 1
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('RoleComboBox', () => {
},
{
name: 'deprecated_role',
description: 'Deprecated role description',
elasticsearch: { cluster: [], indices: [], run_as: [] },
kibana: [],
metadata: { _reserved: true, _deprecated: true },
Expand Down Expand Up @@ -72,6 +73,7 @@ describe('RoleComboBox', () => {
"label": "custom_role",
"value": Object {
"deprecatedReason": undefined,
"description": undefined,
"isAdmin": false,
"isDeprecated": false,
"isReserved": false,
Expand All @@ -89,6 +91,7 @@ describe('RoleComboBox', () => {
"label": "reserved_role",
"value": Object {
"deprecatedReason": undefined,
"description": undefined,
"isAdmin": false,
"isDeprecated": false,
"isReserved": true,
Expand All @@ -106,6 +109,7 @@ describe('RoleComboBox', () => {
"label": "some_admin",
"value": Object {
"deprecatedReason": undefined,
"description": undefined,
"isAdmin": true,
"isDeprecated": false,
"isReserved": true,
Expand All @@ -123,6 +127,7 @@ describe('RoleComboBox', () => {
"label": "some_system",
"value": Object {
"deprecatedReason": undefined,
"description": undefined,
"isAdmin": false,
"isDeprecated": false,
"isReserved": true,
Expand All @@ -140,6 +145,7 @@ describe('RoleComboBox', () => {
"label": "deprecated_role",
"value": Object {
"deprecatedReason": undefined,
"description": "Deprecated role description",
"isAdmin": false,
"isDeprecated": true,
"isReserved": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
*/

import type { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui';
import { EuiBadge, EuiComboBox, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import {
EuiBadge,
EuiComboBox,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiToolTip,
} from '@elastic/eui';
import React from 'react';

import { i18n } from '@kbn/i18n';
Expand Down Expand Up @@ -34,6 +41,7 @@ type Option = EuiComboBoxOptionOption<{
isSystem: boolean;
isAdmin: boolean;
deprecatedReason?: string;
description?: string;
}>;

export const RoleComboBox = (props: Props) => {
Expand All @@ -57,6 +65,7 @@ export const RoleComboBox = (props: Props) => {
isSystem,
isAdmin,
deprecatedReason: roleDefinition?.metadata?._deprecated_reason,
description: roleDefinition?.description,
},
};
};
Expand Down Expand Up @@ -134,7 +143,15 @@ export const RoleComboBox = (props: Props) => {
function renderOption(option: Option) {
return (
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center" responsive={false}>
<EuiFlexItem>{option.label}</EuiFlexItem>
<EuiFlexItem>
{option.value?.description ? (
<EuiToolTip position="left" content={option.value?.description}>
elena-shostak marked this conversation as resolved.
Show resolved Hide resolved
<EuiText size="s">{option.label}</EuiText>
</EuiToolTip>
) : (
<EuiText size="s">{option.label}</EuiText>
)}
</EuiFlexItem>
{option.value?.isDeprecated ? (
<EuiFlexItem grow={false}>
<EuiBadge color={option.color}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,69 @@ describe('<EditRolePage />', () => {
expectSaveFormButtons(wrapper);
});

it('can render a user defined role with description', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
description: 'my custom role description',
name: 'my custom role',
metadata: {},
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [],
},
})}
/>
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find('input[data-test-subj="roleFormDescriptionInput"]').prop('value')).toBe(
'my custom role description'
);
expect(
wrapper.find('input[data-test-subj="roleFormDescriptionInput"]').prop('disabled')
).toBe(undefined);
expectSaveFormButtons(wrapper);
});

it('can render a reserved role with description', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
description: 'my reserved role description',
name: 'my custom role',
metadata: {
_reserved: true,
},
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [],
},
})}
/>
</KibanaContextProvider>
);

await waitForRender(wrapper);

expect(wrapper.find('[data-test-subj="roleFormDescriptionTooltip"]')).toHaveLength(1);

expect(wrapper.find('input[data-test-subj="roleFormDescriptionInput"]').prop('value')).toBe(
'my reserved role description'
);
expect(
wrapper.find('input[data-test-subj="roleFormDescriptionInput"]').prop('disabled')
).toBe(true);
});

it('can render when creating a new role', async () => {
const wrapper = mountWithIntl(
<KibanaContextProvider services={coreStart}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
EuiSpacer,
EuiText,
EuiTitle,
EuiToolTip,
} from '@elastic/eui';
import type { ChangeEvent, FocusEvent, FunctionComponent, HTMLProps } from 'react';
import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react';
Expand Down Expand Up @@ -211,6 +212,7 @@ function useRole(
? rolesAPIClient.getRole(roleName)
: Promise.resolve({
name: '',
description: '',
elasticsearch: { cluster: [], indices: [], run_as: [], remote_cluster: [] },
kibana: [],
_unrecognized_applications: [],
Expand Down Expand Up @@ -452,45 +454,82 @@ export const EditRolePage: FunctionComponent<Props> = ({
return null;
};

const getRoleName = () => {
const getRoleNameAndDescription = () => {
return (
<EuiPanel hasShadow={false} hasBorder={true}>
<EuiFormRow
data-test-subj={'roleNameFormRow'}
label={
<FormattedMessage
id="xpack.security.management.editRole.roleNameFormRowTitle"
defaultMessage="Role name"
/>
}
helpText={
!isEditingExistingRole ? (
<FormattedMessage
id="xpack.security.management.createRole.roleNameFormRowHelpText"
defaultMessage="Once the role is created you can no longer edit its name."
/>
) : !isRoleReserved ? (
<FormattedMessage
id="xpack.security.management.editRole.roleNameFormRowHelpText"
defaultMessage="A role's name cannot be changed once it has been created."
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
data-test-subj={'roleNameFormRow'}
label={
<FormattedMessage
id="xpack.security.management.editRole.roleNameFormRowTitle"
defaultMessage="Role name"
/>
}
helpText={
!isEditingExistingRole ? (
<FormattedMessage
id="xpack.security.management.createRole.roleNameFormRowHelpText"
defaultMessage="Once the role is created you can no longer edit its name."
/>
) : !isRoleReserved ? (
<FormattedMessage
id="xpack.security.management.editRole.roleNameFormRowHelpText"
defaultMessage="A role's name cannot be changed once it has been created."
/>
) : undefined
}
{...validator.validateRoleName(role)}
{...(creatingRoleAlreadyExists
? { error: 'A role with this name already exists.', isInvalid: true }
: {})}
>
<EuiFieldText
name={'name'}
value={role.name || ''}
onChange={onNameChange}
onBlur={onNameBlur}
data-test-subj={'roleFormNameInput'}
disabled={isRoleReserved || isEditingExistingRole || isRoleReadOnly}
isInvalid={creatingRoleAlreadyExists}
/>
) : undefined
}
{...validator.validateRoleName(role)}
{...(creatingRoleAlreadyExists
? { error: 'A role with this name already exists.', isInvalid: true }
: {})}
>
<EuiFieldText
name={'name'}
value={role.name || ''}
onChange={onNameChange}
onBlur={onNameBlur}
data-test-subj={'roleFormNameInput'}
disabled={isRoleReserved || isEditingExistingRole || isRoleReadOnly}
isInvalid={creatingRoleAlreadyExists}
/>
</EuiFormRow>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
data-test-subj="roleDescriptionFormRow"
label={
<FormattedMessage
id="xpack.security.management.editRole.roleDescriptionFormRowTitle"
defaultMessage="Role description"
/>
}
>
{isRoleReserved || isRoleReadOnly ? (
<EuiToolTip
content={role.description}
display="block"
data-test-subj="roleFormDescriptionTooltip"
>
<EuiFieldText
name="description"
value={role.description ?? ''}
data-test-subj="roleFormDescriptionInput"
disabled
/>
</EuiToolTip>
) : (
<EuiFieldText
name="description"
value={role.description ?? ''}
onChange={onDescriptionChange}
data-test-subj="roleFormDescriptionInput"
/>
)}
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
};
Expand All @@ -510,6 +549,12 @@ export const EditRolePage: FunctionComponent<Props> = ({
}
};

const onDescriptionChange = (e: ChangeEvent<HTMLInputElement>) =>
setRole({
...role,
description: e.target.value.trim().length ? e.target.value : undefined,
});

const getElasticsearchPrivileges = () => {
return (
<div>
Expand Down Expand Up @@ -787,7 +832,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
</Fragment>
)}
<EuiSpacer />
{getRoleName()}
{getRoleNameAndDescription()}
{getElasticsearchPrivileges()}
{getKibanaPrivileges()}
<EuiSpacer />
Expand Down
Loading