[7.x] Role Management - use ES Builtin Privilege API to drive… (#40752)

* use ES builtin privileges API for role management

* Exclude 'none' from privilege lists

* additional cleanup
This commit is contained in:
Larry Gregory 2019-07-11 08:55:18 -04:00 committed by GitHub
parent d74f56a199
commit 089d92dd46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 231 additions and 217 deletions

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export interface BuiltinESPrivileges {
cluster: string[];
index: string[];
}

View file

@ -10,3 +10,4 @@ export { RawKibanaPrivileges, RawKibanaFeaturePrivileges } from './raw_kibana_pr
export { KibanaPrivileges } from './kibana_privileges';
export { User, EditUser, getUserDisplayName } from './user';
export { AuthenticatedUser, canUserChangePassword } from './authenticated_user';
export { BuiltinESPrivileges } from './builtin_es_privileges';

View file

@ -12,6 +12,7 @@ import { initUsersApi } from './server/routes/api/v1/users';
import { initExternalRolesApi } from './server/routes/api/external/roles';
import { initPrivilegesApi } from './server/routes/api/external/privileges';
import { initIndicesApi } from './server/routes/api/v1/indices';
import { initGetBuiltinPrivilegesApi } from './server/routes/api/v1/builtin_privileges';
import { initOverwrittenSessionView } from './server/routes/views/overwritten_session';
import { initLoginView } from './server/routes/views/login';
import { initLogoutView } from './server/routes/views/logout';
@ -230,6 +231,7 @@ export const security = (kibana) => new kibana.Plugin({
initExternalRolesApi(server);
initIndicesApi(server);
initPrivilegesApi(server);
initGetBuiltinPrivilegesApi(server);
initLoginView(server, xpackMainPlugin);
initLogoutView(server);
initLoggedOutView(server);

View file

@ -1,18 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import 'angular-resource';
import { uiModules } from 'ui/modules';
const module = uiModules.get('security', ['ngResource']);
module.service('ApplicationPrivileges', ($resource, chrome) => {
const baseUrl = chrome.addBasePath('/api/security/v1/privileges');
return $resource(baseUrl, null, {
query: {
isArray: false,
}
});
});

View file

@ -1,57 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
const clusterPrivileges = [
'all',
'monitor',
'manage',
'manage_security',
'manage_index_templates',
'manage_pipeline',
'manage_ingest_pipelines',
'transport_client',
'manage_ml',
'monitor_ml',
'manage_data_frame_transforms',
'monitor_data_frame_transforms',
'manage_watcher',
'monitor_watcher',
'read_ccr',
'manage_ccr',
'manage_ilm',
'read_ilm',
'monitor_rollup',
'manage_rollup',
'manage_token',
'manage_saml',
'create_snapshot',
'manage_oidc',
];
const indexPrivileges = [
'all',
'manage',
'monitor',
'read',
'index',
'create',
'delete',
'write',
'delete_index',
'create_index',
'view_index_metadata',
'read_cross_cluster',
'manage_follow_index',
'manage_ilm',
'manage_leader_index',
];
export function getClusterPrivileges() {
return [...clusterPrivileges];
}
export function getIndexPrivileges() {
return [...indexPrivileges];
}

View file

@ -65,6 +65,13 @@ const buildRawKibanaPrivileges = () => {
return privilegesFactory(actions, xpackMainPlugin as any).get();
};
const buildBuiltinESPrivileges = () => {
return {
cluster: ['all', 'manage', 'monitor'],
index: ['all', 'read', 'write', 'index'],
};
};
const buildUICapabilities = (canManageSpaces = true) => {
return {
catalogue: {},
@ -132,7 +139,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const spaces: Space[] = buildSpaces();
const uiCapabilities: UICapabilities = buildUICapabilities();
@ -146,7 +154,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={spaces}
spacesEnabled={true}
uiCapabilities={uiCapabilities}
@ -180,7 +189,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const spaces: Space[] = buildSpaces();
const uiCapabilities: UICapabilities = buildUICapabilities();
@ -194,7 +204,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={spaces}
spacesEnabled={true}
uiCapabilities={uiCapabilities}
@ -222,7 +233,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const spaces: Space[] = buildSpaces();
const uiCapabilities: UICapabilities = buildUICapabilities();
@ -236,7 +248,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={spaces}
spacesEnabled={true}
uiCapabilities={uiCapabilities}
@ -280,7 +293,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const spaces: Space[] = buildSpaces();
const uiCapabilities: UICapabilities = buildUICapabilities();
@ -294,7 +308,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={spaces}
spacesEnabled={true}
uiCapabilities={uiCapabilities}
@ -327,7 +342,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const spaces: Space[] = buildSpaces();
const uiCapabilities: UICapabilities = buildUICapabilities(false);
@ -341,7 +357,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={spaces}
spacesEnabled={true}
uiCapabilities={uiCapabilities}
@ -374,7 +391,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const spaces: Space[] = buildSpaces();
const uiCapabilities: UICapabilities = buildUICapabilities(false);
@ -388,7 +406,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={spaces}
spacesEnabled={true}
uiCapabilities={uiCapabilities}
@ -424,7 +443,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const uiCapabilities: UICapabilities = buildUICapabilities();
const wrapper = mountWithIntl(
@ -437,7 +457,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={[]}
spacesEnabled={false}
uiCapabilities={uiCapabilities}
@ -471,7 +492,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const uiCapabilities: UICapabilities = buildUICapabilities();
const wrapper = mountWithIntl(
@ -484,7 +506,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={[]}
spacesEnabled={false}
uiCapabilities={uiCapabilities}
@ -512,7 +535,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const uiCapabilities: UICapabilities = buildUICapabilities();
const wrapper = mountWithIntl(
@ -525,7 +549,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={[]}
spacesEnabled={false}
uiCapabilities={uiCapabilities}
@ -568,7 +593,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const uiCapabilities: UICapabilities = buildUICapabilities();
const wrapper = mountWithIntl(
@ -581,7 +607,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={[]}
spacesEnabled={false}
uiCapabilities={uiCapabilities}
@ -613,7 +640,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const uiCapabilities: UICapabilities = buildUICapabilities(false);
const wrapper = mountWithIntl(
@ -626,7 +654,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={[]}
spacesEnabled={false}
uiCapabilities={uiCapabilities}
@ -659,7 +688,8 @@ describe('<EditRolePage />', () => {
const features: Feature[] = buildFeatures();
const mockHttpClient = jest.fn();
const indexPatterns: string[] = ['foo*', 'bar*'];
const privileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const kibanaPrivileges: RawKibanaPrivileges = buildRawKibanaPrivileges();
const builtinESPrivileges = buildBuiltinESPrivileges();
const uiCapabilities: UICapabilities = buildUICapabilities(false);
const wrapper = mountWithIntl(
@ -672,7 +702,8 @@ describe('<EditRolePage />', () => {
features={features}
httpClient={mockHttpClient}
indexPatterns={indexPatterns}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
spaces={[]}
spacesEnabled={false}
uiCapabilities={uiCapabilities}

View file

@ -24,7 +24,12 @@ import { UICapabilities } from 'ui/capabilities';
import { toastNotifications } from 'ui/notify';
import { Space } from '../../../../../../spaces/common/model/space';
import { Feature } from '../../../../../../xpack_main/types';
import { KibanaPrivileges, RawKibanaPrivileges, Role } from '../../../../../common/model';
import {
KibanaPrivileges,
RawKibanaPrivileges,
Role,
BuiltinESPrivileges,
} from '../../../../../common/model';
import {
isReadOnlyRole,
isReservedRole,
@ -46,7 +51,8 @@ interface Props {
httpClient: any;
allowDocumentLevelSecurity: boolean;
allowFieldLevelSecurity: boolean;
privileges: RawKibanaPrivileges;
kibanaPrivileges: RawKibanaPrivileges;
builtinESPrivileges: BuiltinESPrivileges;
spaces?: Space[];
spacesEnabled: boolean;
intl: InjectedIntl;
@ -246,6 +252,7 @@ class EditRolePageUI extends Component<Props, State> {
runAsUsers={this.props.runAsUsers}
validator={this.validator}
indexPatterns={this.props.indexPatterns}
builtinESPrivileges={this.props.builtinESPrivileges}
allowDocumentLevelSecurity={this.props.allowDocumentLevelSecurity}
allowFieldLevelSecurity={this.props.allowFieldLevelSecurity}
/>
@ -264,7 +271,7 @@ class EditRolePageUI extends Component<Props, State> {
<div>
<EuiSpacer />
<KibanaPrivilegesRegion
kibanaPrivileges={new KibanaPrivileges(this.props.privileges)}
kibanaPrivileges={new KibanaPrivileges(this.props.kibanaPrivileges)}
spaces={this.props.spaces}
spacesEnabled={this.props.spacesEnabled}
features={this.props.features}

View file

@ -16,74 +16,11 @@ exports[`it renders without crashing 1`] = `
Object {
"label": "all",
},
Object {
"label": "monitor",
},
Object {
"label": "manage",
},
Object {
"label": "manage_security",
},
Object {
"label": "manage_index_templates",
},
Object {
"label": "manage_pipeline",
},
Object {
"label": "manage_ingest_pipelines",
},
Object {
"label": "transport_client",
},
Object {
"label": "manage_ml",
},
Object {
"label": "monitor_ml",
},
Object {
"label": "manage_data_frame_transforms",
},
Object {
"label": "monitor_data_frame_transforms",
},
Object {
"label": "manage_watcher",
},
Object {
"label": "monitor_watcher",
},
Object {
"label": "read_ccr",
},
Object {
"label": "manage_ccr",
},
Object {
"label": "manage_ilm",
},
Object {
"label": "read_ilm",
},
Object {
"label": "monitor_rollup",
},
Object {
"label": "manage_rollup",
},
Object {
"label": "manage_token",
},
Object {
"label": "manage_saml",
},
Object {
"label": "create_snapshot",
},
Object {
"label": "manage_oidc",
"label": "monitor",
},
]
}

View file

@ -48,6 +48,13 @@ exports[`it renders without crashing 1`] = `
labelType="label"
>
<ClusterPrivileges
availableClusterPrivileges={
Array [
"all",
"manage",
"monitor",
]
}
onChange={[Function]}
role={
Object {
@ -163,6 +170,14 @@ exports[`it renders without crashing 1`] = `
<IndexPrivileges
allowDocumentLevelSecurity={true}
allowFieldLevelSecurity={true}
availableIndexPrivileges={
Array [
"all",
"read",
"write",
"index",
]
}
httpClient={[MockFunction]}
indexPatterns={Array []}
onChange={[MockFunction]}

View file

@ -63,47 +63,14 @@ exports[`it renders without crashing 1`] = `
Object {
"label": "all",
},
Object {
"label": "manage",
},
Object {
"label": "monitor",
},
Object {
"label": "read",
},
Object {
"label": "index",
},
Object {
"label": "create",
},
Object {
"label": "delete",
},
Object {
"label": "write",
},
Object {
"label": "delete_index",
},
Object {
"label": "create_index",
},
Object {
"label": "view_index_metadata",
},
Object {
"label": "read_cross_cluster",
},
Object {
"label": "manage_follow_index",
},
Object {
"label": "manage_ilm",
},
Object {
"label": "manage_leader_index",
"label": "index",
},
]
}

View file

@ -20,6 +20,12 @@ test('it renders without crashing', () => {
kibana: [],
};
const wrapper = shallow(<ClusterPrivileges role={role} onChange={jest.fn()} />);
const wrapper = shallow(
<ClusterPrivileges
role={role}
onChange={jest.fn()}
availableClusterPrivileges={['all', 'manage', 'monitor']}
/>
);
expect(wrapper).toMatchSnapshot();
});

View file

@ -8,19 +8,16 @@ import { EuiComboBox, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { Component } from 'react';
import { Role } from '../../../../../../../common/model';
import { isReadOnlyRole } from '../../../../../../lib/role_utils';
// @ts-ignore
import { getClusterPrivileges } from '../../../../../../services/role_privileges';
interface Props {
role: Role;
availableClusterPrivileges: string[];
onChange: (privs: string[]) => void;
}
export class ClusterPrivileges extends Component<Props, {}> {
public render() {
const clusterPrivileges = getClusterPrivileges();
return <EuiFlexGroup>{this.buildComboBox(clusterPrivileges)}</EuiFlexGroup>;
return <EuiFlexGroup>{this.buildComboBox(this.props.availableClusterPrivileges)}</EuiFlexGroup>;
}
public buildComboBox = (items: string[]) => {

View file

@ -30,6 +30,10 @@ test('it renders without crashing', () => {
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
builtinESPrivileges: {
cluster: ['all', 'manage', 'monitor'],
index: ['all', 'read', 'write', 'index'],
},
};
const wrapper = shallowWithIntl(<ElasticsearchPrivileges {...props} />);
expect(wrapper).toMatchSnapshot();
@ -54,6 +58,10 @@ test('it renders ClusterPrivileges', () => {
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
builtinESPrivileges: {
cluster: ['all', 'manage', 'monitor'],
index: ['all', 'read', 'write', 'index'],
},
};
const wrapper = mountWithIntl(<ElasticsearchPrivileges {...props} />);
expect(wrapper.find(ClusterPrivileges)).toHaveLength(1);
@ -78,6 +86,10 @@ test('it renders IndexPrivileges', () => {
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
builtinESPrivileges: {
cluster: ['all', 'manage', 'monitor'],
index: ['all', 'read', 'write', 'index'],
},
};
const wrapper = mountWithIntl(<ElasticsearchPrivileges {...props} />);
expect(wrapper.find(IndexPrivileges)).toHaveLength(1);

View file

@ -19,7 +19,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { Component, Fragment } from 'react';
import { Role } from '../../../../../../../common/model';
import { Role, BuiltinESPrivileges } from '../../../../../../../common/model';
// @ts-ignore
import { documentationLinks } from '../../../../../../documentation_links';
import { RoleValidator } from '../../../lib/validate_role';
@ -35,6 +35,7 @@ interface Props {
onChange: (role: Role) => void;
runAsUsers: string[];
validator: RoleValidator;
builtinESPrivileges: BuiltinESPrivileges;
indexPatterns: string[];
allowDocumentLevelSecurity: boolean;
allowFieldLevelSecurity: boolean;
@ -59,6 +60,7 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
indexPatterns,
allowDocumentLevelSecurity,
allowFieldLevelSecurity,
builtinESPrivileges,
} = this.props;
const indexProps = {
@ -69,6 +71,7 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
allowDocumentLevelSecurity,
allowFieldLevelSecurity,
onChange,
availableIndexPrivileges: builtinESPrivileges.index,
};
return (
@ -93,7 +96,11 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
}
>
<EuiFormRow fullWidth={true} hasEmptyLabelSpace>
<ClusterPrivileges role={this.props.role} onChange={this.onClusterPrivilegesChange} />
<ClusterPrivileges
role={this.props.role}
onChange={this.onClusterPrivilegesChange}
availableClusterPrivileges={builtinESPrivileges.cluster}
/>
</EuiFormRow>
</EuiDescribedFormGroup>

View file

@ -22,6 +22,7 @@ test('it renders without crashing', () => {
formIndex: 0,
indexPatterns: [],
availableFields: [],
availableIndexPrivileges: ['all', 'read', 'write', 'index'],
isReadOnlyRole: false,
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,
@ -48,6 +49,7 @@ describe('delete button', () => {
formIndex: 0,
indexPatterns: [],
availableFields: [],
availableIndexPrivileges: ['all', 'read', 'write', 'index'],
isReadOnlyRole: false,
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,
@ -99,6 +101,7 @@ describe(`document level security`, () => {
formIndex: 0,
indexPatterns: [],
availableFields: [],
availableIndexPrivileges: ['all', 'read', 'write', 'index'],
isReadOnlyRole: false,
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,
@ -157,6 +160,7 @@ describe('field level security', () => {
formIndex: 0,
indexPatterns: [],
availableFields: [],
availableIndexPrivileges: ['all', 'read', 'write', 'index'],
isReadOnlyRole: false,
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,

View file

@ -20,8 +20,6 @@ import { FormattedMessage } from '@kbn/i18n/react';
import _ from 'lodash';
import React, { ChangeEvent, Component, Fragment } from 'react';
import { RoleIndexPrivilege } from '../../../../../../../common/model';
// @ts-ignore
import { getIndexPrivileges } from '../../../../../../services/role_privileges';
import { RoleValidator } from '../../../lib/validate_role';
const fromOption = (option: any) => option.label;
@ -31,6 +29,7 @@ interface Props {
formIndex: number;
indexPrivilege: RoleIndexPrivilege;
indexPatterns: string[];
availableIndexPrivileges: string[];
availableFields: string[];
onChange: (indexPrivilege: RoleIndexPrivilege) => void;
onDelete: () => void;
@ -126,7 +125,7 @@ export class IndexPrivilegeForm extends Component<Props, State> {
>
<EuiComboBox
data-test-subj={`privilegesInput${this.props.formIndex}`}
options={getIndexPrivileges().map(toOption)}
options={this.props.availableIndexPrivileges.map(toOption)}
selectedOptions={this.props.indexPrivilege.privileges.map(toOption)}
onChange={this.onPrivilegeChange}
isDisabled={this.props.isReadOnlyRole}

View file

@ -28,6 +28,7 @@ test('it renders without crashing', () => {
allowFieldLevelSecurity: true,
editable: true,
validator: new RoleValidator(),
availableIndexPrivileges: ['all', 'read', 'write', 'index'],
};
const wrapper = shallowWithIntl(<IndexPrivileges {...props} />);
expect(wrapper).toMatchSnapshot();
@ -60,6 +61,7 @@ test('it renders a IndexPrivilegeForm for each privilege on the role', () => {
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,
validator: new RoleValidator(),
availableIndexPrivileges: ['all', 'read', 'write', 'index'],
};
const wrapper = mountWithIntl(<IndexPrivileges {...props} />);
expect(wrapper.find(IndexPrivilegeForm)).toHaveLength(1);

View file

@ -14,6 +14,7 @@ import { IndexPrivilegeForm } from './index_privilege_form';
interface Props {
role: Role;
indexPatterns: string[];
availableIndexPrivileges: string[];
allowDocumentLevelSecurity: boolean;
allowFieldLevelSecurity: boolean;
httpClient: any;
@ -42,7 +43,12 @@ export class IndexPrivileges extends Component<Props, State> {
public render() {
const { indices = [] } = this.props.role.elasticsearch;
const { indexPatterns, allowDocumentLevelSecurity, allowFieldLevelSecurity } = this.props;
const {
indexPatterns,
allowDocumentLevelSecurity,
allowFieldLevelSecurity,
availableIndexPrivileges,
} = this.props;
const props = {
indexPatterns,
@ -60,6 +66,7 @@ export class IndexPrivileges extends Component<Props, State> {
{...props}
formIndex={idx}
validator={this.props.validator}
availableIndexPrivileges={availableIndexPrivileges}
indexPrivilege={indexPrivilege}
availableFields={this.state.availableFields[indexPrivilege.names.join(',')]}
onChange={this.onIndexPrivilegeChange(idx)}

View file

@ -10,8 +10,6 @@ import { capabilities } from 'ui/capabilities';
import { kfetch } from 'ui/kfetch';
import { fatalError, toastNotifications } from 'ui/notify';
import template from 'plugins/security/views/management/edit_role/edit_role.html';
import 'ui/angular_ui_select';
import 'plugins/security/services/application_privilege';
import 'plugins/security/services/shield_user';
import 'plugins/security/services/shield_role';
import 'plugins/security/services/shield_indices';
@ -87,9 +85,12 @@ const routeDefinition = (action) => ({
}
return [];
},
privileges() {
kibanaPrivileges() {
return kfetch({ method: 'get', pathname: '/api/security/privileges', query: { includeActions: true } });
},
builtinESPrivileges() {
return kfetch({ method: 'get', pathname: '/api/security/v1/esPrivileges/builtin' });
},
features() {
return kfetch({ method: 'get', pathname: '/api/features/v1' }).catch(e => {
// TODO: This check can be removed once all of these `resolve` entries are moved out of Angular and into the React app.
@ -132,7 +133,8 @@ const routeDefinition = (action) => ({
users,
indexPatterns,
spaces,
privileges,
kibanaPrivileges,
builtinESPrivileges,
features,
} = $route.current.locals;
@ -153,7 +155,8 @@ const routeDefinition = (action) => ({
spacesEnabled={enableSpaceAwarePrivileges}
uiCapabilities={capabilities.get()}
features={features}
privileges={privileges}
kibanaPrivileges={kibanaPrivileges}
builtinESPrivileges={builtinESPrivileges}
/>
</I18nContext>, domNode);

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Legacy } from 'kibana';
import { BuiltinESPrivileges } from '../../../../common/model';
import { getClient } from '../../../../../../server/lib/get_client_shield';
export function initGetBuiltinPrivilegesApi(server: Legacy.Server) {
server.route({
method: 'GET',
path: '/api/security/v1/esPrivileges/builtin',
async handler(req: Legacy.Request) {
const callWithRequest = getClient(server).callWithRequest;
const privileges = await callWithRequest<BuiltinESPrivileges>(
req,
'shield.getBuiltinPrivileges'
);
// Exclude the `none` privilege, as it doesn't make sense as an option within the Kibana UI
privileges.cluster = privileges.cluster.filter(privilege => privilege !== 'none');
privileges.index = privileges.index.filter(privilege => privilege !== 'none');
return privileges;
},
});
}

View file

@ -488,5 +488,14 @@
fmt: '/_security/user/_has_privileges'
}
});
shield.getBuiltinPrivileges = ca({
params: {},
urls: [
{
fmt: '/_security/privilege/_builtin'
}
]
});
};
}));

View file

@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect/expect.js';
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
// eslint-disable-next-line import/no-default-export
export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
const supertest = getService('supertest');
describe('Builtin ES Privileges', () => {
describe('GET /api/security/v1/esPrivileges/builtin', () => {
it('should return a list of available builtin privileges', async () => {
await supertest
.get('/api/security/v1/esPrivileges/builtin')
.set('kbn-xsrf', 'xxx')
.send()
.expect(200)
.then((response: Record<string, any>) => {
const sampleOfExpectedClusterPrivileges = ['all', 'manage', 'monitor'];
const sampleOfExpectedIndexPrivileges = ['create', 'index', 'delete'];
const payload = response.body;
expect(Object.keys(payload).sort()).to.eql(['cluster', 'index']);
sampleOfExpectedClusterPrivileges.forEach(privilege =>
expect(payload.cluster).to.contain(privilege)
);
sampleOfExpectedIndexPrivileges.forEach(privilege =>
expect(payload.index).to.contain(privilege)
);
expect(payload.cluster).not.to.contain('none');
expect(payload.index).not.to.contain('none');
});
});
});
});
}

View file

@ -9,6 +9,7 @@ export default function ({ loadTestFile }) {
this.tags('ciGroup6');
loadTestFile(require.resolve('./basic_login'));
loadTestFile(require.resolve('./builtin_es_privileges'));
loadTestFile(require.resolve('./index_fields'));
loadTestFile(require.resolve('./roles'));
loadTestFile(require.resolve('./privileges'));