Prevent Spaces from being disabled (#115283)

This commit is contained in:
Joe Portner 2021-10-18 11:17:04 -04:00 committed by GitHub
parent 3ab9e07962
commit 0f1c7ccc98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 356 additions and 7676 deletions

View file

@ -7,11 +7,6 @@
By default, spaces is enabled in {kib}. To secure spaces, <<security-settings-kb,enable security>>.
`xpack.spaces.enabled`::
deprecated:[7.16.0,"In 8.0 and later, this setting will no longer be supported and it will not be possible to disable this plugin."]
To enable spaces, set to `true`.
The default is `true`.
`xpack.spaces.maxSpaces`::
The maximum number of spaces that you can use with the {kib} instance. Some {kib} operations
return all spaces using a single `_search` from {es}, so you must

View file

@ -109,11 +109,6 @@ image::images/spaces-configure-landing-page.png["Configure space-level landing p
[float]
[[spaces-delete-started]]
=== Disable and version updates
Spaces are automatically enabled in {kib}. If you don't want use this feature,
you can disable it. For more information, refer to <<spaces-settings-kb,Spaces settings in {kib}>>.
When you upgrade {kib}, the default space contains all of your existing saved objects.
=== Disabling spaces
Starting in {kib} 8.0, the Spaces feature cannot be disabled.

View file

@ -366,7 +366,6 @@ kibana_vars=(
xpack.securitySolution.packagerTaskInterval
xpack.securitySolution.prebuiltRulesFromFileSystem
xpack.securitySolution.prebuiltRulesFromSavedObjects
xpack.spaces.enabled
xpack.spaces.maxSpaces
xpack.task_manager.index
xpack.task_manager.max_attempts

View file

@ -22,7 +22,6 @@ import { userAPIClientMock } from '../../users/index.mock';
import { createRawKibanaPrivileges } from '../__fixtures__/kibana_privileges';
import { indicesAPIClientMock, privilegesAPIClientMock, rolesAPIClientMock } from '../index.mock';
import { EditRolePage } from './edit_role_page';
import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section';
import { SpaceAwarePrivilegeSection } from './privileges/kibana/space_aware_privilege_section';
import { TransformErrorSection } from './privileges/kibana/transform_error_section';
@ -132,12 +131,10 @@ function getProps({
action,
role,
canManageSpaces = true,
spacesEnabled = true,
}: {
action: 'edit' | 'clone';
role?: Role;
canManageSpaces?: boolean;
spacesEnabled?: boolean;
}) {
const rolesAPIClient = rolesAPIClientMock.create();
rolesAPIClient.getRole.mockResolvedValue(role);
@ -165,12 +162,7 @@ function getProps({
const { http, docLinks, notifications } = coreMock.createStart();
http.get.mockImplementation(async (path: any) => {
if (path === '/api/spaces/space') {
if (spacesEnabled) {
return buildSpaces();
}
const notFoundError = { response: { status: 404 } };
throw notFoundError;
return buildSpaces();
}
});
@ -335,152 +327,6 @@ describe('<EditRolePage />', () => {
});
});
describe('with spaces disabled', () => {
it('can render a reserved role', async () => {
const wrapper = mountWithIntl(
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
name: 'superuser',
metadata: { _reserved: true },
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [{ spaces: ['*'], base: ['all'], feature: {} }],
},
})}
/>
);
await waitForRender(wrapper);
expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expectReadOnlyFormButtons(wrapper);
});
it('can render a user defined role', async () => {
const wrapper = mountWithIntl(
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
name: 'my custom role',
metadata: {},
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [{ spaces: ['*'], base: ['all'], feature: {} }],
},
})}
/>
);
await waitForRender(wrapper);
expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0);
expectSaveFormButtons(wrapper);
});
it('can render when creating a new role', async () => {
const wrapper = mountWithIntl(
<EditRolePage {...getProps({ action: 'edit', spacesEnabled: false })} />
);
await waitForRender(wrapper);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expectSaveFormButtons(wrapper);
});
it('can render when cloning an existing role', async () => {
const wrapper = mountWithIntl(
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
role: {
metadata: { _reserved: false },
name: '',
elasticsearch: {
cluster: ['all', 'manage'],
indices: [
{
names: ['foo*'],
privileges: ['all'],
field_security: { except: ['f'], grant: ['b*'] },
},
],
run_as: ['elastic'],
},
kibana: [{ spaces: ['*'], base: ['all'], feature: {} }],
},
})}
/>
);
await waitForRender(wrapper);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expectSaveFormButtons(wrapper);
});
it('does not care if user cannot manage spaces', async () => {
const wrapper = mountWithIntl(
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
canManageSpaces: false,
role: {
name: 'my custom role',
metadata: {},
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [{ spaces: ['*'], base: ['all'], feature: {} }],
},
})}
/>
);
await waitForRender(wrapper);
expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0);
expect(
wrapper.find('EuiCallOut[data-test-subj="userCannotManageSpacesCallout"]')
).toHaveLength(0);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expectSaveFormButtons(wrapper);
});
it('renders a partial read-only view when there is a transform error', async () => {
const wrapper = mountWithIntl(
<EditRolePage
{...getProps({
action: 'edit',
spacesEnabled: false,
canManageSpaces: false,
role: {
name: 'my custom role',
metadata: {},
elasticsearch: { cluster: ['all'], indices: [], run_as: ['*'] },
kibana: [],
_transform_error: ['kibana'],
},
})}
/>
);
await waitForRender(wrapper);
expect(wrapper.find(TransformErrorSection)).toHaveLength(1);
expectReadOnlyFormButtons(wrapper);
});
});
it('registers fatal error if features endpoint fails unexpectedly', async () => {
const error = { response: { status: 500 } };
const getFeatures = jest.fn().mockRejectedValue(error);

View file

@ -436,7 +436,6 @@ export const EditRolePage: FunctionComponent<Props> = ({
<KibanaPrivilegesRegion
kibanaPrivileges={new KibanaPrivileges(kibanaPrivileges, features)}
spaces={spaces.list}
spacesEnabled={spaces.enabled}
uiCapabilities={uiCapabilities}
canCustomizeSubFeaturePrivileges={license.getFeatures().allowSubFeaturePrivileges}
editable={!isRoleReadOnly}
@ -521,7 +520,7 @@ export const EditRolePage: FunctionComponent<Props> = ({
setFormError(null);
try {
await rolesAPIClient.saveRole({ role, spacesEnabled: spaces.enabled });
await rolesAPIClient.saveRole({ role });
} catch (error) {
notifications.toasts.addDanger(
error?.body?.message ??
@ -566,24 +565,17 @@ export const EditRolePage: FunctionComponent<Props> = ({
backToRoleList();
};
const description = spaces.enabled ? (
<FormattedMessage
id="xpack.security.management.editRole.setPrivilegesToKibanaSpacesDescription"
defaultMessage="Set privileges on your Elasticsearch data and control access to your Kibana spaces."
/>
) : (
<FormattedMessage
id="xpack.security.management.editRole.setPrivilegesToKibanaDescription"
defaultMessage="Set privileges on your Elasticsearch data and control access to Kibana."
/>
);
return (
<div className="editRolePage">
<EuiForm {...formError}>
{getFormTitle()}
<EuiSpacer />
<EuiText size="s">{description}</EuiText>
<EuiText size="s">
<FormattedMessage
id="xpack.security.management.editRole.setPrivilegesToKibanaSpacesDescription"
defaultMessage="Set privileges on your Elasticsearch data and control access to your Kibana spaces."
/>
</EuiText>
{isRoleReserved && (
<Fragment>
<EuiSpacer size="s" />

View file

@ -12,11 +12,10 @@ import type { Role } from '../../../../../../common/model';
import { KibanaPrivileges } from '../../../model';
import { RoleValidator } from '../../validate_role';
import { KibanaPrivilegesRegion } from './kibana_privileges_region';
import { SimplePrivilegeSection } from './simple_privilege_section';
import { SpaceAwarePrivilegeSection } from './space_aware_privilege_section';
import { TransformErrorSection } from './transform_error_section';
const buildProps = (customProps = {}) => {
const buildProps = () => {
return {
role: {
name: '',
@ -27,7 +26,6 @@ const buildProps = (customProps = {}) => {
},
kibana: [],
},
spacesEnabled: true,
spaces: [
{
id: 'default',
@ -64,7 +62,6 @@ const buildProps = (customProps = {}) => {
onChange: jest.fn(),
validator: new RoleValidator(),
canCustomizeSubFeaturePrivileges: true,
...customProps,
};
};
@ -73,26 +70,17 @@ describe('<KibanaPrivileges>', () => {
expect(shallow(<KibanaPrivilegesRegion {...buildProps()} />)).toMatchSnapshot();
});
it('renders the simple privilege form when spaces is disabled', () => {
const props = buildProps({ spacesEnabled: false });
it('renders the space-aware privilege form', () => {
const props = buildProps();
const wrapper = shallow(<KibanaPrivilegesRegion {...props} />);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1);
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(0);
});
it('renders the space-aware privilege form when spaces is enabled', () => {
const props = buildProps({ spacesEnabled: true });
const wrapper = shallow(<KibanaPrivilegesRegion {...props} />);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(0);
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1);
});
it('renders the transform error section when the role has a transform error', () => {
const props = buildProps({ spacesEnabled: true });
const props = buildProps();
(props.role as Role)._transform_error = ['kibana'];
const wrapper = shallow(<KibanaPrivilegesRegion {...props} />);
expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(0);
expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(0);
expect(wrapper.find(TransformErrorSection)).toHaveLength(1);
});

View file

@ -14,13 +14,11 @@ import type { Role } from '../../../../../../common/model';
import type { KibanaPrivileges } from '../../../model';
import { CollapsiblePanel } from '../../collapsible_panel';
import type { RoleValidator } from '../../validate_role';
import { SimplePrivilegeSection } from './simple_privilege_section';
import { SpaceAwarePrivilegeSection } from './space_aware_privilege_section';
import { TransformErrorSection } from './transform_error_section';
interface Props {
role: Role;
spacesEnabled: boolean;
canCustomizeSubFeaturePrivileges: boolean;
spaces?: Space[];
uiCapabilities: Capabilities;
@ -44,7 +42,6 @@ export class KibanaPrivilegesRegion extends Component<Props, {}> {
const {
kibanaPrivileges,
role,
spacesEnabled,
canCustomizeSubFeaturePrivileges,
spaces = [],
uiCapabilities,
@ -58,30 +55,18 @@ export class KibanaPrivilegesRegion extends Component<Props, {}> {
return <TransformErrorSection />;
}
if (spacesEnabled) {
return (
<SpaceAwarePrivilegeSection
kibanaPrivileges={kibanaPrivileges}
role={role}
spaces={spaces}
uiCapabilities={uiCapabilities}
onChange={onChange}
editable={editable}
canCustomizeSubFeaturePrivileges={canCustomizeSubFeaturePrivileges}
validator={validator}
spacesApiUi={spacesApiUi!}
/>
);
} else {
return (
<SimplePrivilegeSection
kibanaPrivileges={kibanaPrivileges}
role={role}
onChange={onChange}
editable={editable}
canCustomizeSubFeaturePrivileges={canCustomizeSubFeaturePrivileges}
/>
);
}
return (
<SpaceAwarePrivilegeSection
kibanaPrivileges={kibanaPrivileges}
role={role}
spaces={spaces}
uiCapabilities={uiCapabilities}
onChange={onChange}
editable={editable}
canCustomizeSubFeaturePrivileges={canCustomizeSubFeaturePrivileges}
validator={validator}
spacesApiUi={spacesApiUi!}
/>
);
};
}

View file

@ -1,160 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SimplePrivilegeForm> renders without crashing 1`] = `
<Fragment>
<EuiFlexGroup
direction="column"
>
<EuiFlexItem>
<EuiText
color="subdued"
size="s"
>
<p>
<FormattedMessage
defaultMessage="Specifies the Kibana privilege for this role."
id="xpack.security.management.editRole.simplePrivilegeForm.specifyPrivilegeForRoleDescription"
values={Object {}}
/>
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
describedByIds={Array []}
display="row"
fullWidth={false}
hasChildLabel={true}
hasEmptyLabelSpace={false}
label={
<FormattedMessage
defaultMessage="Kibana privileges"
id="xpack.security.management.editRole.simplePrivilegeForm.kibanaPrivilegesTitle"
values={Object {}}
/>
}
labelType="label"
>
<EuiSuperSelect
compressed={false}
disabled={false}
fullWidth={false}
hasDividers={true}
isInvalid={false}
isLoading={false}
onChange={[Function]}
options={
Array [
Object {
"dropdownDisplay": <EuiText>
<strong>
<FormattedMessage
defaultMessage="None"
id="xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeDropdown"
values={Object {}}
/>
</strong>
<p>
<FormattedMessage
defaultMessage="No access to Kibana"
id="xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeDropdownDescription"
values={Object {}}
/>
</p>
</EuiText>,
"inputDisplay": <EuiText>
<FormattedMessage
defaultMessage="None"
id="xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeInput"
values={Object {}}
/>
</EuiText>,
"value": "none",
},
Object {
"dropdownDisplay": <EuiText>
<strong>
<FormattedMessage
defaultMessage="Custom"
id="xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeDropdown"
values={Object {}}
/>
</strong>
<p>
<FormattedMessage
defaultMessage="Customize access to Kibana"
id="xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeDropdownDescription"
values={Object {}}
/>
</p>
</EuiText>,
"inputDisplay": <EuiText>
<FormattedMessage
defaultMessage="Custom"
id="xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeInput"
values={Object {}}
/>
</EuiText>,
"value": "custom",
},
Object {
"dropdownDisplay": <EuiText>
<strong>
<FormattedMessage
defaultMessage="Read"
id="xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeDropdown"
values={Object {}}
/>
</strong>
<p>
<FormattedMessage
defaultMessage="Grants read-only access to all of Kibana"
id="xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeDropdownDescription"
values={Object {}}
/>
</p>
</EuiText>,
"inputDisplay": <EuiText>
<FormattedMessage
defaultMessage="Read"
id="xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeInput"
values={Object {}}
/>
</EuiText>,
"value": "read",
},
Object {
"dropdownDisplay": <EuiText>
<strong>
<FormattedMessage
defaultMessage="All"
id="xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeDropdown"
values={Object {}}
/>
</strong>
<p>
<FormattedMessage
defaultMessage="Grants full access to all of Kibana"
id="xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeDropdownDescription"
values={Object {}}
/>
</p>
</EuiText>,
"inputDisplay": <EuiText>
<FormattedMessage
defaultMessage="All"
id="xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeInput"
values={Object {}}
/>
</EuiText>,
"value": "all",
},
]
}
valueOfSelected="none"
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
`;

View file

@ -1,8 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { SimplePrivilegeSection } from './simple_privilege_section';

View file

@ -1,59 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiSelect } from '@elastic/eui';
import type { ChangeEvent } from 'react';
import React, { Component } from 'react';
import { NO_PRIVILEGE_VALUE } from '../constants';
interface Props {
['data-test-subj']: string;
availablePrivileges: string[];
onChange: (privilege: string) => void;
value: string | null;
allowNone?: boolean;
disabled?: boolean;
compressed?: boolean;
}
export class PrivilegeSelector extends Component<Props, {}> {
public state = {};
public render() {
const { availablePrivileges, value, disabled, allowNone, compressed } = this.props;
const options = [];
if (allowNone) {
options.push({ value: NO_PRIVILEGE_VALUE, text: 'none' });
}
options.push(
...availablePrivileges.map((p) => ({
value: p,
text: p,
}))
);
return (
<EuiSelect
data-test-subj={this.props['data-test-subj']}
options={options}
hasNoInitialSelection={!allowNone && !value}
value={value || undefined}
onChange={this.onChange}
disabled={disabled}
compressed={compressed}
/>
);
}
public onChange = (e: ChangeEvent<HTMLSelectElement>) => {
this.props.onChange(e.target.value);
};
}

View file

@ -1,248 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { EuiButtonGroupProps } from '@elastic/eui';
import { EuiButtonGroup, EuiComboBox, EuiSuperSelect } from '@elastic/eui';
import React from 'react';
import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest';
import type { Role } from '../../../../../../../common/model';
import { KibanaPrivileges, SecuredFeature } from '../../../../model';
import { SimplePrivilegeSection } from './simple_privilege_section';
import { UnsupportedSpacePrivilegesWarning } from './unsupported_space_privileges_warning';
const buildProps = (customProps: any = {}) => {
const features = [
new SecuredFeature({
id: 'feature1',
name: 'Feature 1',
app: ['app'],
category: { id: 'foo', label: 'foo' },
privileges: {
all: {
app: ['app'],
savedObject: {
all: ['foo'],
read: [],
},
ui: ['app-ui'],
},
read: {
app: ['app'],
savedObject: {
all: [],
read: [],
},
ui: ['app-ui'],
},
},
}),
] as SecuredFeature[];
const kibanaPrivileges = new KibanaPrivileges(
{
features: {
feature1: {
all: ['*'],
read: ['read'],
},
},
global: {},
space: {},
reserved: {},
},
features
);
const role = {
name: '',
elasticsearch: {
cluster: ['manage'],
indices: [],
run_as: [],
},
kibana: [],
...customProps.role,
};
return {
editable: true,
kibanaPrivileges,
features,
onChange: jest.fn(),
canCustomizeSubFeaturePrivileges: true,
...customProps,
role,
};
};
describe('<SimplePrivilegeForm>', () => {
it('renders without crashing', () => {
expect(shallowWithIntl(<SimplePrivilegeSection {...buildProps()} />)).toMatchSnapshot();
});
it('displays "none" when no privilege is selected', () => {
const props = buildProps();
const wrapper = shallowWithIntl(<SimplePrivilegeSection {...props} />);
const selector = wrapper.find(EuiSuperSelect);
expect(selector.props()).toMatchObject({
valueOfSelected: 'none',
});
expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0);
});
it('displays "custom" when feature privileges are customized', () => {
const props = buildProps({
role: {
elasticsearch: {},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['foo'],
},
},
],
},
});
const wrapper = shallowWithIntl(<SimplePrivilegeSection {...props} />);
const selector = wrapper.find(EuiSuperSelect);
expect(selector.props()).toMatchObject({
valueOfSelected: 'custom',
});
expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0);
});
it('displays the selected privilege', () => {
const props = buildProps({
role: {
elasticsearch: {},
kibana: [
{
spaces: ['*'],
base: ['read'],
feature: {},
},
],
},
});
const wrapper = shallowWithIntl(<SimplePrivilegeSection {...props} />);
const selector = wrapper.find(EuiSuperSelect);
expect(selector.props()).toMatchObject({
valueOfSelected: 'read',
});
expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0);
});
it('displays the reserved privilege', () => {
const props = buildProps({
role: {
elasticsearch: {},
kibana: [
{
spaces: ['*'],
base: [],
feature: {},
_reserved: ['foo'],
},
],
},
});
const wrapper = shallowWithIntl(<SimplePrivilegeSection {...props} />);
const selector = wrapper.find(EuiComboBox);
expect(selector.props()).toMatchObject({
isDisabled: true,
selectedOptions: [{ label: 'foo' }],
});
expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0);
});
it('fires its onChange callback when the privilege changes', () => {
const props = buildProps();
const wrapper = mountWithIntl(<SimplePrivilegeSection {...props} />);
const selector = wrapper.find(EuiSuperSelect);
(selector.props() as any).onChange('all');
expect(props.onChange).toHaveBeenCalledWith({
name: '',
elasticsearch: {
cluster: ['manage'],
indices: [],
run_as: [],
},
kibana: [{ feature: {}, base: ['all'], spaces: ['*'] }],
});
expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0);
});
it('allows feature privileges to be customized', () => {
const props = buildProps({
onChange: (role: Role) => {
wrapper.setProps({
role,
});
},
});
const wrapper = mountWithIntl(<SimplePrivilegeSection {...props} />);
const selector = wrapper.find(EuiSuperSelect);
(selector.props() as any).onChange('custom');
wrapper.update();
const featurePrivilegeToggles = wrapper.find(EuiButtonGroup);
expect(featurePrivilegeToggles).toHaveLength(1);
expect(featurePrivilegeToggles.find('input')).toHaveLength(3);
(featurePrivilegeToggles.props() as EuiButtonGroupProps).onChange('feature1_all', null);
wrapper.update();
expect(wrapper.props().role).toEqual({
elasticsearch: {
cluster: ['manage'],
indices: [],
run_as: [],
},
kibana: [
{
base: [],
feature: {
feature1: ['all'],
},
spaces: ['*'],
},
],
name: '',
});
expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0);
});
it('renders a warning when space privileges are found', () => {
const props = buildProps({
role: {
elasticsearch: {},
kibana: [
{
spaces: ['*'],
base: ['read'],
feature: {},
},
{
spaces: ['marketing'],
base: ['read'],
feature: {},
},
],
},
});
const wrapper = mountWithIntl(<SimplePrivilegeSection {...props} />);
expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(1);
});
});

View file

@ -1,332 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
EuiComboBox,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiSuperSelect,
EuiText,
} from '@elastic/eui';
import React, { Component, Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import type { Role, RoleKibanaPrivilege } from '../../../../../../../common/model';
import { copyRole } from '../../../../../../../common/model';
import type { KibanaPrivileges } from '../../../../model';
import { isGlobalPrivilegeDefinition } from '../../../privilege_utils';
import { CUSTOM_PRIVILEGE_VALUE, NO_PRIVILEGE_VALUE } from '../constants';
import { FeatureTable } from '../feature_table';
import { PrivilegeFormCalculator } from '../privilege_form_calculator';
import { UnsupportedSpacePrivilegesWarning } from './unsupported_space_privileges_warning';
interface Props {
role: Role;
kibanaPrivileges: KibanaPrivileges;
onChange: (role: Role) => void;
editable: boolean;
canCustomizeSubFeaturePrivileges: boolean;
}
interface State {
isCustomizingGlobalPrivilege: boolean;
globalPrivsIndex: number;
}
export class SimplePrivilegeSection extends Component<Props, State> {
constructor(props: Props) {
super(props);
const globalPrivs = this.locateGlobalPrivilege(props.role);
const globalPrivsIndex = this.locateGlobalPrivilegeIndex(props.role);
this.state = {
isCustomizingGlobalPrivilege: Boolean(
globalPrivs && Object.keys(globalPrivs.feature).length > 0
),
globalPrivsIndex,
};
}
public render() {
const kibanaPrivilege = this.getDisplayedBasePrivilege();
const reservedPrivileges = this.props.role.kibana[this.state.globalPrivsIndex]?._reserved ?? [];
const title = (
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.kibanaPrivilegesTitle"
defaultMessage="Kibana privileges"
/>
);
const description = (
<p>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.specifyPrivilegeForRoleDescription"
defaultMessage="Specifies the Kibana privilege for this role."
/>
</p>
);
return (
<Fragment>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiText size="s" color="subdued">
{description}
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label={title}>
{reservedPrivileges.length > 0 ? (
<EuiComboBox
fullWidth
selectedOptions={reservedPrivileges.map((rp) => ({ label: rp }))}
isDisabled
/>
) : (
<EuiSuperSelect
disabled={!this.props.editable}
onChange={this.onKibanaPrivilegeChange}
options={[
{
value: NO_PRIVILEGE_VALUE,
inputDisplay: (
<EuiText>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeInput"
defaultMessage="None"
/>
</EuiText>
),
dropdownDisplay: (
<EuiText>
<strong>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeDropdown"
defaultMessage="None"
/>
</strong>
<p>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeDropdownDescription"
defaultMessage="No access to Kibana"
/>
</p>
</EuiText>
),
},
{
value: CUSTOM_PRIVILEGE_VALUE,
inputDisplay: (
<EuiText>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeInput"
defaultMessage="Custom"
/>
</EuiText>
),
dropdownDisplay: (
<EuiText>
<strong>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeDropdown"
defaultMessage="Custom"
/>
</strong>
<p>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeDropdownDescription"
defaultMessage="Customize access to Kibana"
/>
</p>
</EuiText>
),
},
{
value: 'read',
inputDisplay: (
<EuiText>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeInput"
defaultMessage="Read"
/>
</EuiText>
),
dropdownDisplay: (
<EuiText>
<strong>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeDropdown"
defaultMessage="Read"
/>
</strong>
<p>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeDropdownDescription"
defaultMessage="Grants read-only access to all of Kibana"
/>
</p>
</EuiText>
),
},
{
value: 'all',
inputDisplay: (
<EuiText>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeInput"
defaultMessage="All"
/>
</EuiText>
),
dropdownDisplay: (
<EuiText>
<strong>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeDropdown"
defaultMessage="All"
/>
</strong>
<p>
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeDropdownDescription"
defaultMessage="Grants full access to all of Kibana"
/>
</p>
</EuiText>
),
},
]}
hasDividers
valueOfSelected={kibanaPrivilege}
/>
)}
</EuiFormRow>
{this.state.isCustomizingGlobalPrivilege && (
<EuiFormRow fullWidth>
<FeatureTable
role={this.props.role}
kibanaPrivileges={this.props.kibanaPrivileges}
privilegeCalculator={
new PrivilegeFormCalculator(this.props.kibanaPrivileges, this.props.role)
}
onChange={this.onFeaturePrivilegeChange}
onChangeAll={this.onChangeAllFeaturePrivileges}
privilegeIndex={this.props.role.kibana.findIndex((k) =>
isGlobalPrivilegeDefinition(k)
)}
canCustomizeSubFeaturePrivileges={this.props.canCustomizeSubFeaturePrivileges}
/>
</EuiFormRow>
)}
{this.maybeRenderSpacePrivilegeWarning()}
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
);
}
public getDisplayedBasePrivilege = () => {
if (this.state.isCustomizingGlobalPrivilege) {
return CUSTOM_PRIVILEGE_VALUE;
}
const { role } = this.props;
const form = this.locateGlobalPrivilege(role);
return form && form.base.length > 0 ? form.base[0] : NO_PRIVILEGE_VALUE;
};
public onKibanaPrivilegeChange = (privilege: string) => {
const role = copyRole(this.props.role);
const form = this.locateGlobalPrivilege(role) || this.createGlobalPrivilegeEntry(role);
if (privilege === NO_PRIVILEGE_VALUE) {
// Remove global entry if no privilege value
role.kibana = role.kibana.filter((entry) => !isGlobalPrivilegeDefinition(entry));
} else if (privilege === CUSTOM_PRIVILEGE_VALUE) {
// Remove base privilege if customizing feature privileges
form.base = [];
} else {
form.base = [privilege];
form.feature = {};
}
this.props.onChange(role);
this.setState({
isCustomizingGlobalPrivilege: privilege === CUSTOM_PRIVILEGE_VALUE,
globalPrivsIndex: role.kibana.indexOf(form),
});
};
public onFeaturePrivilegeChange = (featureId: string, privileges: string[]) => {
const role = copyRole(this.props.role);
const form = this.locateGlobalPrivilege(role) || this.createGlobalPrivilegeEntry(role);
if (privileges.length > 0) {
form.feature[featureId] = [...privileges];
} else {
delete form.feature[featureId];
}
this.props.onChange(role);
};
private onChangeAllFeaturePrivileges = (privileges: string[]) => {
const role = copyRole(this.props.role);
const form = this.locateGlobalPrivilege(role) || this.createGlobalPrivilegeEntry(role);
if (privileges.length > 0) {
this.props.kibanaPrivileges.getSecuredFeatures().forEach((feature) => {
form.feature[feature.id] = [...privileges];
});
} else {
form.feature = {};
}
this.props.onChange(role);
};
private maybeRenderSpacePrivilegeWarning = () => {
const kibanaPrivileges = this.props.role.kibana;
const hasSpacePrivileges = kibanaPrivileges.some(
(privilege) => !isGlobalPrivilegeDefinition(privilege)
);
if (hasSpacePrivileges) {
return (
<EuiFormRow fullWidth>
<UnsupportedSpacePrivilegesWarning />
</EuiFormRow>
);
}
return null;
};
private locateGlobalPrivilegeIndex = (role: Role) => {
return role.kibana.findIndex((privileges) => isGlobalPrivilegeDefinition(privileges));
};
private locateGlobalPrivilege = (role: Role) => {
const spacePrivileges = role.kibana;
return spacePrivileges.find((privileges) => isGlobalPrivilegeDefinition(privileges));
};
private createGlobalPrivilegeEntry(role: Role): RoleKibanaPrivilege {
const newEntry = {
spaces: ['*'],
base: [],
feature: {},
};
role.kibana.push(newEntry);
return newEntry;
}
}

View file

@ -1,26 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiCallOut } from '@elastic/eui';
import React, { Component } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
export class UnsupportedSpacePrivilegesWarning extends Component<{}, {}> {
public render() {
return <EuiCallOut iconType="alert" color="warning" title={this.getMessage()} />;
}
private getMessage = () => {
return (
<FormattedMessage
id="xpack.security.management.editRole.simplePrivilegeForm.unsupportedSpacePrivilegesWarning"
defaultMessage="This role contains privilege definitions for spaces, but spaces are not enabled in Kibana. Saving this role will remove these privileges."
/>
);
};
}

View file

@ -11,487 +11,252 @@ import type { Role } from '../../../common/model';
import { RolesAPIClient } from './roles_api_client';
describe('RolesAPIClient', () => {
async function saveRole(role: Role, spacesEnabled: boolean) {
async function saveRole(role: Role) {
const httpMock = httpServiceMock.createStartContract();
const rolesAPIClient = new RolesAPIClient(httpMock);
await rolesAPIClient.saveRole({ role, spacesEnabled });
await rolesAPIClient.saveRole({ role });
expect(httpMock.put).toHaveBeenCalledTimes(1);
return JSON.parse((httpMock.put.mock.calls[0] as any)[1]?.body as any);
}
describe('spaces disabled', () => {
it('removes placeholder index privileges', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: [], privileges: [] }],
run_as: [],
},
kibana: [],
};
it('removes placeholder index privileges', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: [], privileges: [] }],
run_as: [],
},
kibana: [],
};
const result = await saveRole(role, false);
const result = await saveRole(role);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
});
});
it('removes placeholder query entries', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: '' }],
run_as: [],
},
kibana: [],
};
const result = await saveRole(role, false);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'] }],
run_as: [],
},
kibana: [],
});
});
it('removes transient fields not required for save', async () => {
const role: Role = {
name: 'my role',
transient_metadata: {
foo: 'bar',
},
_transform_error: ['kibana'],
metadata: {
someOtherMetadata: true,
},
_unrecognized_applications: ['foo'],
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
};
const result = await saveRole(role, false);
expect(result).toEqual({
metadata: {
someOtherMetadata: true,
},
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
});
});
it('does not remove actual query entries', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: 'something' }],
run_as: [],
},
kibana: [],
};
const result = await saveRole(role, false);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: 'something' }],
run_as: [],
},
kibana: [],
});
});
it('should remove feature privileges if a corresponding base privilege is defined', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: ['all'],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
],
};
const result = await saveRole(role, false);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: ['all'],
feature: {},
},
],
});
});
it('should not remove feature privileges if a corresponding base privilege is not defined', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
],
};
const result = await saveRole(role, false);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
],
});
});
it('should remove space privileges', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
{
spaces: ['marketing'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
],
};
const result = await saveRole(role, false);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
],
});
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
});
});
describe('spaces enabled', () => {
it('removes placeholder index privileges', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: [], privileges: [] }],
run_as: [],
},
kibana: [],
};
it('removes placeholder query entries', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: '' }],
run_as: [],
},
kibana: [],
};
const result = await saveRole(role, true);
const result = await saveRole(role);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
});
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'] }],
run_as: [],
},
kibana: [],
});
});
it('removes placeholder query entries', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: '' }],
run_as: [],
},
kibana: [],
};
it('removes transient fields not required for save', async () => {
const role: Role = {
name: 'my role',
transient_metadata: {
foo: 'bar',
},
_transform_error: ['kibana'],
metadata: {
someOtherMetadata: true,
},
_unrecognized_applications: ['foo'],
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
};
const result = await saveRole(role, true);
const result = await saveRole(role);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'] }],
run_as: [],
},
kibana: [],
});
expect(result).toEqual({
metadata: {
someOtherMetadata: true,
},
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
});
});
it('removes transient fields not required for save', async () => {
const role: Role = {
name: 'my role',
transient_metadata: {
foo: 'bar',
},
_transform_error: ['kibana'],
metadata: {
someOtherMetadata: true,
},
_unrecognized_applications: ['foo'],
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
};
it('does not remove actual query entries', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: 'something' }],
run_as: [],
},
kibana: [],
};
const result = await saveRole(role, true);
const result = await saveRole(role);
expect(result).toEqual({
metadata: {
someOtherMetadata: true,
},
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [],
});
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: 'something' }],
run_as: [],
},
kibana: [],
});
});
it('does not remove actual query entries', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: 'something' }],
run_as: [],
it('should remove feature privileges if a corresponding base privilege is defined', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['foo'],
base: ['all'],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
kibana: [],
};
],
};
const result = await saveRole(role, true);
const result = await saveRole(role);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [{ names: ['.kibana*'], privileges: ['all'], query: 'something' }],
run_as: [],
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['foo'],
base: ['all'],
feature: {},
},
kibana: [],
});
],
});
});
it('should remove feature privileges if a corresponding base privilege is defined', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['foo'],
base: ['all'],
feature: {
feature1: ['read'],
feature2: ['write'],
},
it('should not remove feature privileges if a corresponding base privilege is not defined', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['foo'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
],
};
const result = await saveRole(role, true);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['foo'],
base: ['all'],
feature: {},
],
};
const result = await saveRole(role);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['foo'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
],
});
},
],
});
});
it('should not remove feature privileges if a corresponding base privilege is not defined', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
it('should not remove space privileges', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
kibana: [
{
spaces: ['foo'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
{
spaces: ['marketing'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
],
};
const result = await saveRole(role, true);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['foo'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
],
});
});
],
};
it('should not remove space privileges', async () => {
const role: Role = {
name: 'my role',
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
const result = await saveRole(role);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
{
spaces: ['marketing'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
{
spaces: ['marketing'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
],
};
const result = await saveRole(role, true);
expect(result).toEqual({
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
kibana: [
{
spaces: ['*'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
{
spaces: ['marketing'],
base: [],
feature: {
feature1: ['read'],
feature2: ['write'],
},
},
],
});
],
});
});
});

View file

@ -9,7 +9,6 @@ import type { HttpStart } from 'src/core/public';
import type { Role, RoleIndexPrivilege } from '../../../common/model';
import { copyRole } from '../../../common/model';
import { isGlobalPrivilegeDefinition } from './edit_role/privilege_utils';
export class RolesAPIClient {
constructor(private readonly http: HttpStart) {}
@ -26,13 +25,13 @@ export class RolesAPIClient {
await this.http.delete(`/api/security/role/${encodeURIComponent(roleName)}`);
}
public async saveRole({ role, spacesEnabled }: { role: Role; spacesEnabled: boolean }) {
public async saveRole({ role }: { role: Role }) {
await this.http.put(`/api/security/role/${encodeURIComponent(role.name)}`, {
body: JSON.stringify(this.transformRoleForSave(copyRole(role), spacesEnabled)),
body: JSON.stringify(this.transformRoleForSave(copyRole(role))),
});
}
private transformRoleForSave(role: Role, spacesEnabled: boolean) {
private transformRoleForSave(role: Role) {
// Remove any placeholder index privileges
const isPlaceholderPrivilege = (indexPrivilege: RoleIndexPrivilege) =>
indexPrivilege.names.length === 0;
@ -43,11 +42,6 @@ export class RolesAPIClient {
// Remove any placeholder query entries
role.elasticsearch.indices.forEach((index) => index.query || delete index.query);
// If spaces are disabled, then do not persist any space privileges
if (!spacesEnabled) {
role.kibana = role.kibana.filter(isGlobalPrivilegeDefinition);
}
role.kibana.forEach((kibanaPrivilege) => {
// If a base privilege is defined, then do not persist feature privileges
if (kibanaPrivilege.base.length > 0) {

View file

@ -1,71 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { applyDeprecations, configDeprecationFactory } from '@kbn/config';
import { deepFreeze } from '@kbn/std';
import { configDeprecationsMock } from '../../../../src/core/server/mocks';
import { spacesConfigDeprecationProvider } from './config';
const deprecationContext = configDeprecationsMock.createContext();
const applyConfigDeprecations = (settings: Record<string, any> = {}) => {
const deprecations = spacesConfigDeprecationProvider(configDeprecationFactory);
const deprecationMessages: string[] = [];
const { config: migrated } = applyDeprecations(
settings,
deprecations.map((deprecation) => ({
deprecation,
path: '',
context: deprecationContext,
})),
() =>
({ message }) =>
deprecationMessages.push(message)
);
return {
messages: deprecationMessages,
migrated,
};
};
describe('spaces config', () => {
describe('deprecations', () => {
describe('enabled', () => {
it('logs a warning if xpack.spaces.enabled is set to false', () => {
const originalConfig = deepFreeze({ xpack: { spaces: { enabled: false } } });
const { messages, migrated } = applyConfigDeprecations({ ...originalConfig });
expect(messages).toMatchInlineSnapshot(`
Array [
"Disabling the Spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)",
]
`);
expect(migrated).toEqual(originalConfig);
});
it('does not log a warning if no settings are explicitly set', () => {
const originalConfig = deepFreeze({});
const { messages, migrated } = applyConfigDeprecations({ ...originalConfig });
expect(messages).toMatchInlineSnapshot(`Array []`);
expect(migrated).toEqual(originalConfig);
});
it('does not log a warning if xpack.spaces.enabled is set to true', () => {
const originalConfig = deepFreeze({ xpack: { spaces: { enabled: true } } });
const { messages, migrated } = applyConfigDeprecations({ ...originalConfig });
expect(messages).toMatchInlineSnapshot(`Array []`);
expect(migrated).toEqual(originalConfig);
});
});
});
});

View file

@ -9,37 +9,15 @@ import type { Observable } from 'rxjs';
import type { TypeOf } from '@kbn/config-schema';
import { schema } from '@kbn/config-schema';
import type {
ConfigDeprecation,
ConfigDeprecationProvider,
PluginInitializerContext,
} from 'src/core/server';
import type { PluginInitializerContext } from 'src/core/server';
export const ConfigSchema = schema.object({
enabled: schema.boolean({ defaultValue: true }),
maxSpaces: schema.number({ defaultValue: 1000 }),
});
export function createConfig$(context: PluginInitializerContext) {
return context.config.create<TypeOf<typeof ConfigSchema>>();
}
const disabledDeprecation: ConfigDeprecation = (config, fromPath, addDeprecation) => {
if (config.xpack?.spaces?.enabled === false) {
addDeprecation({
configPath: 'xpack.spaces.enabled',
message: `Disabling the Spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)`,
correctiveActions: {
manualSteps: [`Remove "xpack.spaces.enabled: false" from your Kibana configuration`],
},
});
}
};
export const spacesConfigDeprecationProvider: ConfigDeprecationProvider = () => {
return [disabledDeprecation];
};
export type ConfigType = ReturnType<typeof createConfig$> extends Observable<infer P>
? P
: ReturnType<typeof createConfig$>;

View file

@ -7,7 +7,7 @@
import type { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server';
import { ConfigSchema, spacesConfigDeprecationProvider } from './config';
import { ConfigSchema } from './config';
import { SpacesPlugin } from './plugin';
// These exports are part of public Spaces plugin contract, any change in signature of exported
@ -33,7 +33,6 @@ export type {
export const config: PluginConfigDescriptor = {
schema: ConfigSchema,
deprecations: spacesConfigDeprecationProvider,
};
export const plugin = (initializerContext: PluginInitializerContext) =>
new SpacesPlugin(initializerContext);

View file

@ -16,7 +16,7 @@ const createMockDebugLogger = () => {
return jest.fn();
};
const createMockConfig = (mockConfig: ConfigType = { maxSpaces: 1000, enabled: true }) => {
const createMockConfig = (mockConfig: ConfigType = { maxSpaces: 1000 }) => {
return ConfigSchema.validate(mockConfig);
};
@ -75,10 +75,7 @@ describe('#getAll', () => {
mockCallWithRequestRepository.find.mockResolvedValue({
saved_objects: savedObjects,
} as any);
const mockConfig = createMockConfig({
maxSpaces: 1234,
enabled: true,
});
const mockConfig = createMockConfig({ maxSpaces: 1234 });
const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository);
const actualSpaces = await client.getAll();
@ -182,10 +179,7 @@ describe('#create', () => {
total: maxSpaces - 1,
} as any);
const mockConfig = createMockConfig({
maxSpaces,
enabled: true,
});
const mockConfig = createMockConfig({ maxSpaces });
const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository);
@ -211,10 +205,7 @@ describe('#create', () => {
total: maxSpaces,
} as any);
const mockConfig = createMockConfig({
maxSpaces,
enabled: true,
});
const mockConfig = createMockConfig({ maxSpaces });
const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository);

View file

@ -19998,23 +19998,7 @@
"xpack.security.management.editRole.roleNameFormRowTitle": "ロール名",
"xpack.security.management.editRole.roleSuccessfullyDeletedNotificationMessage": "ロールを削除しました",
"xpack.security.management.editRole.roleSuccessfullySavedNotificationMessage": "ロールを保存しました",
"xpack.security.management.editRole.setPrivilegesToKibanaDescription": "Elasticsearch データの権限を設定し、Kibana へのアクセスを管理します。",
"xpack.security.management.editRole.setPrivilegesToKibanaSpacesDescription": "Elasticsearch データの権限を設定し、Kibana スペースへのアクセスを管理します。",
"xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeDropdown": "すべて",
"xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeDropdownDescription": "Kibana 全体への完全アクセスを許可します",
"xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeInput": "すべて",
"xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeDropdown": "カスタム",
"xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeDropdownDescription": "Kibana へのアクセスをカスタマイズします",
"xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeInput": "カスタム",
"xpack.security.management.editRole.simplePrivilegeForm.kibanaPrivilegesTitle": "Kibanaの権限",
"xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeDropdown": "なし",
"xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeDropdownDescription": "Kibana へのアクセス不可",
"xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeInput": "なし",
"xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeDropdown": "読み取り",
"xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeDropdownDescription": "Kibana 全体への読み込み専用アクセスを許可します",
"xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeInput": "読み取り",
"xpack.security.management.editRole.simplePrivilegeForm.specifyPrivilegeForRoleDescription": "このロールの Kibana の権限を指定します。",
"xpack.security.management.editRole.simplePrivilegeForm.unsupportedSpacePrivilegesWarning": "このロールはスペースへの権限が定義されていますが、Kibana でスペースが有効ではありません。このロールを保存するとこれらの権限が削除されます。",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.ensureAccountHasAllPrivilegesGrantedDescription": "{kibanaAdmin}ロールによりアカウントにすべての権限が提供されていることを確認し、再試行してください。",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.globalSpacesName": "*すべてのスペース",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.howToViewAllAvailableSpacesDescription": "利用可能なすべてのスペースを表示する権限がありません。",

View file

@ -20295,23 +20295,7 @@
"xpack.security.management.editRole.roleNameFormRowTitle": "角色名称",
"xpack.security.management.editRole.roleSuccessfullyDeletedNotificationMessage": "已删除角色",
"xpack.security.management.editRole.roleSuccessfullySavedNotificationMessage": "保存的角色",
"xpack.security.management.editRole.setPrivilegesToKibanaDescription": "设置 Elasticsearch 数据的权限并控制对 Kibana 的访问权限。",
"xpack.security.management.editRole.setPrivilegesToKibanaSpacesDescription": "设置 Elasticsearch 数据的权限并控制对 Kibana 空间的访问权限。",
"xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeDropdown": "全部",
"xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeDropdownDescription": "授予对 Kibana 全部功能的完全权限",
"xpack.security.management.editRole.simplePrivilegeForm.allPrivilegeInput": "全部",
"xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeDropdown": "定制",
"xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeDropdownDescription": "定制对 Kibana 的访问权限",
"xpack.security.management.editRole.simplePrivilegeForm.customPrivilegeInput": "定制",
"xpack.security.management.editRole.simplePrivilegeForm.kibanaPrivilegesTitle": "Kibana 权限",
"xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeDropdown": "无",
"xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeDropdownDescription": "没有对 Kibana 的访问权限",
"xpack.security.management.editRole.simplePrivilegeForm.noPrivilegeInput": "无",
"xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeDropdown": "读取",
"xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeDropdownDescription": "授予对 Kibana 全部功能的只读权限。",
"xpack.security.management.editRole.simplePrivilegeForm.readPrivilegeInput": "读取",
"xpack.security.management.editRole.simplePrivilegeForm.specifyPrivilegeForRoleDescription": "为此角色指定 Kibana 权限。",
"xpack.security.management.editRole.simplePrivilegeForm.unsupportedSpacePrivilegesWarning": "此角色包含工作区的权限定义,但在 Kibana 中未启用工作区。保存此角色将会移除这些权限。",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.ensureAccountHasAllPrivilegesGrantedDescription": "请确保您的帐户具有 {kibanaAdmin} 角色授予的所有权限,然后重试。",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.globalSpacesName": "* 所有工作区",
"xpack.security.management.editRole.spaceAwarePrivilegeForm.howToViewAllAvailableSpacesDescription": "您无权查看所有可用工作区。",

View file

@ -44,8 +44,7 @@ export const kibanaDeprecations: DomainDeprecationDetails[] = [
correctiveActions: { manualSteps: ['test-step'] },
domainId: 'xpack.spaces',
level: 'critical',
message:
'Disabling the Spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)',
message: 'Sample warning deprecation',
},
{
title: 'mock-deprecation-title',

View file

@ -36,7 +36,6 @@ const onlyNotInCoverageTests = [
require.resolve('../test/case_api_integration/security_and_spaces/config_basic.ts'),
require.resolve('../test/case_api_integration/security_and_spaces/config_trial.ts'),
require.resolve('../test/case_api_integration/spaces_only/config.ts'),
require.resolve('../test/case_api_integration/security_only/config.ts'),
require.resolve('../test/apm_api_integration/basic/config.ts'),
require.resolve('../test/apm_api_integration/trial/config.ts'),
require.resolve('../test/apm_api_integration/rules/config.ts'),
@ -71,11 +70,8 @@ const onlyNotInCoverageTests = [
require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic.ts'),
require.resolve('../test/saved_object_api_integration/security_and_spaces/config_trial.ts'),
require.resolve('../test/saved_object_api_integration/security_and_spaces/config_basic.ts'),
require.resolve('../test/saved_object_api_integration/security_only/config_trial.ts'),
require.resolve('../test/saved_object_api_integration/security_only/config_basic.ts'),
require.resolve('../test/saved_object_api_integration/spaces_only/config.ts'),
require.resolve('../test/ui_capabilities/security_and_spaces/config.ts'),
require.resolve('../test/ui_capabilities/security_only/config.ts'),
require.resolve('../test/ui_capabilities/spaces_only/config.ts'),
require.resolve('../test/upgrade_assistant_integration/config.js'),
require.resolve('../test/licensing_plugin/config.ts'),

View file

@ -276,17 +276,3 @@ export const observabilityOnlyReadSpacesAll: Role = {
],
},
};
/**
* These roles are specifically for the security_only tests where the spaces plugin is disabled. Most of the roles (except
* for noKibanaPrivileges) have spaces: ['*'] effectively giving it access to the default space since no other spaces
* will exist when the spaces plugin is disabled.
*/
export const rolesDefaultSpace = [
noKibanaPrivileges,
globalRead,
securitySolutionOnlyAllSpacesAll,
securitySolutionOnlyReadSpacesAll,
observabilityOnlyAllSpacesAll,
observabilityOnlyReadSpacesAll,
];

View file

@ -132,18 +132,3 @@ export const obsSecReadSpacesAll: User = {
password: 'obs_sec_read',
roles: [securitySolutionOnlyReadSpacesAll.name, observabilityOnlyReadSpacesAll.name],
};
/**
* These users are for the security_only tests because most of them have access to the default space instead of 'space1'
*/
export const usersDefaultSpace = [
superUser,
secOnlySpacesAll,
secOnlyReadSpacesAll,
obsOnlySpacesAll,
obsOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
globalRead,
noKibanaPrivileges,
];

View file

@ -1,16 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('security_only', {
disabledPlugins: ['spaces'],
license: 'trial',
ssl: true,
testFiles: [require.resolve('./tests/trial')],
});

View file

@ -1,242 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { getPostCaseRequest, postCommentAlertReq } from '../../../../common/lib/mock';
import {
createCase,
createComment,
getCasesByAlert,
deleteAllCaseItems,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
secOnlyReadSpacesAll,
superUser,
} from '../../../../common/lib/authentication/users';
import {
obsOnlyDefaultSpaceAuth,
secOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
obsSecDefaultSpaceAuth,
} from '../../../utils';
import { validateCasesFromAlertIDResponse } from '../../../../common/lib/validation';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
describe('get_cases using alertID', () => {
afterEach(async () => {
await deleteAllCaseItems(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
it('should return the correct cases info', async () => {
const [case1, case2, case3] = await Promise.all([
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, secOnlyDefaultSpaceAuth),
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, secOnlyDefaultSpaceAuth),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
),
]);
await Promise.all([
createComment({
supertest: supertestWithoutAuth,
caseId: case1.id,
params: postCommentAlertReq,
auth: secOnlyDefaultSpaceAuth,
}),
createComment({
supertest: supertestWithoutAuth,
caseId: case2.id,
params: postCommentAlertReq,
auth: secOnlyDefaultSpaceAuth,
}),
createComment({
supertest: supertestWithoutAuth,
caseId: case3.id,
params: { ...postCommentAlertReq, owner: 'observabilityFixture' },
auth: obsOnlyDefaultSpaceAuth,
}),
]);
for (const scenario of [
{
user: globalRead,
cases: [case1, case2, case3],
},
{
user: superUser,
cases: [case1, case2, case3],
},
{ user: secOnlyReadSpacesAll, cases: [case1, case2] },
{ user: obsOnlyReadSpacesAll, cases: [case3] },
{
user: obsSecReadSpacesAll,
cases: [case1, case2, case3],
},
]) {
const cases = await getCasesByAlert({
supertest: supertestWithoutAuth,
// cast because the official type is string | string[] but the ids will always be a single value in the tests
alertID: postCommentAlertReq.alertId as string,
auth: {
user: scenario.user,
space: null,
},
});
expect(cases.length).to.eql(scenario.cases.length);
validateCasesFromAlertIDResponse(cases, scenario.cases);
}
});
it(`User ${
noKibanaPrivileges.username
} with role(s) ${noKibanaPrivileges.roles.join()} - should not get cases`, async () => {
const caseInfo = await createCase(supertest, getPostCaseRequest(), 200, {
user: superUser,
space: null,
});
await createComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
params: postCommentAlertReq,
auth: superUserDefaultSpaceAuth,
});
await getCasesByAlert({
supertest: supertestWithoutAuth,
alertID: postCommentAlertReq.alertId as string,
auth: { user: noKibanaPrivileges, space: null },
expectedHttpCode: 403,
});
});
it('should return a 404 when attempting to access a space', async () => {
const [case1, case2] = await Promise.all([
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, obsSecDefaultSpaceAuth),
createCase(
supertestWithoutAuth,
{ ...getPostCaseRequest(), owner: 'observabilityFixture' },
200,
obsSecDefaultSpaceAuth
),
]);
await Promise.all([
createComment({
supertest: supertestWithoutAuth,
caseId: case1.id,
params: postCommentAlertReq,
auth: obsSecDefaultSpaceAuth,
}),
createComment({
supertest: supertestWithoutAuth,
caseId: case2.id,
params: { ...postCommentAlertReq, owner: 'observabilityFixture' },
auth: obsSecDefaultSpaceAuth,
}),
]);
await getCasesByAlert({
supertest: supertestWithoutAuth,
alertID: postCommentAlertReq.alertId as string,
auth: { user: obsSecSpacesAll, space: 'space1' },
query: { owner: 'securitySolutionFixture' },
expectedHttpCode: 404,
});
});
it('should respect the owner filter when have permissions', async () => {
const [case1, case2] = await Promise.all([
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, obsSecDefaultSpaceAuth),
createCase(
supertestWithoutAuth,
{ ...getPostCaseRequest(), owner: 'observabilityFixture' },
200,
obsSecDefaultSpaceAuth
),
]);
await Promise.all([
createComment({
supertest: supertestWithoutAuth,
caseId: case1.id,
params: postCommentAlertReq,
auth: obsSecDefaultSpaceAuth,
}),
createComment({
supertest: supertestWithoutAuth,
caseId: case2.id,
params: { ...postCommentAlertReq, owner: 'observabilityFixture' },
auth: obsSecDefaultSpaceAuth,
}),
]);
const cases = await getCasesByAlert({
supertest: supertestWithoutAuth,
alertID: postCommentAlertReq.alertId as string,
auth: obsSecDefaultSpaceAuth,
query: { owner: 'securitySolutionFixture' },
});
expect(cases).to.eql([{ id: case1.id, title: case1.title }]);
});
it('should return the correct cases info when the owner query parameter contains unprivileged values', async () => {
const [case1, case2] = await Promise.all([
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, obsSecDefaultSpaceAuth),
createCase(
supertestWithoutAuth,
{ ...getPostCaseRequest(), owner: 'observabilityFixture' },
200,
obsSecDefaultSpaceAuth
),
]);
await Promise.all([
createComment({
supertest: supertestWithoutAuth,
caseId: case1.id,
params: postCommentAlertReq,
auth: obsSecDefaultSpaceAuth,
}),
createComment({
supertest: supertestWithoutAuth,
caseId: case2.id,
params: { ...postCommentAlertReq, owner: 'observabilityFixture' },
auth: obsSecDefaultSpaceAuth,
}),
]);
const cases = await getCasesByAlert({
supertest: supertestWithoutAuth,
alertID: postCommentAlertReq.alertId as string,
auth: secOnlyDefaultSpaceAuth,
// The secOnlyDefaultSpace user does not have permissions for observability cases, so it should only return the security solution one
query: { owner: ['securitySolutionFixture', 'observabilityFixture'] },
});
expect(cases).to.eql([{ id: case1.id, title: case1.title }]);
});
});
};

View file

@ -1,157 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { getPostCaseRequest } from '../../../../common/lib/mock';
import {
deleteCasesByESQuery,
deleteCasesUserActions,
deleteComments,
createCase,
deleteCases,
getCase,
} from '../../../../common/lib/utils';
import {
secOnlySpacesAll,
secOnlyReadSpacesAll,
globalRead,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
} from '../../../../common/lib/authentication/users';
import {
obsOnlyDefaultSpaceAuth,
secOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
} from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const es = getService('es');
describe('delete_cases', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
await deleteComments(es);
await deleteCasesUserActions(es);
});
it('User: security solution only - should delete a case', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
secOnlyDefaultSpaceAuth
);
await deleteCases({
supertest: supertestWithoutAuth,
caseIDs: [postedCase.id],
expectedHttpCode: 204,
auth: secOnlyDefaultSpaceAuth,
});
});
it('User: security solution only - should NOT delete a case of different owner', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
secOnlyDefaultSpaceAuth
);
await deleteCases({
supertest: supertestWithoutAuth,
caseIDs: [postedCase.id],
expectedHttpCode: 403,
auth: obsOnlyDefaultSpaceAuth,
});
});
it('should get an error if the user has not permissions to all requested cases', async () => {
const caseSec = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
secOnlyDefaultSpaceAuth
);
const caseObs = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
);
await deleteCases({
supertest: supertestWithoutAuth,
caseIDs: [caseSec.id, caseObs.id],
expectedHttpCode: 403,
auth: obsOnlyDefaultSpaceAuth,
});
// Cases should have not been deleted.
await getCase({
supertest: supertestWithoutAuth,
caseId: caseSec.id,
expectedHttpCode: 200,
auth: superUserDefaultSpaceAuth,
});
await getCase({
supertest: supertestWithoutAuth,
caseId: caseObs.id,
expectedHttpCode: 200,
auth: superUserDefaultSpaceAuth,
});
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should NOT delete a case`, async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await deleteCases({
supertest: supertestWithoutAuth,
caseIDs: [postedCase.id],
expectedHttpCode: 403,
auth: { user, space: null },
});
});
}
it('should return a 404 when attempting to access a space', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await deleteCases({
supertest: supertestWithoutAuth,
caseIDs: [postedCase.id],
expectedHttpCode: 404,
auth: { user: secOnlySpacesAll, space: 'space1' },
});
});
});
};

View file

@ -1,245 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { CASES_URL } from '../../../../../../plugins/cases/common/constants';
import { getPostCaseRequest } from '../../../../common/lib/mock';
import {
deleteAllCaseItems,
ensureSavedObjectIsAuthorized,
findCases,
createCase,
} from '../../../../common/lib/utils';
import {
secOnlySpacesAll,
obsOnlyReadSpacesAll,
secOnlyReadSpacesAll,
noKibanaPrivileges,
superUser,
globalRead,
obsSecReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import {
obsOnlyDefaultSpaceAuth,
obsSecDefaultSpaceAuth,
secOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
} from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
const supertestWithoutAuth = getService('supertestWithoutAuth');
describe('find_cases', () => {
afterEach(async () => {
await deleteAllCaseItems(es);
});
it('should return the correct cases', async () => {
await Promise.all([
// Create case owned by the security solution user
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
200,
secOnlyDefaultSpaceAuth
),
// Create case owned by the observability user
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
),
]);
for (const scenario of [
{
user: globalRead,
numberOfExpectedCases: 2,
owners: ['securitySolutionFixture', 'observabilityFixture'],
},
{
user: superUser,
numberOfExpectedCases: 2,
owners: ['securitySolutionFixture', 'observabilityFixture'],
},
{
user: secOnlyReadSpacesAll,
numberOfExpectedCases: 1,
owners: ['securitySolutionFixture'],
},
{
user: obsOnlyReadSpacesAll,
numberOfExpectedCases: 1,
owners: ['observabilityFixture'],
},
{
user: obsSecReadSpacesAll,
numberOfExpectedCases: 2,
owners: ['securitySolutionFixture', 'observabilityFixture'],
},
]) {
const res = await findCases({
supertest: supertestWithoutAuth,
auth: {
user: scenario.user,
space: null,
},
});
ensureSavedObjectIsAuthorized(res.cases, scenario.numberOfExpectedCases, scenario.owners);
}
});
it(`User ${
noKibanaPrivileges.username
} with role(s) ${noKibanaPrivileges.roles.join()} - should NOT read a case`, async () => {
await createCase(supertestWithoutAuth, getPostCaseRequest(), 200, superUserDefaultSpaceAuth);
await findCases({
supertest: supertestWithoutAuth,
auth: {
user: noKibanaPrivileges,
space: null,
},
expectedHttpCode: 403,
});
});
it('should return a 404 when attempting to access a space', async () => {
await createCase(supertestWithoutAuth, getPostCaseRequest(), 200, superUserDefaultSpaceAuth);
await findCases({
supertest: supertestWithoutAuth,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
it('should return the correct cases when trying to exploit RBAC through the search query parameter', async () => {
await Promise.all([
// super user creates a case with owner securitySolutionFixture
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, superUserDefaultSpaceAuth),
// super user creates a case with owner observabilityFixture
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
superUserDefaultSpaceAuth
),
]);
const res = await findCases({
supertest: supertestWithoutAuth,
query: {
search: 'securitySolutionFixture observabilityFixture',
searchFields: 'owner',
},
auth: secOnlyDefaultSpaceAuth,
});
ensureSavedObjectIsAuthorized(res.cases, 1, ['securitySolutionFixture']);
});
// This test is to prevent a future developer to add the filter attribute without taking into consideration
// the authorizationFilter produced by the cases authorization class
it('should NOT allow to pass a filter query parameter', async () => {
await supertest
.get(
`${CASES_URL}/_find?sortOrder=asc&filter=cases.attributes.owner:"observabilityFixture"`
)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
});
// This test ensures that the user is not allowed to define the namespaces query param
// so she cannot search across spaces
it('should NOT allow to pass a namespaces query parameter', async () => {
await supertest
.get(`${CASES_URL}/_find?sortOrder=asc&namespaces[0]=*`)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
await supertest
.get(`${CASES_URL}/_find?sortOrder=asc&namespaces=*`)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
});
it('should NOT allow to pass a non supported query parameter', async () => {
await supertest
.get(`${CASES_URL}/_find?notExists=papa`)
.set('kbn-xsrf', 'true')
.send()
.expect(400);
});
it('should respect the owner filter when having permissions', async () => {
await Promise.all([
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
200,
obsSecDefaultSpaceAuth
),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
),
]);
const res = await findCases({
supertest: supertestWithoutAuth,
query: {
owner: 'securitySolutionFixture',
searchFields: 'owner',
},
auth: obsSecDefaultSpaceAuth,
});
ensureSavedObjectIsAuthorized(res.cases, 1, ['securitySolutionFixture']);
});
it('should return the correct cases when trying to exploit RBAC through the owner query parameter', async () => {
await Promise.all([
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
200,
obsSecDefaultSpaceAuth
),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsSecDefaultSpaceAuth
),
]);
// User with permissions only to security solution request cases from observability
const res = await findCases({
supertest: supertestWithoutAuth,
query: {
owner: ['securitySolutionFixture', 'observabilityFixture'],
},
auth: secOnlyDefaultSpaceAuth,
});
// Only security solution cases are being returned
ensureSavedObjectIsAuthorized(res.cases, 1, ['securitySolutionFixture']);
});
});
};

View file

@ -1,144 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { AttributesTypeUser } from '../../../../../../plugins/cases/common/api';
import { postCommentUserReq, getPostCaseRequest } from '../../../../common/lib/mock';
import {
deleteCasesByESQuery,
createCase,
getCase,
createComment,
removeServerGeneratedPropertiesFromSavedObject,
} from '../../../../common/lib/utils';
import {
secOnlySpacesAll,
obsOnlySpacesAll,
globalRead,
superUser,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
obsSecSpacesAll,
} from '../../../../common/lib/authentication/users';
import { getUserInfo } from '../../../../common/lib/authentication';
import { secOnlyDefaultSpaceAuth, superUserDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const es = getService('es');
describe('get_case', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
});
it('should get a case', async () => {
const newCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
for (const user of [
globalRead,
superUser,
secOnlySpacesAll,
secOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
]) {
const theCase = await getCase({
supertest: supertestWithoutAuth,
caseId: newCase.id,
auth: { user, space: null },
});
expect(theCase.owner).to.eql('securitySolutionFixture');
}
});
it('should get a case with comments', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
secOnlyDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
expectedHttpCode: 200,
auth: secOnlyDefaultSpaceAuth,
});
const theCase = await getCase({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
includeComments: true,
auth: secOnlyDefaultSpaceAuth,
});
const comment = removeServerGeneratedPropertiesFromSavedObject(
theCase.comments![0] as AttributesTypeUser
);
expect(theCase.comments?.length).to.eql(1);
expect(comment).to.eql({
type: postCommentUserReq.type,
comment: postCommentUserReq.comment,
associationType: 'case',
created_by: getUserInfo(secOnlySpacesAll),
pushed_at: null,
pushed_by: null,
updated_by: null,
owner: 'securitySolutionFixture',
});
});
it('should not get a case when the user does not have access to owner', async () => {
const newCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
for (const user of [noKibanaPrivileges, obsOnlySpacesAll, obsOnlyReadSpacesAll]) {
await getCase({
supertest: supertestWithoutAuth,
caseId: newCase.id,
expectedHttpCode: 403,
auth: { user, space: null },
});
}
});
it('should return a 404 when attempting to access a space', async () => {
const newCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await getCase({
supertest: supertestWithoutAuth,
caseId: newCase.id,
expectedHttpCode: 404,
auth: { user: secOnlySpacesAll, space: 'space1' },
});
});
});
};

