Skip to content

Commit

Permalink
Assign Roles to Space from Spaces Management (elastic#191795)
Browse files Browse the repository at this point in the history
## Summary

Epic link: elastic/kibana-team#785

This changes bring a new design to the management of Spaces in Stack
Management / Security. We have a new page to view the details of the
Space, and new UX to assign Roles to a Space.

### Release Note
Added several UX improvements to the management of Spaces in **Stack
Management > Spaces**, including the ability to assign Roles to an
existing Space.

### Checklist

Delete any items that are not applicable to this PR.

- [x] Use flaky test runner on changed functional tests:
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/6953
- [x] Create test for the ability to change space avatar from `initials`
to `image` and vice versa
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Eyo Okon Eyo <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
Co-authored-by: Aleh Zasypkin <[email protected]>
  • Loading branch information
5 people authored Sep 23, 2024
1 parent cd5ff16 commit fb9700c
Show file tree
Hide file tree
Showing 76 changed files with 5,207 additions and 476 deletions.
7 changes: 6 additions & 1 deletion x-pack/packages/security/plugin_types_public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export type {
UserProfileSuggestParams,
UserProfileAPIClient,
} from './src/user_profile';
export type { RolePutPayload, RolesAPIClient } from './src/roles';
export type {
BulkUpdatePayload,
BulkUpdateRoleResponse,
RolePutPayload,
RolesAPIClient,
} from './src/roles';
export { PrivilegesAPIClientPublicContract } from './src/privileges';
export type { PrivilegesAPIClientGetAllArgs } from './src/privileges';
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface PrivilegesAPIClientGetAllArgs {
*/
respectLicenseLevel: boolean;
}
// TODO: Eyo include the proper return types for contract

export abstract class PrivilegesAPIClientPublicContract {
abstract getAll(args: PrivilegesAPIClientGetAllArgs): Promise<RawKibanaPrivileges>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@
* 2.0.
*/

export type { RolePutPayload, RolesAPIClient } from './roles_api_client';
export type {
BulkUpdatePayload,
BulkUpdateRoleResponse,
RolePutPayload,
RolesAPIClient,
} from './roles_api_client';
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,20 @@ export interface RolePutPayload {
createOnly?: boolean;
}

export interface BulkUpdatePayload {
rolesUpdate: Role[];
}

export interface BulkUpdateRoleResponse {
created?: string[];
updated?: string[];
errors?: Record<string, { type: string; reason: string }>;
}

export interface RolesAPIClient {
getRoles: () => Promise<Role[]>;
getRole: (roleName: string) => Promise<Role>;
deleteRole: (roleName: string) => Promise<void>;
saveRole: (payload: RolePutPayload) => Promise<void>;
bulkUpdateRoles: (payload: BulkUpdatePayload) => Promise<BulkUpdateRoleResponse>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import {
kibanaFeatures,
} from '@kbn/security-role-management-model/src/__fixtures__';
import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers';
import type { Role } from '@kbn/security-plugin-types-common';

import { getDisplayedFeaturePrivileges } from './__fixtures__';
import { FeatureTable } from './feature_table';
import type { Role } from '@kbn/security-plugin-types-common';
import { PrivilegeFormCalculator } from '../privilege_form_calculator';

const createRole = (kibana: Role['kibana'] = []): Role => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ interface Props {
canCustomizeSubFeaturePrivileges: boolean;
allSpacesSelected: boolean;
disabled?: boolean;
/**
* default is true, to remain backwards compatible
*/
showTitle?: boolean;
}

interface State {
Expand All @@ -58,6 +62,7 @@ export class FeatureTable extends Component<Props, State> {
public static defaultProps = {
privilegeIndex: -1,
showLocks: true,
showTitle: true,
};

private featureCategories: Map<string, SecuredFeature[]> = new Map();
Expand Down Expand Up @@ -187,16 +192,18 @@ export class FeatureTable extends Component<Props, State> {
<div>
<EuiFlexGroup alignItems={'flexEnd'}>
<EuiFlexItem>
<EuiText size="xs">
<b>
{i18n.translate(
'xpack.security.management.editRole.featureTable.featureVisibilityTitle',
{
defaultMessage: 'Customize feature privileges',
}
)}
</b>
</EuiText>
{this.props.showTitle && (
<EuiText size="xs">
<b>
{i18n.translate(
'xpack.security.management.editRole.featureTable.featureVisibilityTitle',
{
defaultMessage: 'Customize feature privileges',
}
)}
</b>
</EuiText>
)}
</EuiFlexItem>
{!this.props.disabled && (
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import {
createKibanaPrivileges,
kibanaFeatures,
} from '@kbn/security-role-management-model/src/__fixtures__';
import type { Role } from '@kbn/security-plugin-types-common';
import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers';

import { FeatureTableExpandedRow } from './feature_table_expanded_row';
import type { Role } from '@kbn/security-plugin-types-common';
import { PrivilegeFormCalculator } from '../privilege_form_calculator';

const createRole = (kibana: Role['kibana'] = []): Role => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {
createKibanaPrivileges,
kibanaFeatures,
} from '@kbn/security-role-management-model/src/__fixtures__';
import type { Role } from '@kbn/security-plugin-types-common';

import { PrivilegeFormCalculator } from './privilege_form_calculator';
import type { Role } from '@kbn/security-plugin-types-common';

const createRole = (kibana: Role['kibana'] = []): Role => {
return {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/security/public/authentication/index.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const authorizationMock = {
getRole: jest.fn(),
deleteRole: jest.fn(),
saveRole: jest.fn(),
bulkUpdateRoles: jest.fn(),
},
privileges: {
getAll: jest.fn(),
Expand All @@ -43,6 +44,7 @@ export const authorizationMock = {
getRole: jest.fn(),
deleteRole: jest.fn(),
saveRole: jest.fn(),
bulkUpdateRoles: jest.fn(),
},
privileges: {
getAll: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class AuthorizationService {
getRole: rolesAPIClient.getRole,
deleteRole: rolesAPIClient.deleteRole,
saveRole: rolesAPIClient.saveRole,
bulkUpdateRoles: rolesAPIClient.bulkUpdateRoles,
},
privileges: {
getAll: privilegesAPIClient.getAll.bind(privilegesAPIClient),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
EuiFlexItem,
EuiForm,
EuiFormRow,
EuiIconTip,
EuiPanel,
EuiSpacer,
EuiText,
Expand Down Expand Up @@ -556,52 +557,46 @@ export const EditRolePage: FunctionComponent<Props> = ({

const getElasticsearchPrivileges = () => {
return (
<div>
<EuiSpacer />
<ElasticsearchPrivileges
role={role}
editable={!isRoleReadOnly}
indicesAPIClient={indicesAPIClient}
onChange={onRoleChange}
runAsUsers={runAsUsers}
validator={validator}
indexPatterns={indexPatternsTitles}
remoteClusters={remoteClustersState.value}
builtinESPrivileges={builtInESPrivileges}
license={license}
docLinks={docLinks}
canUseRemoteIndices={
buildFlavor === 'traditional' && featureCheckState.value?.canUseRemoteIndices
}
canUseRemoteClusters={
buildFlavor === 'traditional' && featureCheckState.value?.canUseRemoteClusters
}
isDarkMode={isDarkMode}
buildFlavor={buildFlavor}
/>
</div>
<ElasticsearchPrivileges
role={role}
editable={!isRoleReadOnly}
indicesAPIClient={indicesAPIClient}
onChange={onRoleChange}
runAsUsers={runAsUsers}
validator={validator}
indexPatterns={indexPatternsTitles}
remoteClusters={remoteClustersState.value}
builtinESPrivileges={builtInESPrivileges}
license={license}
docLinks={docLinks}
canUseRemoteIndices={
buildFlavor === 'traditional' && featureCheckState.value?.canUseRemoteIndices
}
canUseRemoteClusters={
buildFlavor === 'traditional' && featureCheckState.value?.canUseRemoteClusters
}
isDarkMode={isDarkMode}
buildFlavor={buildFlavor}
/>
);
};

const onRoleChange = (newRole: Role) => setRole(newRole);

const getKibanaPrivileges = () => {
return (
<div>
<EuiSpacer />
<KibanaPrivilegesRegion
kibanaPrivileges={new KibanaPrivileges(kibanaPrivileges, features)}
spaces={spaces.list}
spacesEnabled={spaces.enabled}
uiCapabilities={uiCapabilities}
canCustomizeSubFeaturePrivileges={license.getFeatures().allowSubFeaturePrivileges}
editable={!isRoleReadOnly}
role={role}
onChange={onRoleChange}
validator={validator}
spacesApiUi={spacesApiUi}
/>
</div>
<KibanaPrivilegesRegion
kibanaPrivileges={new KibanaPrivileges(kibanaPrivileges, features)}
spaces={spaces.list}
spacesEnabled={spaces.enabled}
uiCapabilities={uiCapabilities}
canCustomizeSubFeaturePrivileges={license.getFeatures().allowSubFeaturePrivileges}
editable={!isRoleReadOnly}
role={role}
onChange={onRoleChange}
validator={validator}
spacesApiUi={spacesApiUi}
/>
);
};

Expand Down Expand Up @@ -800,44 +795,89 @@ export const EditRolePage: FunctionComponent<Props> = ({

return (
<div className="editRolePage">
<EuiForm {...formError}>
{getFormTitle()}
<EuiSpacer />
<EuiText size="s">
<FormattedMessage
id="xpack.security.management.editRole.setPrivilegesToKibanaSpacesDescription"
defaultMessage="Set privileges on your Elasticsearch data and control access to your Project spaces."
/>
</EuiText>
{isRoleReserved && (
<Fragment>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
<p id="reservedRoleDescription" tabIndex={0}>
<EuiForm {...formError} fullWidth>
<EuiFlexGroup direction="column">
<EuiFlexItem>
{getFormTitle()}
<EuiSpacer />
<EuiText size="s">
<FormattedMessage
id="xpack.security.management.editRole.setPrivilegesToKibanaSpacesDescription"
defaultMessage="Set privileges on your Elasticsearch data and control access to your Project spaces."
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
{isRoleReserved && (
<Fragment>
<EuiText size="s" color="subdued">
<p id="reservedRoleDescription" tabIndex={0}>
<FormattedMessage
id="xpack.security.management.editRole.modifyingReversedRolesDescription"
defaultMessage="Reserved roles are built-in and cannot be removed or modified."
/>
</p>
</EuiText>
</Fragment>
)}
</EuiFlexItem>
<EuiFlexItem>
{isDeprecatedRole && (
<Fragment>
<EuiSpacer size="s" />
<EuiCallOut
title={getExtendedRoleDeprecationNotice(role)}
color="warning"
iconType="warning"
/>
</Fragment>
)}
</EuiFlexItem>
<EuiFlexItem>{getRoleNameAndDescription()}</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={
<FormattedMessage
id="xpack.security.management.editRole.modifyingReversedRolesDescription"
defaultMessage="Reserved roles are built-in and cannot be removed or modified."
id="xpack.security.management.editRole.dataLayerLabel"
defaultMessage="Data Layer"
/>
</p>
</EuiText>
</Fragment>
)}
{isDeprecatedRole && (
<Fragment>
<EuiSpacer size="s" />
<EuiCallOut
title={getExtendedRoleDeprecationNotice(role)}
color="warning"
iconType="warning"
/>
</Fragment>
)}
<EuiSpacer />
{getRoleNameAndDescription()}
{getElasticsearchPrivileges()}
{getKibanaPrivileges()}
<EuiSpacer />
{getFormButtons()}
}
>
{getElasticsearchPrivileges()}
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label={
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
<FormattedMessage
id="xpack.security.management.editRole.appLayerLabel"
defaultMessage="Application layer"
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip
type="iInCircle"
color="subdued"
content={
<FormattedMessage
id="xpack.security.management.editRole.appLayerTooltipText"
defaultMessage="Feature access is granted on a per space basis for all features. Feature visibility is set on the space. Both must be enabled for this role to use a feature"
/>
}
/>
</EuiFlexItem>
</EuiFlexGroup>
}
>
{getKibanaPrivileges()}
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow fullWidth={false}>{getFormButtons()}</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</EuiForm>
</div>
);
Expand Down
Loading

0 comments on commit fb9700c

Please sign in to comment.