View file

@ -1,243 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { getPostCaseRequest, postCaseReq } from '../../../../common/lib/mock';
import {
deleteAllCaseItems,
createCase,
updateCase,
findCases,
getAuthWithSuperUser,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
superUser,
} from '../../../../common/lib/authentication/users';
import {
obsOnlyDefaultSpaceAuth,
secOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
} from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
describe('patch_cases', () => {
afterEach(async () => {
await deleteAllCaseItems(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
it('should update a case when the user has the correct permissions', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
postCaseReq,
200,
secOnlyDefaultSpaceAuth
);
const patchedCases = await updateCase({
supertest: supertestWithoutAuth,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: 'new title',
},
],
},
auth: secOnlyDefaultSpaceAuth,
});
expect(patchedCases[0].owner).to.eql('securitySolutionFixture');
});
it('should update multiple cases when the user has the correct permissions', async () => {
const [case1, case2, case3] = await Promise.all([
createCase(supertestWithoutAuth, postCaseReq, 200, superUserDefaultSpaceAuth),
createCase(supertestWithoutAuth, postCaseReq, 200, superUserDefaultSpaceAuth),
createCase(supertestWithoutAuth, postCaseReq, 200, superUserDefaultSpaceAuth),
]);
const patchedCases = await updateCase({
supertest: supertestWithoutAuth,
params: {
cases: [
{
id: case1.id,
version: case1.version,
title: 'new title',
},
{
id: case2.id,
version: case2.version,
title: 'new title',
},
{
id: case3.id,
version: case3.version,
title: 'new title',
},
],
},
auth: secOnlyDefaultSpaceAuth,
});
expect(patchedCases[0].owner).to.eql('securitySolutionFixture');
expect(patchedCases[1].owner).to.eql('securitySolutionFixture');
expect(patchedCases[2].owner).to.eql('securitySolutionFixture');
});
it('should not update a case when the user does not have the correct ownership', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
);
await updateCase({
supertest: supertestWithoutAuth,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: 'new title',
},
],
},
auth: secOnlyDefaultSpaceAuth,
expectedHttpCode: 403,
});
});
it('should not update any cases when the user does not have the correct ownership', async () => {
const [case1, case2, case3] = await Promise.all([
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
superUserDefaultSpaceAuth
),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
superUserDefaultSpaceAuth
),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
superUserDefaultSpaceAuth
),
]);
await updateCase({
supertest: supertestWithoutAuth,
params: {
cases: [
{
id: case1.id,
version: case1.version,
title: 'new title',
},
{
id: case2.id,
version: case2.version,
title: 'new title',
},
{
id: case3.id,
version: case3.version,
title: 'new title',
},
],
},
auth: secOnlyDefaultSpaceAuth,
expectedHttpCode: 403,
});
const resp = await findCases({ supertest, auth: getAuthWithSuperUser(null) });
expect(resp.cases.length).to.eql(3);
// the update should have failed and none of the title should have been changed
expect(resp.cases[0].title).to.eql(postCaseReq.title);
expect(resp.cases[1].title).to.eql(postCaseReq.title);
expect(resp.cases[2].title).to.eql(postCaseReq.title);
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should NOT update a case`, async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await updateCase({
supertest: supertestWithoutAuth,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: 'new title',
},
],
},
auth: { user, space: null },
expectedHttpCode: 403,
});
});
}
it('should return a 404 when attempting to access a space', async () => {
const postedCase = await createCase(supertestWithoutAuth, getPostCaseRequest(), 200, {
user: superUser,
space: null,
});
await updateCase({
supertest: supertestWithoutAuth,
params: {
cases: [
{
id: postedCase.id,
version: postedCase.version,
title: 'new title',
},
],
},
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,83 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { getPostCaseRequest } from '../../../../common/lib/mock';
import { deleteCasesByESQuery, createCase } from '../../../../common/lib/utils';
import {
secOnlySpacesAll,
secOnlyReadSpacesAll,
globalRead,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
} from '../../../../common/lib/authentication/users';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { secOnlyDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const supertestWithoutAuth = getService('supertestWithoutAuth');
describe('post_case', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
});
it('User: security solution only - should create a case', async () => {
const theCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
200,
secOnlyDefaultSpaceAuth
);
expect(theCase.owner).to.eql('securitySolutionFixture');
});
it('User: security solution only - should NOT create a case of different owner', async () => {
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
403,
secOnlyDefaultSpaceAuth
);
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should NOT create a case`, async () => {
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
403,
{ user, space: null }
);
});
}
it('should return a 404 when attempting to access a space', async () => {
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
404,
{
user: secOnlySpacesAll,
space: 'space1',
}
);
});
});
};

View file

@ -1,162 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { getPostCaseRequest } from '../../../../../common/lib/mock';
import { createCase, deleteCasesByESQuery, getReporters } from '../../../../../common/lib/utils';
import {
secOnlySpacesAll,
obsOnlySpacesAll,
globalRead,
superUser,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
obsSecSpacesAll,
} from '../../../../../common/lib/authentication/users';
import { getUserInfo } from '../../../../../common/lib/authentication';
import {
secOnlyDefaultSpaceAuth,
obsOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
obsSecDefaultSpaceAuth,
} from '../../../../utils';
import { UserInfo } from '../../../../../common/lib/authentication/types';
const sortReporters = (reporters: UserInfo[]) =>
reporters.sort((a, b) => a.username.localeCompare(b.username));
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const es = getService('es');
describe('get_reporters', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
});
it('User: security solution only - should read the correct reporters', async () => {
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
200,
secOnlyDefaultSpaceAuth
);
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
);
for (const scenario of [
{
user: globalRead,
expectedReporters: [getUserInfo(secOnlySpacesAll), getUserInfo(obsOnlySpacesAll)],
},
{
user: superUser,
expectedReporters: [getUserInfo(secOnlySpacesAll), getUserInfo(obsOnlySpacesAll)],
},
{ user: secOnlyReadSpacesAll, expectedReporters: [getUserInfo(secOnlySpacesAll)] },
{ user: obsOnlyReadSpacesAll, expectedReporters: [getUserInfo(obsOnlySpacesAll)] },
{
user: obsSecReadSpacesAll,
expectedReporters: [getUserInfo(secOnlySpacesAll), getUserInfo(obsOnlySpacesAll)],
},
]) {
const reporters = await getReporters({
supertest: supertestWithoutAuth,
expectedHttpCode: 200,
auth: {
user: scenario.user,
space: null,
},
});
// sort reporters to prevent order failure
expect(sortReporters(reporters as unknown as UserInfo[])).to.eql(
sortReporters(scenario.expectedReporters)
);
}
});
it(`User ${
noKibanaPrivileges.username
} with role(s) ${noKibanaPrivileges.roles.join()} - should NOT get all reporters`, async () => {
// super user creates a case at the appropriate space
await createCase(supertestWithoutAuth, getPostCaseRequest(), 200, superUserDefaultSpaceAuth);
// user should not be able to get all reporters at the appropriate space
await getReporters({
supertest: supertestWithoutAuth,
expectedHttpCode: 403,
auth: { user: noKibanaPrivileges, space: null },
});
});
it('should return a 404 when attempting to access a space', async () => {
await createCase(supertestWithoutAuth, getPostCaseRequest(), 200, {
user: superUser,
space: null,
});
await getReporters({
supertest: supertestWithoutAuth,
expectedHttpCode: 404,
auth: { user: obsSecSpacesAll, space: 'space1' },
});
});
it('should respect the owner filter when having permissions', async () => {
await Promise.all([
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, secOnlyDefaultSpaceAuth),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
),
]);
const reporters = await getReporters({
supertest: supertestWithoutAuth,
auth: obsSecDefaultSpaceAuth,
query: { owner: 'securitySolutionFixture' },
});
expect(reporters).to.eql([getUserInfo(secOnlySpacesAll)]);
});
it('should return the correct cases when trying to exploit RBAC through the owner query parameter', async () => {
await Promise.all([
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, secOnlyDefaultSpaceAuth),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
),
]);
// User with permissions only to security solution request reporters from observability
const reporters = await getReporters({
supertest: supertestWithoutAuth,
auth: secOnlyDefaultSpaceAuth,
query: { owner: ['securitySolutionFixture', 'observabilityFixture'] },
});
// Only security solution reporters are being returned
expect(reporters).to.eql([getUserInfo(secOnlySpacesAll)]);
});
});
};

View file

@ -1,144 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { CaseStatuses } from '../../../../../../../plugins/cases/common/api';
import { getPostCaseRequest } from '../../../../../common/lib/mock';
import {
createCase,
updateCase,
getAllCasesStatuses,
deleteAllCaseItems,
} from '../../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
superUser,
} from '../../../../../common/lib/authentication/users';
import { superUserDefaultSpaceAuth } from '../../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
describe('get_status', () => {
afterEach(async () => {
await deleteAllCaseItems(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
it('should return the correct status stats', async () => {
/**
* Owner: Sec
* open: 0, in-prog: 1, closed: 1
* Owner: Obs
* open: 1, in-prog: 1
*/
const [inProgressSec, closedSec, , inProgressObs] = await Promise.all([
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, superUserDefaultSpaceAuth),
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, superUserDefaultSpaceAuth),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
superUserDefaultSpaceAuth
),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
superUserDefaultSpaceAuth
),
]);
await updateCase({
supertest: supertestWithoutAuth,
params: {
cases: [
{
id: inProgressSec.id,
version: inProgressSec.version,
status: CaseStatuses['in-progress'],
},
{
id: closedSec.id,
version: closedSec.version,
status: CaseStatuses.closed,
},
{
id: inProgressObs.id,
version: inProgressObs.version,
status: CaseStatuses['in-progress'],
},
],
},
auth: superUserDefaultSpaceAuth,
});
for (const scenario of [
{ user: globalRead, stats: { open: 1, inProgress: 2, closed: 1 } },
{ user: superUser, stats: { open: 1, inProgress: 2, closed: 1 } },
{ user: secOnlyReadSpacesAll, stats: { open: 0, inProgress: 1, closed: 1 } },
{ user: obsOnlyReadSpacesAll, stats: { open: 1, inProgress: 1, closed: 0 } },
{ user: obsSecReadSpacesAll, stats: { open: 1, inProgress: 2, closed: 1 } },
{
user: obsSecReadSpacesAll,
stats: { open: 1, inProgress: 1, closed: 0 },
owner: 'observabilityFixture',
},
{
user: obsSecReadSpacesAll,
stats: { open: 1, inProgress: 2, closed: 1 },
owner: ['observabilityFixture', 'securitySolutionFixture'],
},
]) {
const statuses = await getAllCasesStatuses({
supertest: supertestWithoutAuth,
auth: { user: scenario.user, space: null },
query: {
owner: scenario.owner,
},
});
expect(statuses).to.eql({
count_open_cases: scenario.stats.open,
count_closed_cases: scenario.stats.closed,
count_in_progress_cases: scenario.stats.inProgress,
});
}
});
it(`should return a 403 when retrieving the statuses when the user ${
noKibanaPrivileges.username
} with role(s) ${noKibanaPrivileges.roles.join()}`, async () => {
await createCase(supertestWithoutAuth, getPostCaseRequest(), 200, superUserDefaultSpaceAuth);
await getAllCasesStatuses({
supertest: supertestWithoutAuth,
auth: { user: noKibanaPrivileges, space: null },
expectedHttpCode: 403,
});
});
it('should return a 404 when attempting to access a space', async () => {
await createCase(supertestWithoutAuth, getPostCaseRequest(), 200, superUserDefaultSpaceAuth);
await getAllCasesStatuses({
supertest: supertestWithoutAuth,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,170 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
import { deleteCasesByESQuery, createCase, getTags } from '../../../../../common/lib/utils';
import { getPostCaseRequest } from '../../../../../common/lib/mock';
import {
secOnlySpacesAll,
globalRead,
superUser,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
} from '../../../../../common/lib/authentication/users';
import {
secOnlyDefaultSpaceAuth,
obsOnlyDefaultSpaceAuth,
obsSecDefaultSpaceAuth,
superUserDefaultSpaceAuth,
} from '../../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const es = getService('es');
describe('get_tags', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
});
it('should read the correct tags', async () => {
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture', tags: ['sec'] }),
200,
secOnlyDefaultSpaceAuth
);
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture', tags: ['obs'] }),
200,
obsOnlyDefaultSpaceAuth
);
for (const scenario of [
{
user: globalRead,
expectedTags: ['sec', 'obs'],
},
{
user: superUser,
expectedTags: ['sec', 'obs'],
},
{ user: secOnlyReadSpacesAll, expectedTags: ['sec'] },
{ user: obsOnlyReadSpacesAll, expectedTags: ['obs'] },
{
user: obsSecReadSpacesAll,
expectedTags: ['sec', 'obs'],
},
]) {
const tags = await getTags({
supertest: supertestWithoutAuth,
expectedHttpCode: 200,
auth: {
user: scenario.user,
space: null,
},
});
expect(tags).to.eql(scenario.expectedTags);
}
});
it(`User ${
noKibanaPrivileges.username
} with role(s) ${noKibanaPrivileges.roles.join()} - should NOT get all tags`, async () => {
// super user creates a case at the appropriate space
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture', tags: ['sec'] }),
200,
superUserDefaultSpaceAuth
);
// user should not be able to get all tags at the appropriate space
await getTags({
supertest: supertestWithoutAuth,
expectedHttpCode: 403,
auth: { user: noKibanaPrivileges, space: null },
});
});
it('should return a 404 when attempting to access a space', async () => {
// super user creates a case at the appropriate space
await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture', tags: ['sec'] }),
200,
superUserDefaultSpaceAuth
);
await getTags({
supertest: supertestWithoutAuth,
expectedHttpCode: 404,
auth: { user: secOnlySpacesAll, space: 'space1' },
});
});
it('should respect the owner filter when having permissions', async () => {
await Promise.all([
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture', tags: ['sec'] }),
200,
obsSecDefaultSpaceAuth
),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture', tags: ['obs'] }),
200,
obsSecDefaultSpaceAuth
),
]);
const tags = await getTags({
supertest: supertestWithoutAuth,
auth: obsSecDefaultSpaceAuth,
query: { owner: 'securitySolutionFixture' },
});
expect(tags).to.eql(['sec']);
});
it('should return the correct cases when trying to exploit RBAC through the owner query parameter', async () => {
await Promise.all([
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture', tags: ['sec'] }),
200,
obsSecDefaultSpaceAuth
),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture', tags: ['obs'] }),
200,
obsSecDefaultSpaceAuth
),
]);
// User with permissions only to security solution request tags from observability
const tags = await getTags({
supertest: supertestWithoutAuth,
auth: secOnlyDefaultSpaceAuth,
query: { owner: ['securitySolutionFixture', 'observabilityFixture'] },
});
// Only security solution tags are being returned
expect(tags).to.eql(['sec']);
});
});
};

View file

@ -1,205 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { getPostCaseRequest, postCommentUserReq } from '../../../../common/lib/mock';
import {
deleteAllCaseItems,
deleteCasesByESQuery,
deleteCasesUserActions,
deleteComments,
createCase,
createComment,
deleteComment,
deleteAllComments,
getAuthWithSuperUser,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import { obsOnlyDefaultSpaceAuth, secOnlyDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
const superUserNoSpaceAuth = getAuthWithSuperUser(null);
describe('delete_comment', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
await deleteComments(es);
await deleteCasesUserActions(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
afterEach(async () => {
await deleteAllCaseItems(es);
});
it('should delete a comment from the appropriate owner', async () => {
const secCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
secOnlyDefaultSpaceAuth
);
const commentResp = await createComment({
supertest: supertestWithoutAuth,
caseId: secCase.id,
params: postCommentUserReq,
auth: secOnlyDefaultSpaceAuth,
});
await deleteComment({
supertest: supertestWithoutAuth,
caseId: secCase.id,
commentId: commentResp.comments![0].id,
auth: secOnlyDefaultSpaceAuth,
});
});
it('should delete multiple comments from the appropriate owner', async () => {
const secCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
secOnlyDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: secCase.id,
params: postCommentUserReq,
auth: secOnlyDefaultSpaceAuth,
});
await createComment({
supertest: supertestWithoutAuth,
caseId: secCase.id,
params: postCommentUserReq,
auth: secOnlyDefaultSpaceAuth,
});
await deleteAllComments({
supertest: supertestWithoutAuth,
caseId: secCase.id,
auth: secOnlyDefaultSpaceAuth,
});
});
it('should not delete a comment from a different owner', async () => {
const secCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
secOnlyDefaultSpaceAuth
);
const commentResp = await createComment({
supertest: supertestWithoutAuth,
caseId: secCase.id,
params: postCommentUserReq,
auth: secOnlyDefaultSpaceAuth,
});
await deleteComment({
supertest: supertestWithoutAuth,
caseId: secCase.id,
commentId: commentResp.comments![0].id,
auth: obsOnlyDefaultSpaceAuth,
expectedHttpCode: 403,
});
await deleteAllComments({
supertest: supertestWithoutAuth,
caseId: secCase.id,
auth: obsOnlyDefaultSpaceAuth,
expectedHttpCode: 403,
});
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should NOT delete a comment`, async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserNoSpaceAuth
);
const commentResp = await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
auth: superUserNoSpaceAuth,
});
await deleteComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
commentId: commentResp.comments![0].id,
auth: { user, space: null },
expectedHttpCode: 403,
});
await deleteAllComments({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
auth: { user, space: null },
expectedHttpCode: 403,
});
});
}
it('should return a 404 when attempting to access a space', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserNoSpaceAuth
);
const commentResp = await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
auth: superUserNoSpaceAuth,
});
await deleteComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
commentId: commentResp.comments![0].id,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
await deleteAllComments({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,278 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { CASES_URL } from '../../../../../../plugins/cases/common/constants';
import { CommentsResponse } from '../../../../../../plugins/cases/common/api';
import {
getPostCaseRequest,
postCommentAlertReq,
postCommentUserReq,
} from '../../../../common/lib/mock';
import {
createComment,
deleteAllCaseItems,
deleteCasesByESQuery,
deleteCasesUserActions,
deleteComments,
ensureSavedObjectIsAuthorized,
getSpaceUrlPrefix,
createCase,
} from '../../../../common/lib/utils';
import {
secOnlySpacesAll,
obsOnlyReadSpacesAll,
secOnlyReadSpacesAll,
noKibanaPrivileges,
superUser,
globalRead,
obsSecReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import {
obsOnlyDefaultSpaceAuth,
secOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
} from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
describe('find_comments', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
await deleteComments(es);
await deleteCasesUserActions(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
afterEach(async () => {
await deleteAllCaseItems(es);
});
it('should return the correct comments', async () => {
const [secCase, obsCase] = await Promise.all([
// Create case owned by the security solution user
createCase(supertestWithoutAuth, getPostCaseRequest(), 200, secOnlyDefaultSpaceAuth),
createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
),
// Create case owned by the observability user
]);
await Promise.all([
createComment({
supertest: supertestWithoutAuth,
caseId: secCase.id,
params: postCommentUserReq,
auth: secOnlyDefaultSpaceAuth,
}),
createComment({
supertest: supertestWithoutAuth,
caseId: obsCase.id,
params: { ...postCommentAlertReq, owner: 'observabilityFixture' },
auth: obsOnlyDefaultSpaceAuth,
}),
]);
for (const scenario of [
{
user: globalRead,
numExpectedEntites: 1,
owners: ['securitySolutionFixture', 'observabilityFixture'],
caseID: secCase.id,
},
{
user: globalRead,
numExpectedEntites: 1,
owners: ['securitySolutionFixture', 'observabilityFixture'],
caseID: obsCase.id,
},
{
user: superUser,
numExpectedEntites: 1,
owners: ['securitySolutionFixture', 'observabilityFixture'],
caseID: secCase.id,
},
{
user: superUser,
numExpectedEntites: 1,
owners: ['securitySolutionFixture', 'observabilityFixture'],
caseID: obsCase.id,
},
{
user: secOnlyReadSpacesAll,
numExpectedEntites: 1,
owners: ['securitySolutionFixture'],
caseID: secCase.id,
},
{
user: obsOnlyReadSpacesAll,
numExpectedEntites: 1,
owners: ['observabilityFixture'],
caseID: obsCase.id,
},
{
user: obsSecReadSpacesAll,
numExpectedEntites: 1,
owners: ['securitySolutionFixture', 'observabilityFixture'],
caseID: secCase.id,
},
{
user: obsSecReadSpacesAll,
numExpectedEntites: 1,
owners: ['securitySolutionFixture', 'observabilityFixture'],
caseID: obsCase.id,
},
]) {
const { body: caseComments }: { body: CommentsResponse } = await supertestWithoutAuth
.get(`${getSpaceUrlPrefix(null)}${CASES_URL}/${scenario.caseID}/comments/_find`)
.auth(scenario.user.username, scenario.user.password)
.expect(200);
ensureSavedObjectIsAuthorized(
caseComments.comments,
scenario.numExpectedEntites,
scenario.owners
);
}
});
it(`User ${
noKibanaPrivileges.username
} with role(s) ${noKibanaPrivileges.roles.join()} - should NOT read a comment`, async () => {
// super user creates a case and comment in the appropriate space
const caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
auth: { user: superUser, space: null },
params: { ...postCommentUserReq, owner: 'securitySolutionFixture' },
caseId: caseInfo.id,
});
// user should not be able to read comments
await supertestWithoutAuth
.get(`${getSpaceUrlPrefix(null)}${CASES_URL}/${caseInfo.id}/comments/_find`)
.auth(noKibanaPrivileges.username, noKibanaPrivileges.password)
.expect(403);
});
it('should return a 404 when attempting to access a space', async () => {
const caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
auth: superUserDefaultSpaceAuth,
params: { ...postCommentUserReq, owner: 'securitySolutionFixture' },
caseId: caseInfo.id,
});
await supertestWithoutAuth
.get(`${getSpaceUrlPrefix('space1')}${CASES_URL}/${caseInfo.id}/comments/_find`)
.auth(secOnlySpacesAll.username, secOnlySpacesAll.password)
.expect(404);
});
it('should not return any comments when trying to exploit RBAC through the search query parameter', async () => {
const obsCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
auth: superUserDefaultSpaceAuth,
params: { ...postCommentUserReq, owner: 'observabilityFixture' },
caseId: obsCase.id,
});
const { body: res }: { body: CommentsResponse } = await supertestWithoutAuth
.get(
`${getSpaceUrlPrefix(null)}${CASES_URL}/${
obsCase.id
}/comments/_find?search=securitySolutionFixture+observabilityFixture`
)
.auth(secOnlySpacesAll.username, secOnlySpacesAll.password)
.expect(200);
// shouldn't find any comments since they were created under the observability ownership
ensureSavedObjectIsAuthorized(res.comments, 0, ['securitySolutionFixture']);
});
it('should not allow retrieving unauthorized comments using the filter field', async () => {
const obsCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
auth: superUserDefaultSpaceAuth,
params: { ...postCommentUserReq, owner: 'observabilityFixture' },
caseId: obsCase.id,
});
const { body: res } = await supertestWithoutAuth
.get(
`${getSpaceUrlPrefix(null)}${CASES_URL}/${
obsCase.id
}/comments/_find?filter=cases-comments.attributes.owner:"observabilityFixture"`
)
.auth(secOnlySpacesAll.username, secOnlySpacesAll.password)
.expect(200);
expect(res.comments.length).to.be(0);
});
// This test ensures that the user is not allowed to define the namespaces query param
// so she cannot search across spaces
it('should NOT allow to pass a namespaces query parameter', async () => {
const obsCase = await createCase(
supertest,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200
);
await createComment({
supertest,
params: { ...postCommentUserReq, owner: 'observabilityFixture' },
caseId: obsCase.id,
});
await supertest.get(`${CASES_URL}/${obsCase.id}/comments/_find?namespaces[0]=*`).expect(400);
await supertest.get(`${CASES_URL}/${obsCase.id}/comments/_find?namespaces=*`).expect(400);
});
it('should NOT allow to pass a non supported query parameter', async () => {
await supertest.get(`${CASES_URL}/id/comments/_find?notExists=papa`).expect(400);
await supertest.get(`${CASES_URL}/id/comments/_find?owner=papa`).expect(400);
});
});
};

View file

@ -1,139 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { getPostCaseRequest, postCommentUserReq } from '../../../../common/lib/mock';
import {
deleteAllCaseItems,
createCase,
createComment,
getAllComments,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlySpacesAll,
obsOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
superUser,
} from '../../../../common/lib/authentication/users';
import { superUserDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
describe('get_all_comments', () => {
afterEach(async () => {
await deleteAllCaseItems(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
it('should get all comments when the user has the correct permissions', async () => {
const caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
await createComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
for (const user of [
globalRead,
superUser,
secOnlySpacesAll,
secOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
]) {
const comments = await getAllComments({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
auth: { user, space: null },
});
expect(comments.length).to.eql(2);
}
});
it('should not get comments when the user does not have correct permission', async () => {
const caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
for (const scenario of [
{ user: noKibanaPrivileges, returnCode: 403 },
{ user: obsOnlySpacesAll, returnCode: 200 },
{ user: obsOnlyReadSpacesAll, returnCode: 200 },
]) {
const comments = await getAllComments({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
auth: { user: scenario.user, space: null },
expectedHttpCode: scenario.returnCode,
});
// only check the length if we get a 200 in response
if (scenario.returnCode === 200) {
expect(comments.length).to.be(0);
}
}
});
it('should return a 404 when attempting to access a space', async () => {
const caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
await getAllComments({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,123 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { postCommentUserReq, getPostCaseRequest } from '../../../../common/lib/mock';
import {
deleteAllCaseItems,
createCase,
createComment,
getComment,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlySpacesAll,
obsOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
superUser,
} from '../../../../common/lib/authentication/users';
import { superUserDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
describe('get_comment', () => {
afterEach(async () => {
await deleteAllCaseItems(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
it('should get a comment when the user has the correct permissions', async () => {
const caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
const caseWithComment = await createComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
for (const user of [
globalRead,
superUser,
secOnlySpacesAll,
secOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
]) {
await getComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
commentId: caseWithComment.comments![0].id,
auth: { user, space: null },
});
}
});
it('should not get comment when the user does not have correct permissions', async () => {
const caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
const caseWithComment = await createComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
for (const user of [noKibanaPrivileges, obsOnlySpacesAll, obsOnlyReadSpacesAll]) {
await getComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
commentId: caseWithComment.comments![0].id,
auth: { user, space: null },
expectedHttpCode: 403,
});
}
});
it('should return a 404 when attempting to access a space', async () => {
const caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
const caseWithComment = await createComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
await getComment({
supertest: supertestWithoutAuth,
caseId: caseInfo.id,
commentId: caseWithComment.comments![0].id,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,189 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { AttributesTypeUser, CommentType } from '../../../../../../plugins/cases/common/api';
import { defaultUser, postCommentUserReq, getPostCaseRequest } from '../../../../common/lib/mock';
import {
deleteAllCaseItems,
deleteCasesByESQuery,
deleteCasesUserActions,
deleteComments,
createCase,
createComment,
updateComment,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import {
obsOnlyDefaultSpaceAuth,
secOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
} from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
describe('patch_comment', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
await deleteComments(es);
await deleteCasesUserActions(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
afterEach(async () => {
await deleteAllCaseItems(es);
});
it('should update a comment that the user has permissions for', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
const patchedCase = await createComment({
supertest,
caseId: postedCase.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
const newComment = 'Well I decided to update my comment. So what? Deal with it.';
const updatedCase = await updateComment({
supertest,
caseId: postedCase.id,
req: {
...postCommentUserReq,
id: patchedCase.comments![0].id,
version: patchedCase.comments![0].version,
comment: newComment,
},
auth: secOnlyDefaultSpaceAuth,
});
const userComment = updatedCase.comments![0] as AttributesTypeUser;
expect(userComment.comment).to.eql(newComment);
expect(userComment.type).to.eql(CommentType.user);
expect(updatedCase.updated_by).to.eql(defaultUser);
expect(userComment.owner).to.eql('securitySolutionFixture');
});
it('should not update a comment that has a different owner thant he user has access to', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
const patchedCase = await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
const newComment = 'Well I decided to update my comment. So what? Deal with it.';
await updateComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
req: {
...postCommentUserReq,
id: patchedCase.comments![0].id,
version: patchedCase.comments![0].version,
comment: newComment,
},
auth: obsOnlyDefaultSpaceAuth,
expectedHttpCode: 403,
});
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should NOT update a comment`, async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
const patchedCase = await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
const newComment = 'Well I decided to update my comment. So what? Deal with it.';
await updateComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
req: {
...postCommentUserReq,
id: patchedCase.comments![0].id,
version: patchedCase.comments![0].version,
comment: newComment,
},
auth: { user, space: null },
expectedHttpCode: 403,
});
});
}
it('should return a 404 when attempting to access a space', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
const patchedCase = await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
auth: superUserDefaultSpaceAuth,
});
const newComment = 'Well I decided to update my comment. So what? Deal with it.';
await updateComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
req: {
...postCommentUserReq,
id: patchedCase.comments![0].id,
version: patchedCase.comments![0].version,
comment: newComment,
},
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,128 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { postCommentUserReq, getPostCaseRequest } from '../../../../common/lib/mock';
import {
deleteAllCaseItems,
deleteCasesByESQuery,
deleteCasesUserActions,
deleteComments,
createCase,
createComment,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import {
obsOnlyDefaultSpaceAuth,
secOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
} from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
describe('post_comment', () => {
afterEach(async () => {
await deleteCasesByESQuery(es);
await deleteComments(es);
await deleteCasesUserActions(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
afterEach(async () => {
await deleteAllCaseItems(es);
});
it('should create a comment when the user has the correct permissions for that owner', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
auth: secOnlyDefaultSpaceAuth,
});
});
it('should not create a comment when the user does not have permissions for that owner', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'observabilityFixture' }),
200,
obsOnlyDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: { ...postCommentUserReq, owner: 'observabilityFixture' },
auth: secOnlyDefaultSpaceAuth,
expectedHttpCode: 403,
});
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should not create a comment`, async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
auth: { user, space: null },
expectedHttpCode: 403,
});
});
}
it('should return a 404 when attempting to access a space', async () => {
const postedCase = await createCase(
supertestWithoutAuth,
getPostCaseRequest({ owner: 'securitySolutionFixture' }),
200,
superUserDefaultSpaceAuth
);
await createComment({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
params: postCommentUserReq,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,195 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
deleteConfiguration,
getConfiguration,
createConfiguration,
getConfigurationRequest,
ensureSavedObjectIsAuthorized,
} from '../../../../common/lib/utils';
import {
secOnlySpacesAll,
obsOnlyReadSpacesAll,
secOnlyReadSpacesAll,
noKibanaPrivileges,
superUser,
globalRead,
obsSecReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import {
obsOnlyDefaultSpaceAuth,
obsSecDefaultSpaceAuth,
secOnlyDefaultSpaceAuth,
superUserDefaultSpaceAuth,
} from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const es = getService('es');
describe('get_configure', () => {
afterEach(async () => {
await deleteConfiguration(es);
});
it('should return the correct configuration', async () => {
await createConfiguration(
supertestWithoutAuth,
getConfigurationRequest(),
200,
secOnlyDefaultSpaceAuth
);
await createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'observabilityFixture' },
200,
obsOnlyDefaultSpaceAuth
);
for (const scenario of [
{
user: globalRead,
numberOfExpectedCases: 2,
owners: ['securitySolutionFixture', 'observabilityFixture'],
},
{
user: superUser,
numberOfExpectedCases: 2,
owners: ['securitySolutionFixture', 'observabilityFixture'],
},
{
user: secOnlyReadSpacesAll,
numberOfExpectedCases: 1,
owners: ['securitySolutionFixture'],
},
{
user: obsOnlyReadSpacesAll,
numberOfExpectedCases: 1,
owners: ['observabilityFixture'],
},
{
user: obsSecReadSpacesAll,
numberOfExpectedCases: 2,
owners: ['securitySolutionFixture', 'observabilityFixture'],
},
]) {
const configuration = await getConfiguration({
supertest: supertestWithoutAuth,
query: { owner: scenario.owners },
expectedHttpCode: 200,
auth: {
user: scenario.user,
space: null,
},
});
ensureSavedObjectIsAuthorized(
configuration,
scenario.numberOfExpectedCases,
scenario.owners
);
}
});
it(`User ${
noKibanaPrivileges.username
} with role(s) ${noKibanaPrivileges.roles.join()} - should NOT read a case configuration`, async () => {
// super user creates a configuration at the appropriate space
await createConfiguration(
supertestWithoutAuth,
getConfigurationRequest(),
200,
superUserDefaultSpaceAuth
);
// user should not be able to read configurations at the appropriate space
await getConfiguration({
supertest: supertestWithoutAuth,
expectedHttpCode: 403,
auth: {
user: noKibanaPrivileges,
space: null,
},
});
});
it('should return a 404 when attempting to access a space', async () => {
await createConfiguration(
supertestWithoutAuth,
getConfigurationRequest(),
200,
superUserDefaultSpaceAuth
);
await getConfiguration({
supertest: supertestWithoutAuth,
expectedHttpCode: 404,
auth: {
user: secOnlySpacesAll,
space: 'space1',
},
});
});
it('should respect the owner filter when having permissions', async () => {
await Promise.all([
createConfiguration(
supertestWithoutAuth,
getConfigurationRequest(),
200,
obsSecDefaultSpaceAuth
),
createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'observabilityFixture' },
200,
obsSecDefaultSpaceAuth
),
]);
const res = await getConfiguration({
supertest: supertestWithoutAuth,
query: { owner: 'securitySolutionFixture' },
auth: obsSecDefaultSpaceAuth,
});
ensureSavedObjectIsAuthorized(res, 1, ['securitySolutionFixture']);
});
it('should return the correct cases when trying to exploit RBAC through the owner query parameter', async () => {
await Promise.all([
createConfiguration(
supertestWithoutAuth,
getConfigurationRequest(),
200,
obsSecDefaultSpaceAuth
),
createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'observabilityFixture' },
200,
obsSecDefaultSpaceAuth
),
]);
// User with permissions only to security solution request cases from observability
const res = await getConfiguration({
supertest: supertestWithoutAuth,
query: { owner: ['securitySolutionFixture', 'observabilityFixture'] },
auth: secOnlyDefaultSpaceAuth,
});
// Only security solution cases are being returned
ensureSavedObjectIsAuthorized(res, 1, ['securitySolutionFixture']);
});
});
};

View file

@ -1,140 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib';
import {
getConfigurationRequest,
deleteConfiguration,
createConfiguration,
updateConfiguration,
} from '../../../../common/lib/utils';
import {
secOnlySpacesAll,
obsOnlyReadSpacesAll,
secOnlyReadSpacesAll,
noKibanaPrivileges,
globalRead,
obsSecReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import { secOnlyDefaultSpaceAuth, superUserDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const es = getService('es');
describe('patch_configure', () => {
const actionsRemover = new ActionsRemover(supertest);
afterEach(async () => {
await deleteConfiguration(es);
await actionsRemover.removeAll();
});
it('User: security solution only - should update a configuration', async () => {
const configuration = await createConfiguration(
supertestWithoutAuth,
getConfigurationRequest(),
200,
secOnlyDefaultSpaceAuth
);
const newConfiguration = await updateConfiguration(
supertestWithoutAuth,
configuration.id,
{
closure_type: 'close-by-pushing',
version: configuration.version,
},
200,
secOnlyDefaultSpaceAuth
);
expect(newConfiguration.owner).to.eql('securitySolutionFixture');
});
it('User: security solution only - should NOT update a configuration of different owner', async () => {
const configuration = await createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'observabilityFixture' },
200,
superUserDefaultSpaceAuth
);
await updateConfiguration(
supertestWithoutAuth,
configuration.id,
{
closure_type: 'close-by-pushing',
version: configuration.version,
},
403,
secOnlyDefaultSpaceAuth
);
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should NOT update a configuration`, async () => {
const configuration = await createConfiguration(
supertestWithoutAuth,
getConfigurationRequest(),
200,
superUserDefaultSpaceAuth
);
await updateConfiguration(
supertestWithoutAuth,
configuration.id,
{
closure_type: 'close-by-pushing',
version: configuration.version,
},
403,
{
user,
space: null,
}
);
});
}
it('should return a 404 when attempting to access a space', async () => {
const configuration = await createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'securitySolutionFixture' },
200,
superUserDefaultSpaceAuth
);
await updateConfiguration(
supertestWithoutAuth,
configuration.id,
{
closure_type: 'close-by-pushing',
version: configuration.version,
},
404,
{
user: secOnlySpacesAll,
space: 'space1',
}
);
});
});
};

View file

@ -1,133 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib';
import {
getConfigurationRequest,
deleteConfiguration,
createConfiguration,
getConfiguration,
ensureSavedObjectIsAuthorized,
} from '../../../../common/lib/utils';
import {
secOnlySpacesAll,
obsOnlyReadSpacesAll,
secOnlyReadSpacesAll,
noKibanaPrivileges,
globalRead,
obsSecReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import { secOnlyDefaultSpaceAuth, superUserDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const es = getService('es');
describe('post_configure', () => {
const actionsRemover = new ActionsRemover(supertest);
afterEach(async () => {
await deleteConfiguration(es);
await actionsRemover.removeAll();
});
it('User: security solution only - should create a configuration', async () => {
const configuration = await createConfiguration(
supertestWithoutAuth,
getConfigurationRequest(),
200,
secOnlyDefaultSpaceAuth
);
expect(configuration.owner).to.eql('securitySolutionFixture');
});
it('User: security solution only - should NOT create a configuration of different owner', async () => {
await createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'observabilityFixture' },
403,
secOnlyDefaultSpaceAuth
);
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should NOT create a configuration`, async () => {
await createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'securitySolutionFixture' },
403,
{
user,
space: null,
}
);
});
}
it('should return a 404 when attempting to access a space', async () => {
await createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'securitySolutionFixture' },
404,
{
user: secOnlySpacesAll,
space: 'space1',
}
);
});
it('it deletes the correct configurations', async () => {
await createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'securitySolutionFixture' },
200,
superUserDefaultSpaceAuth
);
/**
* This API call should not delete the previously created configuration
* as it belongs to a different owner
*/
await createConfiguration(
supertestWithoutAuth,
{ ...getConfigurationRequest(), owner: 'observabilityFixture' },
200,
superUserDefaultSpaceAuth
);
const configuration = await getConfiguration({
supertest: supertestWithoutAuth,
query: { owner: ['securitySolutionFixture', 'observabilityFixture'] },
auth: superUserDefaultSpaceAuth,
});
/**
* This ensures that both configuration are returned as expected
* and neither of has been deleted
*/
ensureSavedObjectIsAuthorized(configuration, 2, [
'securitySolutionFixture',
'observabilityFixture',
]);
});
});
};

View file

@ -1,33 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile }: FtrProviderContext): void => {
describe('Common', function () {
loadTestFile(require.resolve('./comments/delete_comment'));
loadTestFile(require.resolve('./comments/find_comments'));
loadTestFile(require.resolve('./comments/get_comment'));
loadTestFile(require.resolve('./comments/get_all_comments'));
loadTestFile(require.resolve('./comments/patch_comment'));
loadTestFile(require.resolve('./comments/post_comment'));
loadTestFile(require.resolve('./alerts/get_cases'));
loadTestFile(require.resolve('./cases/delete_cases'));
loadTestFile(require.resolve('./cases/find_cases'));
loadTestFile(require.resolve('./cases/get_case'));
loadTestFile(require.resolve('./cases/patch_cases'));
loadTestFile(require.resolve('./cases/post_case'));
loadTestFile(require.resolve('./cases/reporters/get_reporters'));
loadTestFile(require.resolve('./cases/status/get_status'));
loadTestFile(require.resolve('./cases/tags/get_tags'));
loadTestFile(require.resolve('./user_actions/get_all_user_actions'));
loadTestFile(require.resolve('./configure/get_configure'));
loadTestFile(require.resolve('./configure/patch_configure'));
loadTestFile(require.resolve('./configure/post_configure'));
});
};

View file

@ -1,104 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { CaseResponse, CaseStatuses } from '../../../../../../plugins/cases/common/api';
import { getPostCaseRequest } from '../../../../common/lib/mock';
import {
deleteAllCaseItems,
createCase,
updateCase,
getCaseUserActions,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsSecSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
superUser,
} from '../../../../common/lib/authentication/users';
import { superUserDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const es = getService('es');
describe('get_all_user_actions', () => {
afterEach(async () => {
await deleteAllCaseItems(es);
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
let caseInfo: CaseResponse;
beforeEach(async () => {
caseInfo = await createCase(
supertestWithoutAuth,
getPostCaseRequest(),
200,
superUserDefaultSpaceAuth
);
await updateCase({
supertest: supertestWithoutAuth,
params: {
cases: [
{
id: caseInfo.id,
version: caseInfo.version,
status: CaseStatuses.closed,
},
],
},
auth: superUserDefaultSpaceAuth,
});
});
it('should get the user actions for a case when the user has the correct permissions', async () => {
for (const user of [
globalRead,
superUser,
secOnlySpacesAll,
secOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
]) {
const userActions = await getCaseUserActions({
supertest: supertestWithoutAuth,
caseID: caseInfo.id,
auth: { user, space: null },
});
expect(userActions.length).to.eql(2);
}
});
it(`should 403 when requesting the user actions of a case with user ${
noKibanaPrivileges.username
} with role(s) ${noKibanaPrivileges.roles.join()}`, async () => {
await getCaseUserActions({
supertest: supertestWithoutAuth,
caseID: caseInfo.id,
auth: { user: noKibanaPrivileges, space: null },
expectedHttpCode: 403,
});
});
it('should return a 404 when attempting to access a space', async () => {
await getCaseUserActions({
supertest: supertestWithoutAuth,
caseID: caseInfo.id,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,131 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import http from 'http';
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib';
import { getPostCaseRequest } from '../../../../common/lib/mock';
import {
pushCase,
deleteAllCaseItems,
createCaseWithConnector,
getServiceNowSimulationServer,
} from '../../../../common/lib/utils';
import {
globalRead,
noKibanaPrivileges,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
secOnlySpacesAll,
secOnlyReadSpacesAll,
} from '../../../../common/lib/authentication/users';
import { secOnlyDefaultSpaceAuth } from '../../../utils';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const es = getService('es');
describe('push_case', () => {
const actionsRemover = new ActionsRemover(supertest);
let serviceNowSimulatorURL: string = '';
let serviceNowServer: http.Server;
before(async () => {
const { server, url } = await getServiceNowSimulationServer();
serviceNowServer = server;
serviceNowSimulatorURL = url;
});
afterEach(async () => {
await deleteAllCaseItems(es);
await actionsRemover.removeAll();
});
after(async () => {
serviceNowServer.close();
});
const supertestWithoutAuth = getService('supertestWithoutAuth');
it('should push a case that the user has permissions for', async () => {
const { postedCase, connector } = await createCaseWithConnector({
supertest,
serviceNowSimulatorURL,
actionsRemover,
});
await pushCase({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
connectorId: connector.id,
auth: secOnlyDefaultSpaceAuth,
});
});
it('should not push a case that the user does not have permissions for', async () => {
const { postedCase, connector } = await createCaseWithConnector({
supertest,
serviceNowSimulatorURL,
actionsRemover,
createCaseReq: getPostCaseRequest({ owner: 'observabilityFixture' }),
});
await pushCase({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
connectorId: connector.id,
auth: secOnlyDefaultSpaceAuth,
expectedHttpCode: 403,
});
});
for (const user of [
globalRead,
secOnlyReadSpacesAll,
obsOnlyReadSpacesAll,
obsSecReadSpacesAll,
noKibanaPrivileges,
]) {
it(`User ${
user.username
} with role(s) ${user.roles.join()} - should NOT push a case`, async () => {
const { postedCase, connector } = await createCaseWithConnector({
supertest,
serviceNowSimulatorURL,
actionsRemover,
});
await pushCase({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
connectorId: connector.id,
auth: { user, space: null },
expectedHttpCode: 403,
});
});
}
it('should return a 404 when attempting to access a space', async () => {
const { postedCase, connector } = await createCaseWithConnector({
supertest,
serviceNowSimulatorURL,
actionsRemover,
});
await pushCase({
supertest: supertestWithoutAuth,
caseId: postedCase.id,
connectorId: connector.id,
auth: { user: secOnlySpacesAll, space: 'space1' },
expectedHttpCode: 404,
});
});
});
};

View file

@ -1,34 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { rolesDefaultSpace } from '../../../common/lib/authentication/roles';
import { usersDefaultSpace } from '../../../common/lib/authentication/users';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import { createUsersAndRoles, deleteUsersAndRoles } from '../../../common/lib/authentication';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile, getService }: FtrProviderContext): void => {
describe('cases security only enabled: trial', function () {
// Fastest ciGroup for the moment.
this.tags('ciGroup5');
before(async () => {
// since spaces are disabled this changes each role to have access to all available spaces (it'll just be the default one)
await createUsersAndRoles(getService, usersDefaultSpace, rolesDefaultSpace);
});
after(async () => {
await deleteUsersAndRoles(getService, usersDefaultSpace, rolesDefaultSpace);
});
// Trial
loadTestFile(require.resolve('./cases/push_case'));
// Common
loadTestFile(require.resolve('../common'));
});
};

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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
obsOnlySpacesAll,
obsSecSpacesAll,
secOnlySpacesAll,
} from '../common/lib/authentication/users';
import { getAuthWithSuperUser } from '../common/lib/utils';
export const secOnlyDefaultSpaceAuth = { user: secOnlySpacesAll, space: null };
export const obsOnlyDefaultSpaceAuth = { user: obsOnlySpacesAll, space: null };
export const obsSecDefaultSpaceAuth = { user: obsSecSpacesAll, space: null };
export const superUserDefaultSpaceAuth = getAuthWithSuperUser(null);

View file

@ -173,21 +173,6 @@ export const obsSecReadSpacesAll: User = {
roles: [securitySolutionOnlyReadSpacesAll.name, observabilityOnlyReadSpacesAll.name],
};
/**
* These users are for the security_only tests because most of them have access to the default space instead of 'space1'
*/
export const usersDefaultSpace = [
superUser,
secOnlySpacesAll,
secOnlyReadSpacesAll,
obsOnlySpacesAll,
obsOnlyReadSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
globalRead,
noKibanaPrivileges,
];
/**
* Trial users with trial roles
*/

View file

@ -60,7 +60,7 @@ const createTestCases = (spaceId: string) => {
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, namespaces: [SPACE_1_ID] }, // second try searches for it in a single other space, which is valid
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, namespaces: [SPACE_2_ID], ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, namespaces: [SPACE_2_ID, 'x'] }, // unknown space is allowed / ignored
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, namespaces: [ALL_SPACES_ID] }, // this is different than the same test case in the spaces_only and security_only suites, since MULTI_NAMESPACE_ONLY_SPACE_1 *may* return a 404 error to a partially authorized user
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, namespaces: [ALL_SPACES_ID] }, // this is different than the same test case in the spaces_only suite, since MULTI_NAMESPACE_ONLY_SPACE_1 *may* return a 404 error to a partially authorized user
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = [...normalTypes, ...crossNamespace, ...hiddenType];

View file

@ -1,114 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
bulkCreateTestSuiteFactory,
TEST_CASES as CASES,
BulkCreateTestDefinition,
} from '../../common/suites/bulk_create';
const {
DEFAULT: { spaceId: DEFAULT_SPACE_ID },
} = SPACES;
const { fail400, fail409 } = testCaseFailures;
const unresolvableConflict = () => ({ fail409Param: 'unresolvableConflict' });
const createTestCases = (overwrite: boolean) => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const expectedNamespaces = [DEFAULT_SPACE_ID]; // newly created objects should have this `namespaces` array in their return value
const normalTypes = [
{ ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite) },
{ ...CASES.SINGLE_NAMESPACE_SPACE_1, expectedNamespaces },
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, expectedNamespaces },
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409(), ...unresolvableConflict() },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409(), ...unresolvableConflict() },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail409(), ...unresolvableConflict() },
{ ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) },
{ ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces },
{ ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces },
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
{
...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
initialNamespaces: ['x', 'y'],
...fail400(), // cannot be created in multiple spaces
},
CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
{
...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
initialNamespaces: [ALL_SPACES_ID],
...fail400(), // cannot be created in multiple spaces
},
CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = normalTypes.concat(hiddenType);
return { normalTypes, hiddenType, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions, expectSavedObjectForbidden } =
bulkCreateTestSuiteFactory(esArchiver, supertest);
const createTests = (overwrite: boolean, user: TestUser) => {
const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite);
// use singleRequest to reduce execution time and/or test combined cases
return {
unauthorized: createTestDefinitions(allTypes, true, overwrite, { user }),
authorized: [
createTestDefinitions(normalTypes, false, overwrite, { user, singleRequest: true }),
createTestDefinitions(hiddenType, true, overwrite, { user }),
createTestDefinitions(allTypes, true, overwrite, {
user,
singleRequest: true,
responseBodyOverride: expectSavedObjectForbidden(['hiddentype']),
}),
].flat(),
superuser: createTestDefinitions(allTypes, false, overwrite, { user, singleRequest: true }),
};
};
describe('_bulk_create', () => {
getTestScenarios([false, true]).security.forEach(({ users, modifier: overwrite }) => {
const suffix = overwrite ? ' with overwrite enabled' : '';
const _addTests = (user: TestUser, tests: BulkCreateTestDefinition[]) => {
addTests(`${user.description}${suffix}`, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.dualRead,
users.readGlobally,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
const { unauthorized } = createTests(overwrite!, user);
_addTests(user, unauthorized);
});
[users.dualAll, users.allGlobally].forEach((user) => {
const { authorized } = createTests(overwrite!, user);
_addTests(user, authorized);
});
const { superuser } = createTests(overwrite!, users.superuser);
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,108 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
bulkGetTestSuiteFactory,
TEST_CASES as CASES,
BulkGetTestDefinition,
} from '../../common/suites/bulk_get';
const {
SPACE_1: { spaceId: SPACE_1_ID },
SPACE_2: { spaceId: SPACE_2_ID },
} = SPACES;
const { fail400, fail404 } = testCaseFailures;
const createTestCases = () => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const normalTypes = [
CASES.SINGLE_NAMESPACE_DEFAULT_SPACE,
{ ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail404() },
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail404() },
CASES.MULTI_NAMESPACE_ALL_SPACES,
CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1,
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail404() },
CASES.NAMESPACE_AGNOSTIC,
{ ...CASES.DOES_NOT_EXIST, ...fail404() },
{
...CASES.SINGLE_NAMESPACE_SPACE_2,
namespaces: ['x', 'y'],
...fail400(), // cannot be searched for in multiple spaces
},
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, namespaces: [SPACE_2_ID] }, // second try searches for it in a single other space, which is valid
{
...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1,
namespaces: [ALL_SPACES_ID],
...fail400(), // cannot be searched for in multiple spaces
},
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, namespaces: [SPACE_1_ID] }, // second try searches for it in a single other space, which is valid
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, namespaces: [SPACE_2_ID], ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, namespaces: [SPACE_2_ID, 'x'] }, // unknown space is allowed / ignored
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, namespaces: [ALL_SPACES_ID] },
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = normalTypes.concat(hiddenType);
return { normalTypes, hiddenType, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions, expectSavedObjectForbidden } = bulkGetTestSuiteFactory(
esArchiver,
supertest
);
const createTests = () => {
const { normalTypes, hiddenType, allTypes } = createTestCases();
// use singleRequest to reduce execution time and/or test combined cases
return {
unauthorized: createTestDefinitions(allTypes, true),
authorized: [
createTestDefinitions(normalTypes, false, { singleRequest: true }),
createTestDefinitions(hiddenType, true),
createTestDefinitions(allTypes, true, {
singleRequest: true,
responseBodyOverride: expectSavedObjectForbidden(['hiddentype']),
}),
].flat(),
superuser: createTestDefinitions(allTypes, false, { singleRequest: true }),
};
};
describe('_bulk_get', () => {
getTestScenarios().security.forEach(({ users }) => {
const { unauthorized, authorized, superuser } = createTests();
const _addTests = (user: TestUser, tests: BulkGetTestDefinition[]) => {
addTests(user.description, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.dualRead, users.allGlobally, users.readGlobally].forEach((user) => {
_addTests(user, authorized);
});
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,74 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
bulkResolveTestSuiteFactory,
TEST_CASES as CASES,
BulkResolveTestDefinition,
} from '../../common/suites/bulk_resolve';
const { fail400, fail404 } = testCaseFailures;
const createTestCases = () => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const normalTypes = [
{ ...CASES.EXACT_MATCH },
{ ...CASES.ALIAS_MATCH, ...fail404() },
{ ...CASES.CONFLICT, expectedOutcome: 'exactMatch' as const },
{ ...CASES.DISABLED, ...fail404() },
{ ...CASES.DOES_NOT_EXIST, ...fail404() },
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = [...normalTypes, ...hiddenType];
return { normalTypes, hiddenType, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions } = bulkResolveTestSuiteFactory(esArchiver, supertest);
const createTests = () => {
const { normalTypes, hiddenType, allTypes } = createTestCases();
return {
unauthorized: createTestDefinitions(allTypes, true),
authorized: [
createTestDefinitions(normalTypes, false, { singleRequest: true }),
createTestDefinitions(hiddenType, true),
].flat(),
superuser: createTestDefinitions(allTypes, false, { singleRequest: true }),
};
};
describe('_bulk_resolve', () => {
getTestScenarios().security.forEach(({ users }) => {
const { unauthorized, authorized, superuser } = createTests();
const _addTests = (user: TestUser, tests: BulkResolveTestDefinition[]) => {
addTests(user.description, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.dualRead, users.allGlobally, users.readGlobally].forEach((user) => {
_addTests(user, authorized);
});
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,114 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SPACES } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
bulkUpdateTestSuiteFactory,
TEST_CASES as CASES,
BulkUpdateTestDefinition,
} from '../../common/suites/bulk_update';
const {
DEFAULT: { spaceId: DEFAULT_SPACE_ID },
SPACE_1: { spaceId: SPACE_1_ID },
SPACE_2: { spaceId: SPACE_2_ID },
} = SPACES;
const { fail404 } = testCaseFailures;
const createTestCases = () => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const normalTypes = [
CASES.SINGLE_NAMESPACE_DEFAULT_SPACE,
{ ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail404() },
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail404() },
CASES.MULTI_NAMESPACE_ALL_SPACES,
CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1,
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail404() },
CASES.NAMESPACE_AGNOSTIC,
{ ...CASES.DOES_NOT_EXIST, ...fail404() },
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail404() }];
const allTypes = normalTypes.concat(hiddenType);
// an "object namespace" string can be specified for individual objects (to bulkUpdate across namespaces)
// even if the Spaces plugin is disabled, this should work, as `namespace` is handled by the Core API
const withObjectNamespaces = [
{ ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, namespace: DEFAULT_SPACE_ID },
{ ...CASES.SINGLE_NAMESPACE_SPACE_1, namespace: SPACE_1_ID },
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, namespace: SPACE_1_ID, ...fail404() }, // intentional 404 test case
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, namespace: DEFAULT_SPACE_ID }, // any spaceId will work (not '*')
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, namespace: DEFAULT_SPACE_ID }, // SPACE_1_ID would also work
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, namespace: SPACE_2_ID, ...fail404() }, // intentional 404 test case
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, namespace: SPACE_2_ID },
CASES.NAMESPACE_AGNOSTIC, // any namespace would work and would make no difference
{ ...CASES.DOES_NOT_EXIST, ...fail404() },
];
return { normalTypes, hiddenType, allTypes, withObjectNamespaces };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions, expectSavedObjectForbidden } =
bulkUpdateTestSuiteFactory(esArchiver, supertest);
const createTests = () => {
const { normalTypes, hiddenType, allTypes, withObjectNamespaces } = createTestCases();
// use singleRequest to reduce execution time and/or test combined cases
return {
unauthorized: [
createTestDefinitions(allTypes, true),
createTestDefinitions(withObjectNamespaces, true, { singleRequest: true }),
].flat(),
authorized: [
createTestDefinitions(normalTypes, false, { singleRequest: true }),
createTestDefinitions(hiddenType, true),
createTestDefinitions(allTypes, true, {
singleRequest: true,
responseBodyOverride: expectSavedObjectForbidden(['hiddentype']),
}),
createTestDefinitions(withObjectNamespaces, false, { singleRequest: true }),
].flat(),
superuser: [
createTestDefinitions(allTypes, false, { singleRequest: true }),
createTestDefinitions(withObjectNamespaces, false, { singleRequest: true }),
].flat(),
};
};
describe('_bulk_update', () => {
getTestScenarios().security.forEach(({ users }) => {
const { unauthorized, authorized, superuser } = createTests();
const _addTests = (user: TestUser, tests: BulkUpdateTestDefinition[]) => {
addTests(user.description, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.dualRead,
users.readGlobally,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.allGlobally].forEach((user) => {
_addTests(user, authorized);
});
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,106 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SPACES, ALL_SPACES_ID } from '../../common/lib/spaces';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
createTestSuiteFactory,
TEST_CASES as CASES,
CreateTestDefinition,
} from '../../common/suites/create';
const {
DEFAULT: { spaceId: DEFAULT_SPACE_ID },
} = SPACES;
const { fail400, fail409 } = testCaseFailures;
const createTestCases = (overwrite: boolean) => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const expectedNamespaces = [DEFAULT_SPACE_ID]; // newly created objects should have this `namespaces` array in their return value
const normalTypes = [
{ ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite) },
{ ...CASES.SINGLE_NAMESPACE_SPACE_1, expectedNamespaces },
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, expectedNamespaces },
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail409() },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail409() },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail409() },
{ ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) },
{ ...CASES.NEW_SINGLE_NAMESPACE_OBJ, expectedNamespaces },
{ ...CASES.NEW_MULTI_NAMESPACE_OBJ, expectedNamespaces },
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
{
...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE,
initialNamespaces: ['x', 'y'],
...fail400(), // cannot be created in multiple spaces
},
CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
{
...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE,
initialNamespaces: [ALL_SPACES_ID],
...fail400(), // cannot be created in multiple spaces
},
CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid
CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE,
CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES,
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = normalTypes.concat(hiddenType);
return { normalTypes, hiddenType, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions } = createTestSuiteFactory(esArchiver, supertest);
const createTests = (overwrite: boolean, user: TestUser) => {
const { normalTypes, hiddenType, allTypes } = createTestCases(overwrite);
return {
unauthorized: createTestDefinitions(allTypes, true, overwrite, { user }),
authorized: [
createTestDefinitions(normalTypes, false, overwrite, { user }),
createTestDefinitions(hiddenType, true, overwrite, { user }),
].flat(),
superuser: createTestDefinitions(allTypes, false, overwrite, { user }),
};
};
describe('_create', () => {
getTestScenarios([false, true]).security.forEach(({ users, modifier: overwrite }) => {
const suffix = overwrite ? ' with overwrite enabled' : '';
const _addTests = (user: TestUser, tests: CreateTestDefinition[]) => {
addTests(`${user.description}${suffix}`, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.dualRead,
users.readGlobally,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
const { unauthorized } = createTests(overwrite!, user);
_addTests(user, unauthorized);
});
[users.dualAll, users.allGlobally].forEach((user) => {
const { authorized } = createTests(overwrite!, user);
_addTests(user, authorized);
});
const { superuser } = createTests(overwrite!, users.superuser);
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,86 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
deleteTestSuiteFactory,
TEST_CASES as CASES,
DeleteTestDefinition,
} from '../../common/suites/delete';
const { fail400, fail404 } = testCaseFailures;
const createTestCases = () => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const normalTypes = [
CASES.SINGLE_NAMESPACE_DEFAULT_SPACE,
{ ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail404() },
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, ...fail400() },
// try to delete this object again, this time using the `force` option
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, force: true },
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail400() },
// try to delete this object again, this time using the `force` option
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, force: true },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail404() },
CASES.NAMESPACE_AGNOSTIC,
{ ...CASES.DOES_NOT_EXIST, ...fail404() },
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail404() }];
const allTypes = normalTypes.concat(hiddenType);
return { normalTypes, hiddenType, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions } = deleteTestSuiteFactory(esArchiver, supertest);
const createTests = () => {
const { normalTypes, hiddenType, allTypes } = createTestCases();
return {
unauthorized: createTestDefinitions(allTypes, true),
authorized: [
createTestDefinitions(normalTypes, false),
createTestDefinitions(hiddenType, true),
].flat(),
superuser: createTestDefinitions(allTypes, false),
};
};
describe('_delete', () => {
getTestScenarios().security.forEach(({ users }) => {
const { unauthorized, authorized, superuser } = createTests();
const _addTests = (user: TestUser, tests: DeleteTestDefinition[]) => {
addTests(user.description, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.dualRead,
users.readGlobally,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.allGlobally].forEach((user) => {
_addTests(user, authorized);
});
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,86 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
exportTestSuiteFactory,
getTestCases,
ExportTestDefinition,
} from '../../common/suites/export';
const createTestCases = () => {
const cases = getTestCases();
const exportableObjects = [
cases.singleNamespaceObject,
cases.multiNamespaceObject,
cases.multiNamespaceIsolatedObject,
cases.namespaceAgnosticObject,
];
const exportableTypes = [
cases.singleNamespaceType,
cases.multiNamespaceType,
cases.multiNamespaceIsolatedType,
cases.namespaceAgnosticType,
];
const nonExportableObjectsAndTypes = [cases.hiddenObject, cases.hiddenType];
const allObjectsAndTypes = [
exportableObjects,
exportableTypes,
nonExportableObjectsAndTypes,
].flat();
return { exportableObjects, exportableTypes, nonExportableObjectsAndTypes, allObjectsAndTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions } = exportTestSuiteFactory(esArchiver, supertest);
const createTests = () => {
const { exportableObjects, exportableTypes, nonExportableObjectsAndTypes, allObjectsAndTypes } =
createTestCases();
return {
unauthorized: [
createTestDefinitions(exportableObjects, { statusCode: 403, reason: 'unauthorized' }),
createTestDefinitions(exportableTypes, { statusCode: 403, reason: 'unauthorized' }), // failure with empty result
createTestDefinitions(nonExportableObjectsAndTypes, false),
].flat(),
authorized: createTestDefinitions(allObjectsAndTypes, false),
};
};
describe('_export', () => {
getTestScenarios().security.forEach(({ users }) => {
const { unauthorized, authorized } = createTests();
const _addTests = (user: TestUser, tests: ExportTestDefinition[]) => {
addTests(user.description, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[
users.dualAll,
users.dualRead,
users.allGlobally,
users.readGlobally,
users.superuser,
].forEach((user) => {
_addTests(user, authorized);
});
});
});
}

View file

@ -1,93 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SPACES } from '../../common/lib/spaces';
import { AUTHENTICATION } from '../../common/lib/authentication';
import { getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { findTestSuiteFactory, getTestCases } from '../../common/suites/find';
const {
DEFAULT: { spaceId: DEFAULT_SPACE_ID },
SPACE_1: { spaceId: SPACE_1_ID },
SPACE_2: { spaceId: SPACE_2_ID },
} = SPACES;
const createTestCases = (crossSpaceSearch?: string[]) => {
const cases = getTestCases({ crossSpaceSearch });
const normalTypes = [
cases.singleNamespaceType,
cases.multiNamespaceType,
cases.multiNamespaceIsolatedType,
cases.namespaceAgnosticType,
cases.eachType,
cases.pageBeyondTotal,
cases.unknownSearchField,
cases.filterWithNamespaceAgnosticType,
cases.filterWithDisallowedType,
];
const hiddenAndUnknownTypes = [
cases.hiddenType,
cases.unknownType,
cases.filterWithHiddenType,
cases.filterWithUnknownType,
];
const allTypes = normalTypes.concat(hiddenAndUnknownTypes);
return { normalTypes, hiddenAndUnknownTypes, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions } = findTestSuiteFactory(esArchiver, supertest);
const createTests = (user: TestUser) => {
const defaultCases = createTestCases();
const crossSpaceCases = createTestCases([DEFAULT_SPACE_ID, SPACE_1_ID, SPACE_2_ID]);
if (user.username === AUTHENTICATION.SUPERUSER.username) {
return {
defaultCases: createTestDefinitions(defaultCases.allTypes, false, { user }),
crossSpace: createTestDefinitions(
crossSpaceCases.allTypes,
{ statusCode: 400, reason: 'cross_namespace_not_permitted' },
{ user }
),
};
}
const isAuthorizedGlobally = user.authorizedAtSpaces.includes('*');
return {
defaultCases: isAuthorizedGlobally
? [
createTestDefinitions(defaultCases.normalTypes, false, { user }),
createTestDefinitions(defaultCases.hiddenAndUnknownTypes, {
statusCode: 200,
reason: 'unauthorized',
}),
].flat()
: createTestDefinitions(defaultCases.allTypes, { statusCode: 200, reason: 'unauthorized' }),
crossSpace: createTestDefinitions(
crossSpaceCases.allTypes,
{ statusCode: 400, reason: 'cross_namespace_not_permitted' },
{ user }
),
};
};
describe('_find', () => {
getTestScenarios().security.forEach(({ users }) => {
Object.values(users).forEach((user) => {
const { defaultCases, crossSpace } = createTests(user);
addTests(`${user.description}`, { user, tests: [...defaultCases, ...crossSpace] });
});
});
});
}

View file

@ -1,80 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
getTestSuiteFactory,
TEST_CASES as CASES,
GetTestDefinition,
} from '../../common/suites/get';
const { fail404 } = testCaseFailures;
const createTestCases = () => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const normalTypes = [
CASES.SINGLE_NAMESPACE_DEFAULT_SPACE,
{ ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail404() },
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail404() },
CASES.MULTI_NAMESPACE_ALL_SPACES,
CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1,
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail404() },
CASES.NAMESPACE_AGNOSTIC,
{ ...CASES.DOES_NOT_EXIST, ...fail404() },
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail404() }];
const allTypes = normalTypes.concat(hiddenType);
return { normalTypes, hiddenType, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions } = getTestSuiteFactory(esArchiver, supertest);
const createTests = () => {
const { normalTypes, hiddenType, allTypes } = createTestCases();
return {
unauthorized: createTestDefinitions(allTypes, true),
authorized: [
createTestDefinitions(normalTypes, false),
createTestDefinitions(hiddenType, true),
].flat(),
superuser: createTestDefinitions(allTypes, false),
};
};
describe('_get', () => {
getTestScenarios().security.forEach(({ users }) => {
const { unauthorized, authorized, superuser } = createTests();
const _addTests = (user: TestUser, tests: GetTestDefinition[]) => {
addTests(user.description, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.dualRead, users.allGlobally, users.readGlobally].forEach((user) => {
_addTests(user, authorized);
});
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,178 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
importTestSuiteFactory,
TEST_CASES as CASES,
ImportTestDefinition,
} from '../../common/suites/import';
const { fail400, fail409 } = testCaseFailures;
const destinationId = (condition?: boolean) =>
condition !== false ? { successParam: 'destinationId' } : {};
const newCopy = () => ({ successParam: 'createNewCopy' });
const ambiguousConflict = (suffix: string) => ({
failure: 409 as 409,
fail409Param: `ambiguous_conflict_${suffix}`,
});
const createNewCopiesTestCases = () => {
// for each outcome, if failure !== undefined then we expect to receive
// an error; otherwise, we expect to receive a success result
const cases = Object.entries(CASES).filter(([key]) => key !== 'HIDDEN');
const importable = cases.map(([, val]) => ({ ...val, successParam: 'createNewCopies' }));
const nonImportable = [{ ...CASES.HIDDEN, ...fail400() }];
const all = [...importable, ...nonImportable];
return { importable, nonImportable, all };
};
const createTestCases = (overwrite: boolean) => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const group1Importable = [
// when overwrite=true, all of the objects in this group are created successfully, so we can check the created object attributes
{ ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite) },
CASES.SINGLE_NAMESPACE_SPACE_1,
CASES.SINGLE_NAMESPACE_SPACE_2,
{ ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) },
CASES.NEW_SINGLE_NAMESPACE_OBJ,
CASES.NEW_NAMESPACE_AGNOSTIC_OBJ,
];
const group1NonImportable = [{ ...CASES.HIDDEN, ...fail400() }];
const group1All = group1Importable.concat(group1NonImportable);
const group2 = [
// when overwrite=true, all of the objects in this group are created successfully, so we can check the created object attributes
CASES.NEW_MULTI_NAMESPACE_OBJ,
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...destinationId() },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...destinationId() },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...destinationId() },
{ ...CASES.CONFLICT_1A_OBJ, ...newCopy() }, // "ambiguous source" conflict which results in a new destination ID and empty origin ID
{ ...CASES.CONFLICT_1B_OBJ, ...newCopy() }, // "ambiguous source" conflict which results in a new destination ID and empty origin ID
{ ...CASES.CONFLICT_3A_OBJ, ...fail409(!overwrite), ...destinationId() }, // "inexact match" conflict
{ ...CASES.CONFLICT_4_OBJ, ...fail409(!overwrite), ...destinationId() }, // "inexact match" conflict
];
const group3 = [
// when overwrite=true, all of the objects in this group are errors, so we cannot check the created object attributes
// grouping errors together simplifies the test suite code
{ ...CASES.CONFLICT_2C_OBJ, ...ambiguousConflict('2c') }, // "ambiguous destination" conflict
];
const group4 = [
// when overwrite=true, all of the objects in this group are created successfully, so we can check the created object attributes
{ ...CASES.CONFLICT_1_OBJ, ...fail409(!overwrite) }, // "exact match" conflict
CASES.CONFLICT_1A_OBJ, // no conflict because CONFLICT_1_OBJ is an exact match
CASES.CONFLICT_1B_OBJ, // no conflict because CONFLICT_1_OBJ is an exact match
{ ...CASES.CONFLICT_2C_OBJ, ...newCopy() }, // "ambiguous source and destination" conflict which results in a new destination ID and empty origin ID
{ ...CASES.CONFLICT_2D_OBJ, ...newCopy() }, // "ambiguous source and destination" conflict which results in a new destination ID and empty origin ID
];
return { group1Importable, group1NonImportable, group1All, group2, group3, group4 };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const es = getService('es');
const { addTests, createTestDefinitions, expectSavedObjectForbidden } = importTestSuiteFactory(
es,
esArchiver,
supertest
);
const createTests = (overwrite: boolean, createNewCopies: boolean) => {
// use singleRequest to reduce execution time and/or test combined cases
const singleRequest = true;
if (createNewCopies) {
const { importable, nonImportable, all } = createNewCopiesTestCases();
return {
unauthorized: [
createTestDefinitions(importable, true, { createNewCopies }),
createTestDefinitions(nonImportable, false, { createNewCopies, singleRequest }),
createTestDefinitions(all, true, {
createNewCopies,
singleRequest,
responseBodyOverride: expectSavedObjectForbidden([
'dashboard',
'globaltype',
'isolatedtype',
'sharedtype',
'sharecapabletype',
]),
}),
].flat(),
authorized: createTestDefinitions(all, false, { createNewCopies, singleRequest }),
};
}
const { group1Importable, group1NonImportable, group1All, group2, group3, group4 } =
createTestCases(overwrite);
return {
unauthorized: [
createTestDefinitions(group1Importable, true, { overwrite }),
createTestDefinitions(group1NonImportable, false, { overwrite, singleRequest }),
createTestDefinitions(group1All, true, {
overwrite,
singleRequest,
responseBodyOverride: expectSavedObjectForbidden([
'dashboard',
'globaltype',
'isolatedtype',
]),
}),
createTestDefinitions(group2, true, { overwrite, singleRequest }),
createTestDefinitions(group3, true, { overwrite, singleRequest }),
createTestDefinitions(group4, true, { overwrite, singleRequest }),
].flat(),
authorized: [
createTestDefinitions(group1All, false, { overwrite, singleRequest }),
createTestDefinitions(group2, false, { overwrite, singleRequest }),
createTestDefinitions(group3, false, { overwrite, singleRequest }),
createTestDefinitions(group4, false, { overwrite, singleRequest }),
].flat(),
};
};
describe('_import', () => {
getTestScenarios([
[false, false],
[false, true],
[true, false],
]).security.forEach(({ users, modifier }) => {
const [overwrite, createNewCopies] = modifier!;
const suffix = overwrite
? ' with overwrite enabled'
: createNewCopies
? ' with createNewCopies enabled'
: '';
const { unauthorized, authorized } = createTests(overwrite, createNewCopies);
const _addTests = (user: TestUser, tests: ImportTestDefinition[]) => {
addTests(`${user.description}${suffix}`, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.dualRead,
users.readGlobally,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.allGlobally, users.superuser].forEach((user) => {
_addTests(user, authorized);
});
});
});
}

View file

@ -1,36 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createUsersAndRoles } from '../../common/lib/create_users_and_roles';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ({ getService, loadTestFile }: FtrProviderContext) {
const es = getService('es');
const supertest = getService('supertest');
describe('saved objects security only enabled', function () {
this.tags('ciGroup9');
before(async () => {
await createUsersAndRoles(es, supertest);
});
loadTestFile(require.resolve('./bulk_create'));
loadTestFile(require.resolve('./bulk_get'));
loadTestFile(require.resolve('./bulk_resolve'));
loadTestFile(require.resolve('./bulk_update'));
loadTestFile(require.resolve('./create'));
loadTestFile(require.resolve('./delete'));
loadTestFile(require.resolve('./export'));
loadTestFile(require.resolve('./find'));
loadTestFile(require.resolve('./get'));
loadTestFile(require.resolve('./import'));
loadTestFile(require.resolve('./resolve_import_errors'));
loadTestFile(require.resolve('./resolve'));
loadTestFile(require.resolve('./update'));
});
}

View file

@ -1,74 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
resolveTestSuiteFactory,
TEST_CASES as CASES,
ResolveTestDefinition,
} from '../../common/suites/resolve';
const { fail400, fail404 } = testCaseFailures;
const createTestCases = () => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const normalTypes = [
{ ...CASES.EXACT_MATCH },
{ ...CASES.ALIAS_MATCH, ...fail404() },
{ ...CASES.CONFLICT, expectedOutcome: 'exactMatch' as const },
{ ...CASES.DISABLED, ...fail404() },
{ ...CASES.DOES_NOT_EXIST, ...fail404() },
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }];
const allTypes = [...normalTypes, ...hiddenType];
return { normalTypes, hiddenType, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions } = resolveTestSuiteFactory(esArchiver, supertest);
const createTests = () => {
const { normalTypes, hiddenType, allTypes } = createTestCases();
return {
unauthorized: createTestDefinitions(allTypes, true),
authorized: [
createTestDefinitions(normalTypes, false),
createTestDefinitions(hiddenType, true),
].flat(),
superuser: createTestDefinitions(allTypes, false),
};
};
describe('_resolve', () => {
getTestScenarios().security.forEach(({ users }) => {
const { unauthorized, authorized, superuser } = createTests();
const _addTests = (user: TestUser, tests: ResolveTestDefinition[]) => {
addTests(user.description, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.dualRead, users.allGlobally, users.readGlobally].forEach((user) => {
_addTests(user, authorized);
});
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,147 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { v4 as uuidv4 } from 'uuid';
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
resolveImportErrorsTestSuiteFactory,
TEST_CASES as CASES,
ResolveImportErrorsTestDefinition,
} from '../../common/suites/resolve_import_errors';
const { fail400, fail409 } = testCaseFailures;
const destinationId = (condition?: boolean) =>
condition !== false ? { successParam: 'destinationId' } : {};
const newCopy = () => ({ successParam: 'createNewCopy' });
const createNewCopiesTestCases = () => {
// for each outcome, if failure !== undefined then we expect to receive
// an error; otherwise, we expect to receive a success result
const cases = Object.entries(CASES).filter(([key]) => key !== 'HIDDEN');
const importable = cases.map(([, val]) => ({
...val,
successParam: 'createNewCopies',
expectedNewId: uuidv4(),
}));
const nonImportable = [{ ...CASES.HIDDEN, ...fail400() }];
const all = [...importable, ...nonImportable];
return { importable, nonImportable, all };
};
const createTestCases = (overwrite: boolean) => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const group1Importable = [
{ ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, ...fail409(!overwrite) },
{ ...CASES.NAMESPACE_AGNOSTIC, ...fail409(!overwrite) },
];
const group1NonImportable = [{ ...CASES.HIDDEN, ...fail400() }];
const group1All = [...group1Importable, ...group1NonImportable];
const group2 = [
{ ...CASES.MULTI_NAMESPACE_ALL_SPACES, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, ...fail409(!overwrite) },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE, ...fail409(!overwrite) },
{ ...CASES.CONFLICT_1A_OBJ, ...newCopy() }, // "ambiguous source" conflict which results in a new destination ID and empty origin ID
{ ...CASES.CONFLICT_1B_OBJ, ...newCopy() }, // "ambiguous source" conflict which results in a new destination ID and empty origin ID
// all of the cases below represent imports that had an inexact match conflict or an ambiguous conflict
// if we call _resolve_import_errors and don't specify overwrite, each of these will result in a conflict because an object with that
// `expectedDestinationId` already exists
{ ...CASES.CONFLICT_2C_OBJ, ...fail409(!overwrite), ...destinationId() }, // "ambiguous destination" conflict; if overwrite=true, will overwrite 'conflict_2a'
{ ...CASES.CONFLICT_3A_OBJ, ...fail409(!overwrite), ...destinationId() }, // "inexact match" conflict; if overwrite=true, will overwrite 'conflict_3'
{ ...CASES.CONFLICT_4_OBJ, ...fail409(!overwrite), ...destinationId() }, // "inexact match" conflict; if overwrite=true, will overwrite 'conflict_4a'
];
return { group1Importable, group1NonImportable, group1All, group2 };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const es = getService('es');
const { addTests, createTestDefinitions, expectSavedObjectForbidden } =
resolveImportErrorsTestSuiteFactory(es, esArchiver, supertest);
const createTests = (overwrite: boolean, createNewCopies: boolean) => {
// use singleRequest to reduce execution time and/or test combined cases
const singleRequest = true;
if (createNewCopies) {
const { importable, nonImportable, all } = createNewCopiesTestCases();
return {
unauthorized: [
createTestDefinitions(importable, true, { createNewCopies }),
createTestDefinitions(nonImportable, false, { createNewCopies, singleRequest }),
createTestDefinitions(all, true, {
createNewCopies,
singleRequest,
responseBodyOverride: expectSavedObjectForbidden([
'globaltype',
'isolatedtype',
'sharedtype',
'sharecapabletype',
]),
}),
].flat(),
authorized: createTestDefinitions(all, false, { createNewCopies, singleRequest }),
};
}
const { group1Importable, group1NonImportable, group1All, group2 } = createTestCases(overwrite);
return {
unauthorized: [
createTestDefinitions(group1Importable, true, { overwrite }),
createTestDefinitions(group1NonImportable, false, { overwrite, singleRequest }),
createTestDefinitions(group1All, true, {
overwrite,
singleRequest,
responseBodyOverride: expectSavedObjectForbidden(['globaltype', 'isolatedtype']),
}),
createTestDefinitions(group2, true, { overwrite, singleRequest }),
].flat(),
authorized: [
createTestDefinitions(group1All, false, { overwrite, singleRequest }),
createTestDefinitions(group2, false, { overwrite, singleRequest }),
].flat(),
};
};
describe('_resolve_import_errors', () => {
getTestScenarios([
[false, false],
[false, true],
[true, false],
]).security.forEach(({ users, modifier }) => {
const [overwrite, createNewCopies] = modifier!;
const suffix = overwrite
? ' with overwrite enabled'
: createNewCopies
? ' with createNewCopies enabled'
: '';
const { unauthorized, authorized } = createTests(overwrite, createNewCopies);
const _addTests = (user: TestUser, tests: ResolveImportErrorsTestDefinition[]) => {
addTests(`${user.description}${suffix}`, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.dualRead,
users.readGlobally,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.allGlobally, users.superuser].forEach((user) => {
_addTests(user, authorized);
});
});
});
}

View file

@ -1,82 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { testCaseFailures, getTestScenarios } from '../../common/lib/saved_object_test_utils';
import { TestUser } from '../../common/lib/types';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
updateTestSuiteFactory,
TEST_CASES as CASES,
UpdateTestDefinition,
} from '../../common/suites/update';
const { fail404 } = testCaseFailures;
const createTestCases = () => {
// for each permitted (non-403) outcome, if failure !== undefined then we expect
// to receive an error; otherwise, we expect to receive a success result
const normalTypes = [
CASES.SINGLE_NAMESPACE_DEFAULT_SPACE,
{ ...CASES.SINGLE_NAMESPACE_SPACE_1, ...fail404() },
{ ...CASES.SINGLE_NAMESPACE_SPACE_2, ...fail404() },
CASES.MULTI_NAMESPACE_ALL_SPACES,
CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1,
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ONLY_SPACE_2, ...fail404() },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_DEFAULT_SPACE },
{ ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, ...fail404() },
CASES.NAMESPACE_AGNOSTIC,
{ ...CASES.DOES_NOT_EXIST, ...fail404() },
];
const hiddenType = [{ ...CASES.HIDDEN, ...fail404() }];
const allTypes = normalTypes.concat(hiddenType);
return { normalTypes, hiddenType, allTypes };
};
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const { addTests, createTestDefinitions } = updateTestSuiteFactory(esArchiver, supertest);
const createTests = () => {
const { normalTypes, hiddenType, allTypes } = createTestCases();
return {
unauthorized: createTestDefinitions(allTypes, true),
authorized: [
createTestDefinitions(normalTypes, false),
createTestDefinitions(hiddenType, true),
].flat(),
superuser: createTestDefinitions(allTypes, false),
};
};
describe('_update', () => {
getTestScenarios().security.forEach(({ users }) => {
const { unauthorized, authorized, superuser } = createTests();
const _addTests = (user: TestUser, tests: UpdateTestDefinition[]) => {
addTests(user.description, { user, tests });
};
[
users.noAccess,
users.legacyAll,
users.dualRead,
users.readGlobally,
users.allAtDefaultSpace,
users.readAtDefaultSpace,
users.allAtSpace1,
users.readAtSpace1,
].forEach((user) => {
_addTests(user, unauthorized);
});
[users.dualAll, users.allGlobally].forEach((user) => {
_addTests(user, authorized);
});
_addTests(users.superuser, superuser);
});
});
}

View file

@ -1,11 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('security_only', { disabledPlugins: ['spaces'], license: 'basic' });

View file

@ -1,11 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('security_only', { disabledPlugins: ['spaces'], license: 'trial' });

View file

@ -1,16 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('security_only', {
license: 'basic',
disabledPlugins: ['spaces'],
ssl: false,
testFiles: [require.resolve('./tests/basic')],
});

View file

@ -1,16 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('security_only', {
license: 'trial',
disabledPlugins: ['spaces'],
ssl: false,
testFiles: [require.resolve('./tests/trial')],
});

View file

@ -1,144 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { JsonObject } from '@kbn/utility-types';
import { ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { getSpaceUrlPrefix } from '../../../../rule_registry/common/lib/authentication/spaces';
import {
superUser,
globalRead,
secOnly,
secOnlyRead,
noKibanaPrivileges,
} from '../../../../rule_registry/common/lib/authentication/users';
import {
Direction,
TimelineEventsQueries,
} from '../../../../../plugins/security_solution/common/search_strategy';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
const TO = '3000-01-01T00:00:00.000Z';
const FROM = '2000-01-01T00:00:00.000Z';
const TEST_URL = '/internal/search/timelineSearchStrategy/';
const SPACE_1 = 'space1';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const getPostBody = (): JsonObject => ({
defaultIndex: ['.alerts-*'],
entityType: 'alerts',
docValueFields: [
{
field: '@timestamp',
},
{
field: ALERT_RULE_CONSUMER,
},
{
field: ALERT_INSTANCE_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_INSTANCE_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
filter: [
{
match_all: {},
},
],
},
},
pagination: {
activePage: 0,
querySize: 25,
},
language: 'kuery',
sort: [
{
field: '@timestamp',
direction: Direction.desc,
type: 'number',
},
],
timerange: {
from: FROM,
to: TO,
interval: '12h',
},
});
describe('Timeline - Events', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
});
const authorizedSecSpace1 = [secOnly, secOnlyRead];
const authorizedInAllSpaces = [superUser, globalRead];
const unauthorized = [noKibanaPrivileges];
[...authorizedSecSpace1, ...authorizedInAllSpaces].forEach(({ username, password }) => {
it(`${username} - should return a 404 when accessing a spaces route`, async () => {
await supertestWithoutAuth
.post(`${getSpaceUrlPrefix(SPACE_1)}${TEST_URL}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
...getPostBody(),
defaultIndex: ['.alerts-*'],
entityType: 'alerts',
alertConsumers: ['siem'],
})
.expect(404);
});
});
[...authorizedInAllSpaces].forEach(({ username, password }) => {
it(`${username} - should return 200 for authorized users`, async () => {
await supertestWithoutAuth
.post(`${getSpaceUrlPrefix()}${TEST_URL}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
...getPostBody(),
alertConsumers: ['siem', 'apm'],
})
.expect(200);
});
});
[...unauthorized].forEach(({ username, password }) => {
it(`${username} - should return 403 for unauthorized users`, async () => {
await supertestWithoutAuth
.post(`${getSpaceUrlPrefix()}${TEST_URL}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
...getPostBody(),
alertConsumers: ['siem', 'apm'],
})
// TODO - This should be updated to be a 403 once this ticket is resolved
// https://github.com/elastic/kibana/issues/106005
.expect(500);
});
});
});
};

View file

@ -1,31 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import {
createUsersAndRoles,
deleteUsersAndRoles,
} from '../../../../rule_registry/common/lib/authentication';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile, getService }: FtrProviderContext): void => {
describe('timeline security only: basic', function () {
// Fastest ciGroup for the moment.
this.tags('ciGroup5');
before(async () => {
await createUsersAndRoles(getService);
});
after(async () => {
await deleteUsersAndRoles(getService);
});
// Basic
loadTestFile(require.resolve('./events'));
});
};

View file

@ -1,144 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { JsonObject } from '@kbn/utility-types';
import { ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { getSpaceUrlPrefix } from '../../../../rule_registry/common/lib/authentication/spaces';
import {
superUser,
globalRead,
secOnly,
secOnlyRead,
noKibanaPrivileges,
} from '../../../../rule_registry/common/lib/authentication/users';
import {
Direction,
TimelineEventsQueries,
} from '../../../../../plugins/security_solution/common/search_strategy';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
const TO = '3000-01-01T00:00:00.000Z';
const FROM = '2000-01-01T00:00:00.000Z';
const TEST_URL = '/internal/search/timelineSearchStrategy/';
const SPACE_1 = 'space1';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const getPostBody = (): JsonObject => ({
defaultIndex: ['.alerts-*'],
entityType: 'alerts',
docValueFields: [
{
field: '@timestamp',
},
{
field: ALERT_RULE_CONSUMER,
},
{
field: ALERT_INSTANCE_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_INSTANCE_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
filter: [
{
match_all: {},
},
],
},
},
pagination: {
activePage: 0,
querySize: 25,
},
language: 'kuery',
sort: [
{
field: '@timestamp',
direction: Direction.desc,
type: 'number',
},
],
timerange: {
from: FROM,
to: TO,
interval: '12h',
},
});
describe('Timeline - Events', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
});
const authorizedSecSpace1 = [secOnly, secOnlyRead];
const authorizedInAllSpaces = [superUser, globalRead];
const unauthorized = [noKibanaPrivileges];
[...authorizedSecSpace1, ...authorizedInAllSpaces].forEach(({ username, password }) => {
it(`${username} - should return a 404 when accessing a spaces route`, async () => {
await supertestWithoutAuth
.post(`${getSpaceUrlPrefix(SPACE_1)}${TEST_URL}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
...getPostBody(),
defaultIndex: ['.alerts-*'],
entityType: 'alerts',
alertConsumers: ['siem'],
})
.expect(404);
});
});
[...authorizedInAllSpaces].forEach(({ username, password }) => {
it(`${username} - should return 200 for authorized users`, async () => {
await supertestWithoutAuth
.post(`${getSpaceUrlPrefix()}${TEST_URL}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
...getPostBody(),
alertConsumers: ['siem', 'apm'],
})
.expect(200);
});
});
[...unauthorized].forEach(({ username, password }) => {
it(`${username} - should return 403 for unauthorized users`, async () => {
await supertestWithoutAuth
.post(`${getSpaceUrlPrefix()}${TEST_URL}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
...getPostBody(),
alertConsumers: ['siem', 'apm'],
})
// TODO - This should be updated to be a 403 once this ticket is resolved
// https://github.com/elastic/kibana/issues/106005
.expect(500);
});
});
});
};

View file

@ -1,31 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import {
createUsersAndRoles,
deleteUsersAndRoles,
} from '../../../../rule_registry/common/lib/authentication';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile, getService }: FtrProviderContext): void => {
describe('timeline security only: trial', function () {
// Fastest ciGroup for the moment.
this.tags('ciGroup5');
before(async () => {
await createUsersAndRoles(getService);
});
after(async () => {
await deleteUsersAndRoles(getService);
});
// Basic
loadTestFile(require.resolve('./events'));
});
};

View file

@ -1,16 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('spaces_only', {
license: 'trial',
disabledPlugins: ['security'],
ssl: false,
testFiles: [require.resolve('./tests')],
});

View file

@ -1,116 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { JsonObject } from '@kbn/utility-types';
import expect from '@kbn/expect';
import { ALERT_INSTANCE_ID, ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { FtrProviderContext } from '../../../rule_registry/common/ftr_provider_context';
import { getSpaceUrlPrefix } from '../../../rule_registry/common/lib/authentication/spaces';
import {
Direction,
TimelineEventsQueries,
} from '../../../../plugins/security_solution/common/search_strategy';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const TO = '3000-01-01T00:00:00.000Z';
const FROM = '2000-01-01T00:00:00.000Z';
const TEST_URL = '/internal/search/timelineSearchStrategy/';
const SPACE1 = 'space1';
const OTHER = 'other';
const getPostBody = (): JsonObject => ({
defaultIndex: ['.alerts-*'],
entityType: 'alerts',
docValueFields: [
{
field: '@timestamp',
},
{
field: ALERT_RULE_CONSUMER,
},
{
field: ALERT_INSTANCE_ID,
},
{
field: 'event.kind',
},
],
factoryQueryType: TimelineEventsQueries.all,
fieldRequested: ['@timestamp', 'message', ALERT_RULE_CONSUMER, ALERT_INSTANCE_ID, 'event.kind'],
fields: [],
filterQuery: {
bool: {
filter: [
{
match_all: {},
},
],
},
},
pagination: {
activePage: 0,
querySize: 25,
},
language: 'kuery',
sort: [
{
field: '@timestamp',
direction: Direction.desc,
type: 'number',
},
],
timerange: {
from: FROM,
to: TO,
interval: '12h',
},
});
describe('Timeline - Events', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
});
it('should handle alerts request appropriately', async () => {
const resp = await supertest
.post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
...getPostBody(),
alertConsumers: ['siem', 'apm'],
})
.expect(200);
// there's 5 total alerts, one is assigned to space2 only
expect(resp.body.totalCount).to.be(4);
});
it('should not return alerts from another space', async () => {
const resp = await supertest
.post(`${getSpaceUrlPrefix(OTHER)}${TEST_URL}`)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
...getPostBody(),
alertConsumers: ['siem', 'apm'],
})
.expect(200);
expect(resp.body.totalCount).to.be(0);
});
});
};

View file

@ -1,28 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { createSpaces, deleteSpaces } from '../../../rule_registry/common/lib/authentication';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile, getService }: FtrProviderContext): void => {
describe('timeline spaces only: trial', function () {
// Fastest ciGroup for the moment.
this.tags('ciGroup5');
before(async () => {
await createSpaces(getService);
});
after(async () => {
await deleteSpaces(getService);
});
// Basic
loadTestFile(require.resolve('./events'));
});
};

View file

@ -126,6 +126,47 @@ const GlobalRead: User = {
},
};
const FooAll: User = {
username: 'foo_all',
fullName: 'foo_all',
password: 'foo_all-password',
role: {
name: 'foo_all_role',
kibana: [
{
feature: {
foo: ['all'],
},
spaces: ['*'],
},
],
},
};
const FooRead: User = {
username: 'foo_read',
fullName: 'foo_read',
password: 'foo_read-password',
role: {
name: 'foo_read_role',
kibana: [
{
feature: {
foo: ['read'],
},
spaces: ['*'],
},
],
},
};
interface FooAll extends User {
username: 'foo_all';
}
interface FooRead extends User {
username: 'foo_read';
}
const EverythingSpaceAll: User = {
username: 'everything_space_all',
fullName: 'everything_space_all',
@ -194,6 +235,8 @@ export const Users: User[] = [
DualPrivilegesRead,
GlobalAll,
GlobalRead,
FooAll,
FooRead,
EverythingSpaceAll,
EverythingSpaceRead,
NothingSpaceAll,
@ -349,6 +392,42 @@ const GlobalReadAtNothingSpace: GlobalReadAtNothingSpace = {
space: NothingSpace,
};
interface FooAllAtEverythingSpace extends Scenario {
id: 'foo_all at everything_space';
}
const FooAllAtEverythingSpace: FooAllAtEverythingSpace = {
id: 'foo_all at everything_space',
user: FooAll,
space: EverythingSpace,
};
interface FooAllAtNothingSpace extends Scenario {
id: 'foo_all at nothing_space';
}
const FooAllAtNothingSpace: FooAllAtNothingSpace = {
id: 'foo_all at nothing_space',
user: FooAll,
space: NothingSpace,
};
interface FooReadAtEverythingSpace extends Scenario {
id: 'foo_read at everything_space';
}
const FooReadAtEverythingSpace: FooReadAtEverythingSpace = {
id: 'foo_read at everything_space',
user: FooRead,
space: EverythingSpace,
};
interface FooReadAtNothingSpace extends Scenario {
id: 'foo_read at nothing_space';
}
const FooReadAtNothingSpace: FooReadAtNothingSpace = {
id: 'foo_read at nothing_space',
user: FooRead,
space: NothingSpace,
};
interface EverythingSpaceAllAtEverythingSpace extends Scenario {
id: 'everything_space_all at everything_space';
}
@ -421,30 +500,7 @@ const NothingSpaceReadAtNothingSpace: NothingSpaceReadAtNothingSpace = {
space: NothingSpace,
};
export const UserAtSpaceScenarios: [
NoKibanaPrivilegesAtEverythingSpace,
NoKibanaPrivilegesAtNothingSpace,
SuperuserAtEverythingSpace,
SuperuserAtNothingSpace,
LegacyAllAtEverythingSpace,
LegacyAllAtNothingSpace,
DualPrivilegesAllAtEverythingSpace,
DualPrivilegesAllAtNothingSpace,
DualPrivilegesReadAtEverythingSpace,
DualPrivilegesReadAtNothingSpace,
GlobalAllAtEverythingSpace,
GlobalAllAtNothingSpace,
GlobalReadAtEverythingSpace,
GlobalReadAtNothingSpace,
EverythingSpaceAllAtEverythingSpace,
EverythingSpaceAllAtNothingSpace,
EverythingSpaceReadAtEverythingSpace,
EverythingSpaceReadAtNothingSpace,
NothingSpaceAllAtEverythingSpace,
NothingSpaceAllAtNothingSpace,
NothingSpaceReadAtEverythingSpace,
NothingSpaceReadAtNothingSpace
] = [
export const UserAtSpaceScenarios = [
NoKibanaPrivilegesAtEverythingSpace,
NoKibanaPrivilegesAtNothingSpace,
SuperuserAtEverythingSpace,
@ -459,6 +515,10 @@ export const UserAtSpaceScenarios: [
GlobalAllAtNothingSpace,
GlobalReadAtEverythingSpace,
GlobalReadAtNothingSpace,
FooAllAtEverythingSpace,
FooAllAtNothingSpace,
FooReadAtEverythingSpace,
FooReadAtNothingSpace,
EverythingSpaceAllAtEverythingSpace,
EverythingSpaceAllAtNothingSpace,
EverythingSpaceReadAtEverythingSpace,
@ -467,4 +527,4 @@ export const UserAtSpaceScenarios: [
NothingSpaceAllAtNothingSpace,
NothingSpaceReadAtEverythingSpace,
NothingSpaceReadAtNothingSpace,
];
] as const;

View file

@ -85,6 +85,18 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
expect(uiCapabilities.value!.catalogue).to.eql(expected);
break;
}
case 'foo_all at everything_space':
case 'foo_read at everything_space': {
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('catalogue');
// everything except foo is disabled
const expected = mapValues(
uiCapabilities.value!.catalogue,
(enabled, catalogueId) => catalogueId === 'foo'
);
expect(uiCapabilities.value!.catalogue).to.eql(expected);
break;
}
// the nothing_space has no Kibana features enabled, so even if we have
// privileges to perform these actions, we won't be able to.
// Note that ES features may still be enabled if the user has privileges, since
@ -116,6 +128,8 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
// the nothing_space has no Kibana features enabled, so even if we have
// privileges to perform these actions, we won't be able to.
case 'global_read at nothing_space':
case 'foo_all at nothing_space':
case 'foo_read at nothing_space':
case 'dual_privileges_all at nothing_space':
case 'dual_privileges_read at nothing_space':
case 'nothing_space_all at nothing_space':

View file

@ -26,6 +26,7 @@ export default function fooTests({ getService }: FtrProviderContext) {
// these users have a read/write view
case 'superuser at everything_space':
case 'global_all at everything_space':
case 'foo_all at everything_space':
case 'dual_privileges_all at everything_space':
case 'everything_space_all at everything_space':
expect(uiCapabilities.success).to.be(true);
@ -39,6 +40,7 @@ export default function fooTests({ getService }: FtrProviderContext) {
break;
// these users have a read only view
case 'global_read at everything_space':
case 'foo_read at everything_space':
case 'dual_privileges_read at everything_space':
case 'everything_space_read at everything_space':
expect(uiCapabilities.success).to.be(true);
@ -55,6 +57,8 @@ export default function fooTests({ getService }: FtrProviderContext) {
case 'superuser at nothing_space':
case 'global_all at nothing_space':
case 'global_read at nothing_space':
case 'foo_all at nothing_space':
case 'foo_read at nothing_space':
case 'dual_privileges_all at nothing_space':
case 'dual_privileges_read at nothing_space':
case 'nothing_space_all at nothing_space':

View file

@ -62,11 +62,21 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
)
);
break;
case 'foo_all at everything_space':
case 'foo_read at everything_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('navLinks');
expect(uiCapabilities.value!.navLinks).to.eql(
navLinksBuilder.only('kibana', 'foo', 'management')
);
break;
case 'superuser at nothing_space':
case 'global_all at nothing_space':
case 'global_read at nothing_space':
case 'foo_all at nothing_space':
case 'foo_read at nothing_space':
case 'dual_privileges_all at nothing_space':
case 'dual_privileges_read at nothing_space':
case 'global_read at nothing_space':
case 'nothing_space_all at nothing_space':
case 'nothing_space_read at nothing_space':
case 'no_kibana_privileges at everything_space':

View file

@ -1,11 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createTestConfig } from '../common/config';
// eslint-disable-next-line import/no-default-export
export default createTestConfig('security_only', { disabledPlugins: ['spaces'], license: 'trial' });

View file

@ -1,216 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { CustomRoleSpecification, User } from '../common/types';
// For all scenarios, we define both an instance in addition
// to a "type" definition so that we can use the exhaustive switch in
// typescript to ensure all scenarios are handled.
const allRole: CustomRoleSpecification = {
name: 'all_role',
kibana: [
{
base: ['all'],
spaces: ['*'],
},
],
};
interface NoKibanaPrivileges extends User {
username: 'no_kibana_privileges';
}
const NoKibanaPrivileges: NoKibanaPrivileges = {
username: 'no_kibana_privileges',
fullName: 'no_kibana_privileges',
password: 'no_kibana_privileges-password',
role: {
name: 'no_kibana_privileges',
elasticsearch: {
indices: [
{
names: ['foo'],
privileges: ['all'],
},
],
},
},
};
interface Superuser extends User {
username: 'superuser';
}
const Superuser: Superuser = {
username: 'superuser',
fullName: 'superuser',
password: 'superuser-password',
role: {
name: 'superuser',
},
};
interface LegacyAll extends User {
username: 'legacy_all';
}
const LegacyAll: LegacyAll = {
username: 'legacy_all',
fullName: 'legacy_all',
password: 'legacy_all-password',
role: {
name: 'legacy_all_role',
elasticsearch: {
indices: [
{
names: ['.kibana*'],
privileges: ['all'],
},
],
},
},
};
interface DualPrivilegesAll extends User {
username: 'dual_privileges_all';
}
const DualPrivilegesAll: DualPrivilegesAll = {
username: 'dual_privileges_all',
fullName: 'dual_privileges_all',
password: 'dual_privileges_all-password',
role: {
name: 'dual_privileges_all_role',
elasticsearch: {
indices: [
{
names: ['.kibana*'],
privileges: ['all'],
},
],
},
kibana: [
{
base: ['all'],
spaces: ['*'],
},
],
},
};
interface DualPrivilegesRead extends User {
username: 'dual_privileges_read';
}
const DualPrivilegesRead: DualPrivilegesRead = {
username: 'dual_privileges_read',
fullName: 'dual_privileges_read',
password: 'dual_privileges_read-password',
role: {
name: 'dual_privileges_read_role',
elasticsearch: {
indices: [
{
names: ['.kibana*'],
privileges: ['read'],
},
],
},
kibana: [
{
base: ['read'],
spaces: ['*'],
},
],
},
};
interface All extends User {
username: 'all';
}
const All: All = {
username: 'all',
fullName: 'all',
password: 'all-password',
role: allRole,
};
interface Read extends User {
username: 'read';
}
const Read: Read = {
username: 'read',
fullName: 'read',
password: 'read-password',
role: {
name: 'read_role',
kibana: [
{
base: ['read'],
spaces: ['*'],
},
],
},
};
interface FooAll extends User {
username: 'foo_all';
}
const FooAll: FooAll = {
username: 'foo_all',
fullName: 'foo_all',
password: 'foo_all-password',
role: {
name: 'foo_all_role',
kibana: [
{
feature: {
foo: ['all'],
},
spaces: ['*'],
},
],
},
};
interface FooRead extends User {
username: 'foo_read';
}
const FooRead: FooRead = {
username: 'foo_read',
fullName: 'foo_read',
password: 'foo_read-password',
role: {
name: 'foo_read_role',
kibana: [
{
feature: {
foo: ['read'],
},
spaces: ['*'],
},
],
},
};
export const UserScenarios: [
NoKibanaPrivileges,
Superuser,
LegacyAll,
DualPrivilegesAll,
DualPrivilegesRead,
All,
Read,
FooAll,
FooRead
] = [
NoKibanaPrivileges,
Superuser,
LegacyAll,
DualPrivilegesAll,
DualPrivilegesRead,
All,
Read,
FooAll,
FooRead,
];

View file

@ -1,111 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { mapValues } from 'lodash';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
import { UserScenarios } from '../scenarios';
export default function catalogueTests({ getService }: FtrProviderContext) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
const esFeatureExceptions = [
'security',
'index_lifecycle_management',
'snapshot_restore',
'rollup_jobs',
'reporting',
'transform',
'watcher',
];
describe('catalogue', () => {
UserScenarios.forEach((scenario) => {
it(`${scenario.fullName}`, async () => {
const uiCapabilities = await uiCapabilitiesService.get({
credentials: {
username: scenario.username,
password: scenario.password,
},
});
switch (scenario.username) {
case 'superuser': {
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('catalogue');
// everything is enabled
const expected = mapValues(uiCapabilities.value!.catalogue, () => true);
expect(uiCapabilities.value!.catalogue).to.eql(expected);
break;
}
case 'all':
case 'dual_privileges_all': {
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('catalogue');
// everything except ml, monitoring, and ES features are enabled
const expected = mapValues(
uiCapabilities.value!.catalogue,
(enabled, catalogueId) =>
catalogueId !== 'ml' &&
catalogueId !== 'monitoring' &&
catalogueId !== 'ml_file_data_visualizer' &&
catalogueId !== 'osquery' &&
!esFeatureExceptions.includes(catalogueId)
);
expect(uiCapabilities.value!.catalogue).to.eql(expected);
break;
}
case 'read':
case 'dual_privileges_read': {
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('catalogue');
// everything except ml and monitoring and enterprise search is enabled
const exceptions = [
'ml',
'ml_file_data_visualizer',
'monitoring',
'enterpriseSearch',
'appSearch',
'workplaceSearch',
'osquery',
...esFeatureExceptions,
];
const expected = mapValues(
uiCapabilities.value!.catalogue,
(enabled, catalogueId) => !exceptions.includes(catalogueId)
);
expect(uiCapabilities.value!.catalogue).to.eql(expected);
break;
}
case 'foo_all':
case 'foo_read': {
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('catalogue');
// only foo is enabled
const expected = mapValues(
uiCapabilities.value!.catalogue,
(value, catalogueId) => catalogueId === 'foo'
);
expect(uiCapabilities.value!.catalogue).to.eql(expected);
break;
}
// these users have no access to even get the ui capabilities
case 'legacy_all':
case 'no_kibana_privileges':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('catalogue');
// only foo is enabled
const expected = mapValues(uiCapabilities.value!.catalogue, () => false);
expect(uiCapabilities.value!.catalogue).to.eql(expected);
break;
default:
throw new UnreachableError(scenario);
}
});
});
});
}

View file

@ -1,72 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
import { UserScenarios } from '../scenarios';
export default function fooTests({ getService }: FtrProviderContext) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
describe('foo', () => {
UserScenarios.forEach((scenario) => {
it(`${scenario.fullName}`, async () => {
const uiCapabilities = await uiCapabilitiesService.get({
credentials: {
username: scenario.username,
password: scenario.password,
},
});
switch (scenario.username) {
// these users have a read/write view of Foo
case 'superuser':
case 'all':
case 'dual_privileges_all':
case 'foo_all':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('foo');
expect(uiCapabilities.value!.foo).to.eql({
create: true,
edit: true,
delete: true,
show: true,
});
break;
// these users have a read-only view of Foo
case 'read':
case 'dual_privileges_read':
case 'foo_read':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('foo');
expect(uiCapabilities.value!.foo).to.eql({
create: false,
edit: false,
delete: false,
show: true,
});
break;
// these users have no access to even get the ui capabilities
case 'legacy_all':
case 'no_kibana_privileges':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('foo');
expect(uiCapabilities.value!.foo).to.eql({
create: false,
edit: false,
delete: false,
show: false,
});
break;
// all other users can't do anything with Foo
default:
throw new UnreachableError(scenario);
}
});
});
});
}

View file

@ -1,55 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { isCustomRoleSpecification } from '../../common/types';
import { UserScenarios } from '../scenarios';
export default function uiCapabilitesTests({ loadTestFile, getService }: FtrProviderContext) {
const securityService = getService('security');
describe('ui capabilities', function () {
this.tags('ciGroup9');
before(async () => {
for (const user of UserScenarios) {
const roles = [...(user.role ? [user.role] : []), ...(user.roles ? user.roles : [])];
await securityService.user.create(user.username, {
password: user.password,
full_name: user.fullName,
roles: roles.map((role) => role.name),
});
for (const role of roles) {
if (isCustomRoleSpecification(role)) {
await securityService.role.create(role.name, {
kibana: role.kibana,
});
}
}
}
});
after(async () => {
for (const user of UserScenarios) {
await securityService.user.delete(user.username);
const roles = [...(user.role ? [user.role] : []), ...(user.roles ? user.roles : [])];
for (const role of roles) {
if (isCustomRoleSpecification(role)) {
await securityService.role.delete(role.name);
}
}
}
});
loadTestFile(require.resolve('./catalogue'));
loadTestFile(require.resolve('./foo'));
loadTestFile(require.resolve('./nav_links'));
});
}

View file

@ -1,83 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { NavLinksBuilder } from '../../common/nav_links_builder';
import { FeaturesService } from '../../common/services';
import { UICapabilitiesService } from '../../common/services/ui_capabilities';
import { UserScenarios } from '../scenarios';
export default function navLinksTests({ getService }: FtrProviderContext) {
const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities');
const featuresService: FeaturesService = getService('features');
describe('navLinks', () => {
let navLinksBuilder: NavLinksBuilder;
before(async () => {
const features = await featuresService.get();
navLinksBuilder = new NavLinksBuilder(features);
});
UserScenarios.forEach((scenario) => {
it(`${scenario.fullName}`, async () => {
const uiCapabilities = await uiCapabilitiesService.get({
credentials: {
username: scenario.username,
password: scenario.password,
},
});
switch (scenario.username) {
case 'superuser':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('navLinks');
expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.all());
break;
case 'all':
case 'dual_privileges_all':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('navLinks');
expect(uiCapabilities.value!.navLinks).to.eql(
navLinksBuilder.except('ml', 'monitoring', 'osquery')
);
break;
case 'read':
case 'dual_privileges_read':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('navLinks');
expect(uiCapabilities.value!.navLinks).to.eql(
navLinksBuilder.except(
'ml',
'monitoring',
'enterpriseSearch',
'appSearch',
'workplaceSearch',
'osquery'
)
);
break;
case 'foo_all':
case 'foo_read':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('navLinks');
expect(uiCapabilities.value!.navLinks).to.eql(
navLinksBuilder.only('management', 'foo', 'kibana')
);
break;
case 'legacy_all':
case 'no_kibana_privileges':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('navLinks');
expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.only('management'));
break;
default:
throw new UnreachableError(scenario);
}
});
});
});
}

View file

@ -38,8 +38,4 @@ const FooDisabledSpace: FooDisabledSpace = {
disabledFeatures: ['foo'],
};
export const SpaceScenarios: [EverythingSpace, NothingSpace, FooDisabledSpace] = [
EverythingSpace,
NothingSpace,
FooDisabledSpace,
];
export const SpaceScenarios = [EverythingSpace, NothingSpace, FooDisabledSpace] as const;