;
@@ -59,6 +60,7 @@ function createKibanaRequestMock({
method = 'get',
socket = new Socket(),
routeTags,
+ routeAuthRequired,
validation = {},
}: RequestFixtureOptions
= {}) {
const queryString = stringify(query, { sort: false });
@@ -77,7 +79,9 @@ function createKibanaRequestMock
({
query: queryString,
search: queryString ? `?${queryString}` : queryString,
},
- route: { settings: { tags: routeTags } },
+ route: {
+ settings: { tags: routeTags, auth: routeAuthRequired },
+ },
raw: {
req: { socket },
},
diff --git a/src/core/server/saved_objects/mappings/types.ts b/src/core/server/saved_objects/mappings/types.ts
index 578fdcea3718e4..bc556c0429981d 100644
--- a/src/core/server/saved_objects/mappings/types.ts
+++ b/src/core/server/saved_objects/mappings/types.ts
@@ -45,6 +45,9 @@
* @public
*/
export interface SavedObjectsTypeMappingDefinition {
+ /** The dynamic property of the mapping. either `false` or 'strict'. Defaults to strict */
+ dynamic?: false | 'strict';
+ /** The underlying properties of the type mapping */
properties: SavedObjectsMappingProperties;
}
diff --git a/src/core/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap b/src/core/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap
index e82fbfc85dfa02..68f90ea70a0c6e 100644
--- a/src/core/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap
+++ b/src/core/server/saved_objects/migrations/core/__snapshots__/build_active_mappings.test.ts.snap
@@ -60,3 +60,82 @@ Object {
},
}
`;
+
+exports[`buildActiveMappings handles the \`dynamic\` property of types 1`] = `
+Object {
+ "_meta": Object {
+ "migrationMappingPropertyHashes": Object {
+ "config": "87aca8fdb053154f11383fce3dbf3edf",
+ "firstType": "635418ab953d81d93f1190b70a8d3f57",
+ "migrationVersion": "4a1746014a75ade3a714e1db5763276f",
+ "namespace": "2f4316de49999235636386fe51dc06c1",
+ "references": "7997cf5a56cc02bdc9c93361bde732b0",
+ "secondType": "72d57924f415fbadb3ee293b67d233ab",
+ "thirdType": "510f1f0adb69830cf8a1c5ce2923ed82",
+ "type": "2f4316de49999235636386fe51dc06c1",
+ "updated_at": "00da57df13e94e9d98437d13ace4bfe0",
+ },
+ },
+ "dynamic": "strict",
+ "properties": Object {
+ "config": Object {
+ "dynamic": "true",
+ "properties": Object {
+ "buildNum": Object {
+ "type": "keyword",
+ },
+ },
+ },
+ "firstType": Object {
+ "dynamic": "strict",
+ "properties": Object {
+ "field": Object {
+ "type": "keyword",
+ },
+ },
+ },
+ "migrationVersion": Object {
+ "dynamic": "true",
+ "type": "object",
+ },
+ "namespace": Object {
+ "type": "keyword",
+ },
+ "references": Object {
+ "properties": Object {
+ "id": Object {
+ "type": "keyword",
+ },
+ "name": Object {
+ "type": "keyword",
+ },
+ "type": Object {
+ "type": "keyword",
+ },
+ },
+ "type": "nested",
+ },
+ "secondType": Object {
+ "dynamic": false,
+ "properties": Object {
+ "field": Object {
+ "type": "long",
+ },
+ },
+ },
+ "thirdType": Object {
+ "properties": Object {
+ "field": Object {
+ "type": "text",
+ },
+ },
+ },
+ "type": Object {
+ "type": "keyword",
+ },
+ "updated_at": Object {
+ "type": "date",
+ },
+ },
+}
+`;
diff --git a/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts b/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts
index 9d220cfdf94b70..33e1a395e64a2b 100644
--- a/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts
+++ b/src/core/server/saved_objects/migrations/core/build_active_mappings.test.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { IndexMapping } from './../../mappings';
+import { IndexMapping, SavedObjectsTypeMappingDefinitions } from './../../mappings';
import { buildActiveMappings, diffMappings } from './build_active_mappings';
describe('buildActiveMappings', () => {
@@ -49,6 +49,23 @@ describe('buildActiveMappings', () => {
);
});
+ test('handles the `dynamic` property of types', () => {
+ const typeMappings: SavedObjectsTypeMappingDefinitions = {
+ firstType: {
+ dynamic: 'strict',
+ properties: { field: { type: 'keyword' } },
+ },
+ secondType: {
+ dynamic: false,
+ properties: { field: { type: 'long' } },
+ },
+ thirdType: {
+ properties: { field: { type: 'text' } },
+ },
+ };
+ expect(buildActiveMappings(typeMappings)).toMatchSnapshot();
+ });
+
test('generated hashes are stable', () => {
const properties = {
aaa: { type: 'keyword', fields: { a: { type: 'keyword' }, b: { type: 'text' } } },
diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts
index a1e2c1e8dbf263..554acf8d43dcb9 100644
--- a/src/core/server/saved_objects/saved_objects_service.test.ts
+++ b/src/core/server/saved_objects/saved_objects_service.test.ts
@@ -232,6 +232,36 @@ describe('SavedObjectsService', () => {
expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1);
});
+ it('throws when calling setup APIs once started', async () => {
+ const coreContext = createCoreContext({ skipMigration: false });
+ const soService = new SavedObjectsService(coreContext);
+ const setup = await soService.setup(createSetupDeps());
+ await soService.start({});
+
+ expect(() => {
+ setup.setClientFactoryProvider(jest.fn());
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"cannot call \`setClientFactoryProvider\` after service startup."`
+ );
+
+ expect(() => {
+ setup.addClientWrapper(0, 'dummy', jest.fn());
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"cannot call \`addClientWrapper\` after service startup."`
+ );
+
+ expect(() => {
+ setup.registerType({
+ name: 'someType',
+ hidden: false,
+ namespaceAgnostic: false,
+ mappings: { properties: {} },
+ });
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"cannot call \`registerType\` after service startup."`
+ );
+ });
+
describe('#getTypeRegistry', () => {
it('returns the internal type registry of the service', async () => {
const coreContext = createCoreContext({ skipMigration: false });
diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts
index da8f7ab96d6891..d5a9f474209711 100644
--- a/src/core/server/saved_objects/saved_objects_service.ts
+++ b/src/core/server/saved_objects/saved_objects_service.ts
@@ -61,6 +61,9 @@ import { registerRoutes } from './routes';
* the factory provided to `setClientFactory` and wrapped by all wrappers
* registered through `addClientWrapper`.
*
+ * All the setup APIs will throw if called after the service has started, and therefor cannot be used
+ * from legacy plugin code. Legacy plugins should use the legacy savedObject service until migrated.
+ *
* @example
* ```ts
* import { SavedObjectsClient, CoreSetup } from 'src/core/server';
@@ -275,6 +278,7 @@ export class SavedObjectsService
private migrator$ = new Subject();
private typeRegistry = new SavedObjectTypeRegistry();
private validations: PropertyValidators = {};
+ private started = false;
constructor(private readonly coreContext: CoreContext) {
this.logger = coreContext.logger.get('savedobjects-service');
@@ -316,12 +320,18 @@ export class SavedObjectsService
return {
setClientFactoryProvider: provider => {
+ if (this.started) {
+ throw new Error('cannot call `setClientFactoryProvider` after service startup.');
+ }
if (this.clientFactoryProvider) {
throw new Error('custom client factory is already set, and can only be set once');
}
this.clientFactoryProvider = provider;
},
addClientWrapper: (priority, id, factory) => {
+ if (this.started) {
+ throw new Error('cannot call `addClientWrapper` after service startup.');
+ }
this.clientFactoryWrappers.push({
priority,
id,
@@ -329,6 +339,9 @@ export class SavedObjectsService
});
},
registerType: type => {
+ if (this.started) {
+ throw new Error('cannot call `registerType` after service startup.');
+ }
this.typeRegistry.registerType(type);
},
};
@@ -415,6 +428,8 @@ export class SavedObjectsService
clientProvider.addClientWrapperFactory(priority, id, factory);
});
+ this.started = true;
+
return {
migrator,
clientProvider,
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index 8f4feb7169651e..42bc1ce214b191 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -2058,7 +2058,7 @@ export interface SavedObjectsType {
// @public
export interface SavedObjectsTypeMappingDefinition {
- // (undocumented)
+ dynamic?: false | 'strict';
properties: SavedObjectsMappingProperties;
}
diff --git a/src/dev/npm/integration_tests/installed_packages.test.ts b/src/dev/npm/integration_tests/installed_packages.test.ts
index 5c942005d2eee3..75cd0e56076982 100644
--- a/src/dev/npm/integration_tests/installed_packages.test.ts
+++ b/src/dev/npm/integration_tests/installed_packages.test.ts
@@ -41,7 +41,7 @@ describe('src/dev/npm/installed_packages', () => {
includeDev: true,
}),
]);
- }, 60 * 1000);
+ }, 360 * 1000);
it('reads all installed packages of a module', () => {
expect(fixture1Packages).toEqual([
diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts
index 302527e4ed549f..7a5d927d0f219a 100644
--- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts
+++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts
@@ -40,7 +40,7 @@ import {
import { buildTabularInspectorData } from './build_tabular_inspector_data';
import { calculateObjectHash } from '../../../../visualizations/public';
import { tabifyAggResponse } from '../../../../../core_plugins/data/public';
-import { PersistedState } from '../../../../../ui/public/persisted_state';
+import { PersistedState } from '../../../../../../plugins/visualizations/public';
import { Adapters } from '../../../../../../plugins/inspector/public';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getQueryService, getIndexPatterns } from '../../../../../../plugins/data/public/services';
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts
index 257ba8a4711b0a..9ca84735cac168 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts
@@ -59,6 +59,10 @@ export interface RenderDeps {
savedDashboards: SavedObjectLoader;
dashboardConfig: KibanaLegacyStart['dashboardConfig'];
dashboardCapabilities: any;
+ embeddableCapabilities: {
+ visualizeCapabilities: any;
+ mapsCapabilities: any;
+ };
uiSettings: IUiSettingsClient;
chrome: ChromeStart;
addBasePath: (path: string) => string;
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
index 84dd73882d1343..af3347afa9c5f2 100644
--- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
+++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx
@@ -108,6 +108,7 @@ export class DashboardAppController {
embeddable,
share,
dashboardCapabilities,
+ embeddableCapabilities: { visualizeCapabilities, mapsCapabilities },
data: { query: queryService },
core: {
notifications,
@@ -134,7 +135,6 @@ export class DashboardAppController {
} = syncQueryStateWithUrl(queryService, kbnUrlStateStorage);
let lastReloadRequestTime = 0;
-
const dash = ($scope.dash = $route.current.locals.dash);
if (dash.id) {
chrome.docTitle.change(dash.title);
@@ -180,11 +180,18 @@ export class DashboardAppController {
dashboardStateManager.getIsViewMode() &&
!dashboardConfig.getHideWriteControls();
- const getIsEmptyInReadonlyMode = () =>
- !dashboardStateManager.getPanels().length &&
- !getShouldShowEditHelp() &&
- !getShouldShowViewHelp() &&
- dashboardConfig.getHideWriteControls();
+ const shouldShowUnauthorizedEmptyState = () => {
+ const readonlyMode =
+ !dashboardStateManager.getPanels().length &&
+ !getShouldShowEditHelp() &&
+ !getShouldShowViewHelp() &&
+ dashboardConfig.getHideWriteControls();
+ const userHasNoPermissions =
+ !dashboardStateManager.getPanels().length &&
+ !visualizeCapabilities.save &&
+ !mapsCapabilities.save;
+ return readonlyMode || userHasNoPermissions;
+ };
const addVisualization = () => {
navActions[TopNavIds.VISUALIZE]();
@@ -250,7 +257,7 @@ export class DashboardAppController {
}
const shouldShowEditHelp = getShouldShowEditHelp();
const shouldShowViewHelp = getShouldShowViewHelp();
- const isEmptyInReadonlyMode = getIsEmptyInReadonlyMode();
+ const isEmptyInReadonlyMode = shouldShowUnauthorizedEmptyState();
return {
id: dashboardStateManager.savedDashboard.id || '',
filters: queryFilter.getFilters(),
@@ -307,7 +314,7 @@ export class DashboardAppController {
dashboardContainer.renderEmpty = () => {
const shouldShowEditHelp = getShouldShowEditHelp();
const shouldShowViewHelp = getShouldShowViewHelp();
- const isEmptyInReadOnlyMode = getIsEmptyInReadonlyMode();
+ const isEmptyInReadOnlyMode = shouldShowUnauthorizedEmptyState();
const isEmptyState = shouldShowEditHelp || shouldShowViewHelp || isEmptyInReadOnlyMode;
return isEmptyState ? (
diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/synopsis.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/synopsis.js
index 968b8eb64def5c..f43c377b4e5b90 100644
--- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/synopsis.js
+++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/synopsis.js
@@ -37,13 +37,7 @@ export function Synopsis({
if (iconUrl) {
optionalImg = ;
} else if (iconType) {
- optionalImg = (
-
- );
+ optionalImg = ;
}
const classes = classNames('homSynopsis__card', {
diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
index d52d31c2dd79e2..b8ee7cd378750c 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts
@@ -29,7 +29,6 @@ export { State } from 'ui/state_management/state';
export { GlobalStateProvider } from 'ui/state_management/global_state';
// @ts-ignore
export { StateManagementConfigProvider } from 'ui/state_management/config_provider';
-export { PersistedState } from 'ui/persisted_state';
export { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
// @ts-ignore
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/make_stateful.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/make_stateful.ts
index 137d4de1fe9a82..8384585108a594 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/make_stateful.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/lib/make_stateful.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { PersistedState } from '../../../legacy_imports';
+import { PersistedState } from '../../../../../../../../plugins/visualizations/public';
import { ReduxLikeStateContainer } from '../../../../../../../../plugins/kibana_utils/public';
import { VisualizeAppState, VisualizeAppStateTransitions } from '../../types';
diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
index 3b6ecb45b83b34..d95939170419b7 100644
--- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
+++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts
@@ -19,9 +19,10 @@
import { TimeRange, Query, Filter, DataPublicPluginStart } from 'src/plugins/data/public';
import { IEmbeddableStart } from 'src/plugins/embeddable/public';
+import { PersistedState } from 'src/plugins/visualizations/public';
import { LegacyCoreStart } from 'kibana/public';
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
-import { VisSavedObject, PersistedState } from '../legacy_imports';
+import { VisSavedObject } from '../legacy_imports';
export type PureVisState = ReturnType;
diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
index 8615bcdd1bfbd5..d3b843eaaec9f2 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx
@@ -21,13 +21,13 @@ import React, { useMemo, useState, useCallback, KeyboardEventHandler, useEffect
import { get, isEqual } from 'lodash';
import { i18n } from '@kbn/i18n';
import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
-
import { Vis } from 'src/legacy/core_plugins/visualizations/public';
-import { PersistedState, AggGroupNames } from '../../legacy_imports';
+import { AggGroupNames } from '../../legacy_imports';
import { DefaultEditorNavBar, OptionTab } from './navbar';
import { DefaultEditorControls } from './controls';
import { setStateParamValue, useEditorReducer, useEditorFormState, discardChanges } from './state';
import { DefaultEditorAggCommonProps } from '../agg_common_props';
+import { PersistedState } from '../../../../../../plugins/visualizations/public';
interface DefaultEditorSideBarProps {
isCollapsed: boolean;
diff --git a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts
index 5e547eed1c9573..832f73752a99b6 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts
+++ b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts
@@ -48,5 +48,4 @@ export { isValidJson, isValidInterval } from 'ui/agg_types';
export { AggParamOption } from 'ui/agg_types';
export { CidrMask } from 'ui/agg_types';
-export { PersistedState } from 'ui/persisted_state';
export * from 'ui/vis/lib';
diff --git a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx b/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx
index babcb59c6582e6..18fbba1b039b53 100644
--- a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx
+++ b/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx
@@ -17,7 +17,8 @@
* under the License.
*/
-import { IAggConfigs, PersistedState } from './legacy_imports';
+import { PersistedState } from '../../../../plugins/visualizations/public';
+import { IAggConfigs } from './legacy_imports';
import { Vis } from '../../visualizations/public';
export interface VisOptionsProps {
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts
index 401acfc8df7663..a2952b2c83afdd 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/legacy_imports.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-export { PersistedState } from 'ui/persisted_state';
// @ts-ignore
export { defaultFeedbackMessage } from 'ui/vis/default_feedback_message';
// @ts-ignore
diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts
index 576723bad1e434..1f9cbecc2a354b 100644
--- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts
+++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts
@@ -24,10 +24,10 @@ import {
KibanaContext,
Render,
} from '../../../../plugins/expressions/public';
+import { PersistedState } from '../../../../plugins/visualizations/public';
// @ts-ignore
import { metricsRequestHandler } from './request_handler';
-import { PersistedState } from './legacy_imports';
type Input = KibanaContext | null;
type Output = Promise>;
diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts
index 385117be8ae2ea..fb7a157b53a9ac 100644
--- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts
+++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-export { PersistedState } from '../../../ui/public/persisted_state';
export {
AggConfigs,
IAggConfig,
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx
index 5a9a1830ebdf33..33830c45848e43 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx
@@ -19,8 +19,7 @@
import { get } from 'lodash';
import React from 'react';
-
-import { PersistedState } from '../../../legacy_imports';
+import { PersistedState } from '../../../../../../../plugins/visualizations/public';
import { memoizeLast } from '../legacy/memoize';
import { VisualizationChart } from './visualization_chart';
import { VisualizationNoResults } from './visualization_noresults';
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx
index 95fd31049d2336..7b1a18e8066a77 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx
@@ -20,8 +20,7 @@
import React from 'react';
import * as Rx from 'rxjs';
import { debounceTime, filter, share, switchMap } from 'rxjs/operators';
-
-import { PersistedState } from '../../../legacy_imports';
+import { PersistedState } from '../../../../../../../plugins/visualizations/public';
import { Vis, VisualizationController } from '../vis';
import { getUpdateStatus } from '../legacy/update_status';
import { ResizeChecker } from '../../../../../../../plugins/kibana_utils/public';
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts
index 2537caa01cd46d..97e2b8f88172ec 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts
@@ -43,10 +43,10 @@ import {
IExpressionLoaderParams,
ExpressionsStart,
} from '../../../../../../../plugins/expressions/public';
+import { PersistedState } from '../../../../../../../plugins/visualizations/public';
import { buildPipeline } from '../legacy/build_pipeline';
import { Vis } from '../vis';
import { getExpressions, getUiActions } from '../services';
-import { PersistedState } from '../../../legacy_imports';
import { VisSavedObject } from '../types';
const getKeys = (o: T): Array => Object.keys(o) as Array;
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js
index 81224b65f77866..a891140677d603 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js
@@ -29,7 +29,7 @@
import { EventEmitter } from 'events';
import _ from 'lodash';
-import { PersistedState } from '../../../legacy_imports';
+import { PersistedState } from '../../../../../../../plugins/visualizations/public';
import { getTypes } from '../services';
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts
index 4ac0931c5d8655..d98eda4c50ef9f 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts
@@ -19,12 +19,14 @@
import { get } from 'lodash';
import { i18n } from '@kbn/i18n';
-import { VisResponseValue } from '../../../../../../../plugins/visualizations/public';
+import {
+ VisResponseValue,
+ PersistedState,
+} from '../../../../../../../plugins/visualizations/public';
import {
ExpressionFunctionDefinition,
Render,
} from '../../../../../../../plugins/expressions/public';
-import { PersistedState } from '../../../legacy_imports';
import { getTypes, getIndexPatterns, getFilterManager } from '../services';
interface Arguments {
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts
index 6d32a6df5f1ec6..d9af5122eadec6 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { PersistedState } from '../../../legacy_imports';
+import { PersistedState } from '../../../../../../../plugins/visualizations/public';
import { calculateObjectHash } from './calculate_object_hash';
import { Vis } from '../vis';
diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js
index 6f4ab6d708184e..2f36322c67256a 100644
--- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js
+++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis_impl.js
@@ -29,7 +29,8 @@
import { EventEmitter } from 'events';
import _ from 'lodash';
-import { AggConfigs, PersistedState } from '../../legacy_imports';
+import { PersistedState } from '../../../../../../../src/plugins/visualizations/public';
+import { AggConfigs } from '../../legacy_imports';
import { updateVisualizationConfig } from './legacy/vis_update';
import { getTypes } from './services';
diff --git a/src/legacy/ui/public/events.js b/src/legacy/ui/public/events.js
index f0a21a1abd012c..00c92038e7c9f1 100644
--- a/src/legacy/ui/public/events.js
+++ b/src/legacy/ui/public/events.js
@@ -20,19 +20,19 @@
/**
* @name Events
*
- * @extends SimpleEmitter
+ * @extends EventEmitter
*/
import _ from 'lodash';
+import { EventEmitter } from 'events';
import { fatalError } from './notify';
-import { SimpleEmitter } from './utils/simple_emitter';
import { createLegacyClass } from './utils/legacy_class';
import { createDefer } from 'ui/promises';
const location = 'EventEmitter';
export function EventsProvider(Promise) {
- createLegacyClass(Events).inherits(SimpleEmitter);
+ createLegacyClass(Events).inherits(EventEmitter);
function Events() {
Events.Super.call(this);
this._listeners = {};
@@ -79,6 +79,7 @@ export function EventsProvider(Promise) {
*/
Events.prototype.off = function(name, handler) {
if (!name && !handler) {
+ this._listeners = {};
return this.removeAllListeners();
}
diff --git a/src/legacy/ui/public/persisted_state/index.js b/src/legacy/ui/public/persisted_state/index.js
deleted file mode 100644
index ab5a3e7be7d283..00000000000000
--- a/src/legacy/ui/public/persisted_state/index.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-export { PersistedState } from './persisted_state';
diff --git a/src/legacy/ui/public/persisted_state/persisted_state.d.ts b/src/legacy/ui/public/persisted_state/persisted_state.d.ts
deleted file mode 100644
index b5d7513172e76b..00000000000000
--- a/src/legacy/ui/public/persisted_state/persisted_state.d.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// It's currenty hard to properly type PersistedState, since it dynamically
-// inherits the class passed into the constructor. These typings are really pretty bad
-// but needed in the short term to make incremental progress elsewhere. Can't even
-// just use `any` since then typescript complains about using PersistedState as a
-// constructor.
-export class PersistedState {
- constructor(value?: any, path?: any, EmitterClass?: any);
- // method you want typed so far
- [prop: string]: any;
-}
diff --git a/src/legacy/ui/public/persisted_state/persisted_state.js b/src/legacy/ui/public/persisted_state/persisted_state.js
deleted file mode 100644
index 071a39ab2b4f8d..00000000000000
--- a/src/legacy/ui/public/persisted_state/persisted_state.js
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/**
- * @name PersistedState
- *
- * @extends Events
- */
-
-import _ from 'lodash';
-import toPath from 'lodash/internal/toPath';
-import { PersistedStateError } from './errors';
-import { SimpleEmitter } from '../utils/simple_emitter';
-
-function prepSetParams(key, value, path) {
- // key must be the value, set the entire state using it
- if (_.isUndefined(value) && (_.isPlainObject(key) || path.length > 0)) {
- // setting entire tree, swap the key and value to write to the state
- value = key;
- key = undefined;
- }
-
- // ensure the value being passed in is never mutated
- return {
- value: _.cloneDeep(value),
- key: key,
- };
-}
-
-export class PersistedState {
- /**
- *
- * @param value
- * @param path
- * @param EmitterClass {SimpleEmitter} - a SimpleEmitter class that this class will extend. Can be used to
- * inherit a custom event emitter. For example, the EventEmitter is an "angular-ized" version
- * for angular components which automatically triggers a digest loop for every registered
- * handler. TODO: replace angularized SimpleEmitter and force angular callers to handle digest loops manually ala
- * https://github.com/elastic/kibana/issues/13855
- */
- constructor(value, path, EmitterClass = SimpleEmitter) {
- EmitterClass.call(this);
-
- this._path = this._setPath(path);
-
- _.forOwn(EmitterClass.prototype, (method, methodName) => {
- this[methodName] = function() {
- return EmitterClass.prototype[methodName].apply(this, arguments);
- };
- });
-
- // Some validations
- if (!this._path.length && value && !_.isPlainObject(value)) {
- throw new PersistedStateError('State value must be a plain object');
- }
-
- value = value || this._getDefault();
-
- // copy passed state values and create internal trackers
- this.set(value);
- this._initialized = true; // used to track state changes
- }
-
- get(key, def) {
- return _.cloneDeep(this._get(key, def));
- }
-
- set(key, value) {
- const params = prepSetParams(key, value, this._path);
- const val = this._set(params.key, params.value);
- this.emit('set');
- return val;
- }
-
- setSilent(key, value) {
- const params = prepSetParams(key, value, this._path);
- return this._set(params.key, params.value, true);
- }
-
- clearAllKeys() {
- Object.getOwnPropertyNames(this._changedState).forEach(key => {
- this.set(key, null);
- });
- }
-
- reset(path) {
- const keyPath = this._getIndex(path);
- const origValue = _.get(this._defaultState, keyPath);
- const currentValue = _.get(this._mergedState, keyPath);
-
- if (_.isUndefined(origValue)) {
- this._cleanPath(path, this._mergedState);
- } else {
- _.set(this._mergedState, keyPath, origValue);
- }
-
- // clean up the changedState tree
- this._cleanPath(path, this._changedState);
-
- if (!_.isEqual(currentValue, origValue)) this.emit('change');
- }
-
- getChanges() {
- return _.cloneDeep(this._changedState);
- }
-
- toJSON() {
- return this.get();
- }
-
- toString() {
- return JSON.stringify(this.toJSON());
- }
-
- fromString(input) {
- return this.set(JSON.parse(input));
- }
-
- _getIndex(key) {
- if (_.isUndefined(key)) return this._path;
- return (this._path || []).concat(toPath(key));
- }
-
- _getPartialIndex(key) {
- const keyPath = this._getIndex(key);
- return keyPath.slice(this._path.length);
- }
-
- _cleanPath(path, stateTree) {
- const partialPath = this._getPartialIndex(path);
- let remove = true;
-
- // recursively delete value tree, when no other keys exist
- while (partialPath.length > 0) {
- const lastKey = partialPath.splice(partialPath.length - 1, 1)[0];
- const statePath = this._path.concat(partialPath);
- const stateVal = statePath.length > 0 ? _.get(stateTree, statePath) : stateTree;
-
- // if stateVal isn't an object, do nothing
- if (!_.isPlainObject(stateVal)) return;
-
- if (remove) delete stateVal[lastKey];
- if (Object.keys(stateVal).length > 0) remove = false;
- }
- }
-
- _getDefault() {
- return this._hasPath() ? undefined : {};
- }
-
- _setPath(path) {
- const isString = _.isString(path);
- const isArray = Array.isArray(path);
-
- if (!isString && !isArray) return [];
- return isString ? [this._getIndex(path)] : path;
- }
-
- _hasPath() {
- return this._path.length > 0;
- }
-
- _get(key, def) {
- // no path and no key, get the whole state
- if (!this._hasPath() && _.isUndefined(key)) {
- return this._mergedState;
- }
-
- return _.get(this._mergedState, this._getIndex(key), def);
- }
-
- _set(key, value, silent) {
- const self = this;
- let stateChanged = false;
- const initialState = !this._initialized;
- const keyPath = this._getIndex(key);
- const hasKeyPath = keyPath.length > 0;
-
- // if this is the initial state value, save value as the default
- if (initialState) {
- this._changedState = {};
- if (!this._hasPath() && _.isUndefined(key)) this._defaultState = value;
- else this._defaultState = _.set({}, keyPath, value);
- }
-
- if (!initialState) {
- // no path and no key, set the whole state
- if (!this._hasPath() && _.isUndefined(key)) {
- // compare changedState and new state, emit an event when different
- stateChanged = !_.isEqual(this._changedState, value);
- this._changedState = value;
- this._mergedState = _.cloneDeep(value);
- } else {
- // check for changes at path, emit an event when different
- const curVal = hasKeyPath ? this.get(keyPath) : this._mergedState;
- stateChanged = !_.isEqual(curVal, value);
-
- // arrays are merge by index, not desired - ensure they are replaced
- if (Array.isArray(_.get(this._mergedState, keyPath))) {
- if (hasKeyPath) _.set(this._mergedState, keyPath, undefined);
- else this._mergedState = undefined;
- }
-
- if (hasKeyPath) {
- _.set(this._changedState, keyPath, value);
- } else {
- this._changedState = _.isPlainObject(value) ? value : {};
- }
- }
- }
-
- // update the merged state value
- const targetObj = this._mergedState || _.cloneDeep(this._defaultState);
- const sourceObj = _.merge({}, this._changedState);
-
- // handler arguments are (targetValue, sourceValue, key, target, source)
- const mergeMethod = function(targetValue, sourceValue, mergeKey) {
- // if not initial state, skip default merge method (ie. return value, see note below)
- if (!initialState && _.isEqual(keyPath, self._getIndex(mergeKey))) {
- // use the sourceValue or fall back to targetValue
- return !_.isUndefined(sourceValue) ? sourceValue : targetValue;
- }
- };
-
- // If `mergeMethod` is provided it is invoked to produce the merged values of the
- // destination and source properties.
- // If `mergeMethod` returns `undefined` the default merging method is used
- this._mergedState = _.merge(targetObj, sourceObj, mergeMethod);
-
- // sanity check; verify that there are actually changes
- if (_.isEqual(this._mergedState, this._defaultState)) this._changedState = {};
-
- if (!silent && stateChanged) this.emit('change', key);
-
- return this;
- }
-}
diff --git a/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js b/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js
index 1ba0500de7f038..601212d2da1a56 100644
--- a/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js
+++ b/src/legacy/ui/public/state_management/__tests__/state_monitor_factory.js
@@ -19,9 +19,9 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
+import { EventEmitter } from 'events';
import { cloneDeep } from 'lodash';
import { stateMonitorFactory } from '../state_monitor_factory';
-import { SimpleEmitter } from '../../utils/simple_emitter';
describe('stateMonitorFactory', function() {
const noop = () => {};
@@ -35,7 +35,7 @@ describe('stateMonitorFactory', function() {
}
function createMockState(state = {}) {
- const mockState = new SimpleEmitter();
+ const mockState = new EventEmitter();
setState(mockState, state, false);
return mockState;
}
diff --git a/src/legacy/ui/public/state_management/app_state.js b/src/legacy/ui/public/state_management/app_state.js
index 279aff62f55af1..76675b05e0fe56 100644
--- a/src/legacy/ui/public/state_management/app_state.js
+++ b/src/legacy/ui/public/state_management/app_state.js
@@ -29,7 +29,7 @@
import { uiModules } from '../modules';
import { StateProvider } from './state';
-import { PersistedState } from '../persisted_state';
+import { PersistedState } from '../../../../plugins/visualizations/public';
import { createLegacyClass } from '../utils/legacy_class';
const urlParam = '_a';
diff --git a/src/legacy/ui/public/utils/simple_emitter.js b/src/legacy/ui/public/utils/simple_emitter.js
deleted file mode 100644
index 6bdaae2237d558..00000000000000
--- a/src/legacy/ui/public/utils/simple_emitter.js
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import _ from 'lodash';
-
-/**
- * Simple event emitter class used in the vislib. Calls
- * handlers synchronously and implements a chainable api
- *
- * @class
- */
-export function SimpleEmitter() {
- this._listeners = {};
-}
-
-/**
- * Add an event handler
- *
- * @param {string} name
- * @param {function} handler
- * @return {SimpleEmitter} - this, for chaining
- */
-SimpleEmitter.prototype.on = function(name, handler) {
- let handlers = this._listeners[name];
- if (!handlers) handlers = this._listeners[name] = [];
-
- handlers.push(handler);
-
- return this;
-};
-
-/**
- * Remove an event handler
- *
- * @param {string} name
- * @param {function} [handler] - optional handler to remove, if no handler is
- * passed then all are removed
- * @return {SimpleEmitter} - this, for chaining
- */
-SimpleEmitter.prototype.off = function(name, handler) {
- if (!this._listeners[name]) {
- return this;
- }
-
- // remove a specific handler
- if (handler) _.pull(this._listeners[name], handler);
- // or remove all listeners
- else this._listeners[name] = null;
-
- return this;
-};
-
-/**
- * Remove all event listeners bound to this emitter.
- *
- * @return {SimpleEmitter} - this, for chaining
- */
-SimpleEmitter.prototype.removeAllListeners = function() {
- this._listeners = {};
- return this;
-};
-
-/**
- * Emit an event and all arguments to all listeners for an event name
- *
- * @param {string} name
- * @param {*} [arg...] - any number of arguments that will be applied to each handler
- * @return {SimpleEmitter} - this, for chaining
- */
-SimpleEmitter.prototype.emit = _.restParam(function(name, args) {
- if (!this._listeners[name]) return this;
- const listeners = this.listeners(name);
- let i = -1;
-
- while (++i < listeners.length) {
- listeners[i].apply(this, args);
- }
-
- return this;
-});
-
-/**
- * Get a list of the event names that currently have listeners
- *
- * @return {array[string]}
- */
-SimpleEmitter.prototype.activeEvents = function() {
- return _.reduce(
- this._listeners,
- function(active, listeners, name) {
- return active.concat(_.size(listeners) ? name : []);
- },
- []
- );
-};
-
-/**
- * Get a list of the handler functions for a specific event
- *
- * @param {string} name
- * @return {array[function]}
- */
-SimpleEmitter.prototype.listeners = function(name) {
- return this._listeners[name] ? this._listeners[name].slice(0) : [];
-};
-
-/**
- * Get the count of handlers for a specific event
- *
- * @param {string} [name] - optional event name to filter by
- * @return {number}
- */
-SimpleEmitter.prototype.listenerCount = function(name) {
- if (name) {
- return _.size(this._listeners[name]);
- }
-
- return _.reduce(
- this._listeners,
- function(count, handlers) {
- return count + _.size(handlers);
- },
- 0
- );
-};
diff --git a/src/legacy/ui/public/utils/simple_emitter.test.js b/src/legacy/ui/public/utils/simple_emitter.test.js
deleted file mode 100644
index 723a59ccba1c62..00000000000000
--- a/src/legacy/ui/public/utils/simple_emitter.test.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { SimpleEmitter } from './simple_emitter';
-import sinon from 'sinon';
-
-describe('SimpleEmitter class', () => {
- let emitter;
-
- beforeEach(() => {
- emitter = new SimpleEmitter();
- });
-
- it('constructs an event emitter', () => {
- expect(emitter).toHaveProperty('on');
- expect(emitter).toHaveProperty('off');
- expect(emitter).toHaveProperty('emit');
- expect(emitter).toHaveProperty('listenerCount');
- expect(emitter).toHaveProperty('removeAllListeners');
- });
-
- describe('#listenerCount', () => {
- it('counts all event listeners without any arg', () => {
- expect(emitter.listenerCount()).toBe(0);
- emitter.on('a', () => {});
- expect(emitter.listenerCount()).toBe(1);
- emitter.on('b', () => {});
- expect(emitter.listenerCount()).toBe(2);
- });
-
- it('limits to the event that is passed in', () => {
- expect(emitter.listenerCount()).toBe(0);
- emitter.on('a', () => {});
- expect(emitter.listenerCount('a')).toBe(1);
- emitter.on('a', () => {});
- expect(emitter.listenerCount('a')).toBe(2);
- emitter.on('b', () => {});
- expect(emitter.listenerCount('a')).toBe(2);
- expect(emitter.listenerCount('b')).toBe(1);
- expect(emitter.listenerCount()).toBe(3);
- });
- });
-
- describe('#on', () => {
- it('registers a handler', () => {
- const handler = sinon.stub();
- emitter.on('a', handler);
- expect(emitter.listenerCount('a')).toBe(1);
-
- expect(handler.callCount).toBe(0);
- emitter.emit('a');
- expect(handler.callCount).toBe(1);
- });
-
- it('allows multiple event handlers for the same event', () => {
- emitter.on('a', () => {});
- emitter.on('a', () => {});
- expect(emitter.listenerCount('a')).toBe(2);
- });
-
- it('allows the same function to be registered multiple times', () => {
- const handler = () => {};
- emitter.on('a', handler);
- expect(emitter.listenerCount()).toBe(1);
- emitter.on('a', handler);
- expect(emitter.listenerCount()).toBe(2);
- });
- });
-
- describe('#off', () => {
- it('removes a listener if it was registered', () => {
- const handler = sinon.stub();
- expect(emitter.listenerCount()).toBe(0);
- emitter.on('a', handler);
- expect(emitter.listenerCount('a')).toBe(1);
- emitter.off('a', handler);
- expect(emitter.listenerCount('a')).toBe(0);
- });
-
- it('clears all listeners if no handler is passed', () => {
- emitter.on('a', () => {});
- emitter.on('a', () => {});
- expect(emitter.listenerCount()).toBe(2);
- emitter.off('a');
- expect(emitter.listenerCount()).toBe(0);
- });
-
- it('does not mind if the listener is not registered', () => {
- emitter.off('a', () => {});
- });
-
- it('does not mind if the event has no listeners', () => {
- emitter.off('a');
- });
- });
-
- describe('#emit', () => {
- it('calls the handlers in the order they were defined', () => {
- let i = 0;
- const incr = () => ++i;
- const one = sinon.spy(incr);
- const two = sinon.spy(incr);
- const three = sinon.spy(incr);
- const four = sinon.spy(incr);
-
- emitter
- .on('a', one)
- .on('a', two)
- .on('a', three)
- .on('a', four)
- .emit('a');
-
- expect(one).toHaveProperty('callCount', 1);
- expect(one.returned(1)).toBeDefined();
-
- expect(two).toHaveProperty('callCount', 1);
- expect(two.returned(2)).toBeDefined();
-
- expect(three).toHaveProperty('callCount', 1);
- expect(three.returned(3)).toBeDefined();
-
- expect(four).toHaveProperty('callCount', 1);
- expect(four.returned(4)).toBeDefined();
- });
-
- it('always emits the handlers that were initially registered', () => {
- const destructive = sinon.spy(() => {
- emitter.removeAllListeners();
- expect(emitter.listenerCount()).toBe(0);
- });
- const stub = sinon.stub();
-
- emitter
- .on('run', destructive)
- .on('run', stub)
- .emit('run');
-
- expect(destructive).toHaveProperty('callCount', 1);
- expect(stub).toHaveProperty('callCount', 1);
- });
-
- it('applies all arguments except the first', () => {
- emitter
- .on('a', (a, b, c) => {
- expect(a).toBe('foo');
- expect(b).toBe('bar');
- expect(c).toBe('baz');
- })
- .emit('a', 'foo', 'bar', 'baz');
- });
-
- it('uses the SimpleEmitter as the this context', () => {
- emitter
- .on('a', function() {
- expect(this).toBe(emitter);
- })
- .emit('a');
- });
- });
-});
diff --git a/src/plugins/apm_oss/server/index.ts b/src/plugins/apm_oss/server/index.ts
index 801140694c1394..95a4ae4519bc9a 100644
--- a/src/plugins/apm_oss/server/index.ts
+++ b/src/plugins/apm_oss/server/index.ts
@@ -38,4 +38,4 @@ export function plugin(initializerContext: PluginInitializerContext) {
export type APMOSSConfig = TypeOf;
-export { APMOSSPlugin as Plugin };
+export { APMOSSPluginSetup } from './plugin';
diff --git a/src/plugins/apm_oss/server/plugin.ts b/src/plugins/apm_oss/server/plugin.ts
index 2708f7729482bd..9b14d19da90c2e 100644
--- a/src/plugins/apm_oss/server/plugin.ts
+++ b/src/plugins/apm_oss/server/plugin.ts
@@ -20,7 +20,7 @@ import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/server';
import { Observable } from 'rxjs';
import { APMOSSConfig } from './';
-export class APMOSSPlugin implements Plugin<{ config$: Observable }> {
+export class APMOSSPlugin implements Plugin {
constructor(private readonly initContext: PluginInitializerContext) {
this.initContext = initContext;
}
@@ -36,3 +36,7 @@ export class APMOSSPlugin implements Plugin<{ config$: Observable
start() {}
stop() {}
}
+
+export interface APMOSSPluginSetup {
+ config$: Observable;
+}
diff --git a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts
index c1c6a8f187a44c..e1640927c4ead3 100644
--- a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts
+++ b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts
@@ -20,7 +20,8 @@
import { TimedItemBuffer } from '../timed_item_buffer';
import { runItemBufferTests } from './run_item_buffer_tests';
-describe('TimedItemBuffer', () => {
+// FLAKY: https://github.com/elastic/kibana/issues/58662
+describe.skip('TimedItemBuffer', () => {
runItemBufferTests(TimedItemBuffer);
test('does not do unnecessary flushes', async () => {
diff --git a/src/plugins/kibana_legacy/public/utils/register_listen_event_listener.js b/src/plugins/kibana_legacy/public/utils/register_listen_event_listener.js
index cce6c98adbbfec..6cc9a5766d3fe2 100644
--- a/src/plugins/kibana_legacy/public/utils/register_listen_event_listener.js
+++ b/src/plugins/kibana_legacy/public/utils/register_listen_event_listener.js
@@ -21,7 +21,7 @@ export function registerListenEventListener($rootScope) {
* Helper that registers an event listener, and removes that listener when
* the $scope is destroyed.
*
- * @param {SimpleEmitter} emitter - the event emitter to listen to
+ * @param {EventEmitter} emitter - the event emitter to listen to
* @param {string} eventName - the event name
* @param {Function} handler - the event handler
* @return {undefined}
diff --git a/src/legacy/ui/public/persisted_state/errors.ts b/src/plugins/usage_collection/server/mocks.ts
similarity index 58%
rename from src/legacy/ui/public/persisted_state/errors.ts
rename to src/plugins/usage_collection/server/mocks.ts
index 164981107298d3..2194b1fb83f6e8 100644
--- a/src/legacy/ui/public/persisted_state/errors.ts
+++ b/src/plugins/usage_collection/server/mocks.ts
@@ -17,10 +17,22 @@
* under the License.
*/
-import { KbnError } from '../../../../plugins/kibana_utils/public';
+import { loggingServiceMock } from '../../../core/server/mocks';
+import { UsageCollectionSetup } from './plugin';
+import { CollectorSet } from './collector';
-export class PersistedStateError extends KbnError {
- constructor() {
- super('Error with the persisted state');
- }
-}
+const createSetupContract = () => {
+ return {
+ ...new CollectorSet({
+ logger: loggingServiceMock.createLogger(),
+ maximumWaitTimeForAllCollectorsInS: 1,
+ }),
+ registerLegacySavedObjects: jest.fn() as jest.Mocked<
+ UsageCollectionSetup['registerLegacySavedObjects']
+ >,
+ } as UsageCollectionSetup;
+};
+
+export const usageCollectionPluginMock = {
+ createSetupContract,
+};
diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts
index 2b4b10c8329a3a..c08dbf890b8da9 100644
--- a/src/plugins/visualizations/public/index.ts
+++ b/src/plugins/visualizations/public/index.ts
@@ -27,3 +27,5 @@ export function plugin(initializerContext: PluginInitializerContext) {
export { VisualizationsPublicPlugin as Plugin };
export * from './plugin';
export * from './types';
+
+export { PersistedState } from './persisted_state';
diff --git a/src/legacy/ui/public/persisted_state/index.d.ts b/src/plugins/visualizations/public/persisted_state/index.ts
similarity index 100%
rename from src/legacy/ui/public/persisted_state/index.d.ts
rename to src/plugins/visualizations/public/persisted_state/index.ts
diff --git a/src/plugins/visualizations/public/persisted_state/persisted_state.ts b/src/plugins/visualizations/public/persisted_state/persisted_state.ts
new file mode 100644
index 00000000000000..d09dcd5381511e
--- /dev/null
+++ b/src/plugins/visualizations/public/persisted_state/persisted_state.ts
@@ -0,0 +1,254 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { EventEmitter } from 'events';
+
+import { isPlainObject, cloneDeep, get, set, isEqual, isString, merge } from 'lodash';
+import toPath from 'lodash/internal/toPath';
+
+function prepSetParams(key: PersistedStateKey, value: any, path: PersistedStatePath) {
+ // key must be the value, set the entire state using it
+ if (value === undefined && (isPlainObject(key) || path.length > 0)) {
+ // setting entire tree, swap the key and value to write to the state
+ return {
+ value: key,
+ key: undefined,
+ };
+ }
+
+ // ensure the value being passed in is never mutated
+ return {
+ value: cloneDeep(value),
+ key,
+ };
+}
+
+type PersistedStateKey = string | string[] | undefined;
+type PersistedStatePath = string | string[];
+
+export class PersistedState extends EventEmitter {
+ private readonly _path: PersistedStatePath;
+ private readonly _initialized: boolean;
+
+ private _changedState: any;
+ private _defaultState: any;
+ private _mergedState: any;
+
+ constructor(value?: any, path?: PersistedStatePath) {
+ super();
+
+ this._path = this.setPath(path);
+
+ // Some validations
+ if (!this._path.length && value && !isPlainObject(value)) {
+ throw new Error('State value must be a plain object');
+ }
+
+ value = value || this.getDefault();
+
+ // copy passed state values and create internal trackers
+ this.set(value);
+ this._initialized = true; // used to track state changes
+ }
+
+ get(key?: PersistedStateKey, defaultValue?: any) {
+ // no path and no key, get the whole state
+ if (!this.hasPath() && key === undefined) {
+ return this._mergedState;
+ }
+
+ return cloneDeep(get(this._mergedState, this.getIndex(key || ''), defaultValue));
+ }
+
+ set(key: PersistedStateKey | any, value?: any) {
+ const params = prepSetParams(key, value, this._path);
+ const val = this.setValue(params.key, params.value);
+
+ this.emit('set');
+ return val;
+ }
+
+ setSilent(key: PersistedStateKey | any, value?: any) {
+ const params = prepSetParams(key, value, this._path);
+
+ if (params.key) {
+ return this.setValue(params.key, params.value, true);
+ }
+ }
+
+ clearAllKeys() {
+ Object.getOwnPropertyNames(this._changedState).forEach(key => {
+ this.set(key, null);
+ });
+ }
+
+ reset(path: PersistedStatePath) {
+ const keyPath = this.getIndex(path);
+ const origValue = get(this._defaultState, keyPath);
+ const currentValue = get(this._mergedState, keyPath);
+
+ if (origValue === undefined) {
+ this.cleanPath(path, this._mergedState);
+ } else {
+ set(this._mergedState, keyPath, origValue);
+ }
+
+ // clean up the changedState tree
+ this.cleanPath(path, this._changedState);
+
+ if (!isEqual(currentValue, origValue)) this.emit('change');
+ }
+
+ getChanges() {
+ return cloneDeep(this._changedState);
+ }
+
+ toJSON() {
+ return this.get();
+ }
+
+ toString() {
+ return JSON.stringify(this.toJSON());
+ }
+
+ fromString(input: string) {
+ return this.set(JSON.parse(input));
+ }
+
+ private getIndex(key: PersistedStateKey) {
+ if (key === undefined) return this._path;
+
+ return [...(this._path || []), ...toPath(key)];
+ }
+
+ private getPartialIndex(key: PersistedStateKey) {
+ const keyPath = this.getIndex(key);
+
+ return keyPath.slice(this._path.length);
+ }
+
+ private cleanPath(path: PersistedStatePath, stateTree: any) {
+ const partialPath = this.getPartialIndex(path);
+ let remove = true;
+
+ if (Array.isArray(partialPath)) {
+ // recursively delete value tree, when no other keys exist
+ while (partialPath.length > 0) {
+ const lastKey = partialPath.splice(partialPath.length - 1, 1)[0];
+ const statePath = [...this._path, partialPath];
+ const stateVal = statePath.length > 0 ? get(stateTree, statePath) : stateTree;
+
+ // if stateVal isn't an object, do nothing
+ if (!isPlainObject(stateVal)) return;
+
+ if (remove) delete stateVal[lastKey];
+ if (Object.keys(stateVal).length > 0) remove = false;
+ }
+ }
+ }
+
+ private getDefault() {
+ return this.hasPath() ? undefined : {};
+ }
+
+ private setPath(path?: PersistedStatePath): string[] {
+ if (Array.isArray(path)) {
+ return path;
+ }
+
+ if (isString(path)) {
+ return [...this.getIndex(path)];
+ }
+
+ return [];
+ }
+
+ private hasPath() {
+ return this._path.length > 0;
+ }
+
+ private setValue(key: PersistedStateKey, value: any, silent: boolean = false) {
+ const self = this;
+ let stateChanged = false;
+ const initialState = !this._initialized;
+ const keyPath = this.getIndex(key);
+ const hasKeyPath = keyPath.length > 0;
+
+ // if this is the initial state value, save value as the default
+ if (initialState) {
+ this._changedState = {};
+ if (!this.hasPath() && key === undefined) this._defaultState = value;
+ else this._defaultState = set({}, keyPath, value);
+ }
+
+ if (!initialState) {
+ // no path and no key, set the whole state
+ if (!this.hasPath() && key === undefined) {
+ // compare changedState and new state, emit an event when different
+ stateChanged = !isEqual(this._changedState, value);
+ this._changedState = value;
+ this._mergedState = cloneDeep(value);
+ } else {
+ // check for changes at path, emit an event when different
+ const curVal = hasKeyPath ? this.get(keyPath) : this._mergedState;
+ stateChanged = !isEqual(curVal, value);
+
+ // arrays are merge by index, not desired - ensure they are replaced
+ if (Array.isArray(get(this._mergedState, keyPath))) {
+ if (hasKeyPath) {
+ set(this._mergedState, keyPath, undefined);
+ } else {
+ this._mergedState = undefined;
+ }
+ }
+
+ if (hasKeyPath) {
+ set(this._changedState, keyPath, value);
+ } else {
+ this._changedState = isPlainObject(value) ? value : {};
+ }
+ }
+ }
+
+ // update the merged state value
+ const targetObj = this._mergedState || cloneDeep(this._defaultState);
+ const sourceObj = merge({}, this._changedState);
+
+ // handler arguments are (targetValue, sourceValue, key, target, source)
+ const mergeMethod = function(targetValue: any, sourceValue: any, mergeKey: string) {
+ // if not initial state, skip default merge method (ie. return value, see note below)
+ if (!initialState && isEqual(keyPath, self.getIndex(mergeKey))) {
+ // use the sourceValue or fall back to targetValue
+ return sourceValue === undefined ? targetValue : sourceValue;
+ }
+ };
+
+ // If `mergeMethod` is provided it is invoked to produce the merged values of the
+ // destination and source properties.
+ // If `mergeMethod` returns `undefined` the default merging method is used
+ this._mergedState = merge(targetObj, sourceObj, mergeMethod);
+
+ // sanity check; verify that there are actually changes
+ if (isEqual(this._mergedState, this._defaultState)) this._changedState = {};
+
+ if (!silent && stateChanged) this.emit('change', key);
+
+ return this;
+ }
+}
diff --git a/src/legacy/ui/public/persisted_state/persisted_state_provider.test.ts b/src/plugins/visualizations/public/persisted_state/persisted_state_provider.test.ts
similarity index 96%
rename from src/legacy/ui/public/persisted_state/persisted_state_provider.test.ts
rename to src/plugins/visualizations/public/persisted_state/persisted_state_provider.test.ts
index f14215002cf8f2..76446a3f44861b 100644
--- a/src/legacy/ui/public/persisted_state/persisted_state_provider.test.ts
+++ b/src/plugins/visualizations/public/persisted_state/persisted_state_provider.test.ts
@@ -17,7 +17,6 @@
* under the License.
*/
-import { PersistedStateError } from './errors';
import { PersistedState } from './persisted_state';
describe('Persisted State Provider', () => {
@@ -47,7 +46,7 @@ describe('Persisted State Provider', () => {
});
test('should throw if given an invalid value', () => {
- expect(() => new PersistedState('bananas')).toThrow(PersistedStateError);
+ expect(() => new PersistedState('bananas')).toThrow(Error);
});
});
@@ -224,13 +223,13 @@ describe('Persisted State Provider', () => {
describe('internal state tracking', () => {
test('should be an empty object', () => {
const persistedState = new PersistedState();
- expect(persistedState._defaultState).toEqual({});
+ expect(persistedState).toHaveProperty('_defaultState', {});
});
test('should store the default state value', () => {
const val = { one: 1, two: 2 };
const persistedState = new PersistedState(val);
- expect(persistedState._defaultState).toEqual(val);
+ expect(persistedState).toHaveProperty('_defaultState', val);
});
test('should keep track of changes', () => {
@@ -238,8 +237,8 @@ describe('Persisted State Provider', () => {
const persistedState = new PersistedState(val);
persistedState.set('two', 22);
- expect(persistedState._defaultState).toEqual(val);
- expect(persistedState._changedState).toEqual({ two: 22 });
+ expect(persistedState).toHaveProperty('_defaultState', val);
+ expect(persistedState).toHaveProperty('_changedState', { two: 22 });
});
});
diff --git a/tasks/test_jest.js b/tasks/test_jest.js
index ff1a941610ad9d..bcb05a83675e5e 100644
--- a/tasks/test_jest.js
+++ b/tasks/test_jest.js
@@ -33,7 +33,7 @@ module.exports = function(grunt) {
function runJest(jestScript) {
const serverCmd = {
cmd: 'node',
- args: [jestScript, '--ci'],
+ args: [jestScript, '--ci', '--detectOpenHandles'],
opts: { stdio: 'inherit' },
};
diff --git a/test/functional/apps/context/_filters.js b/test/functional/apps/context/_filters.js
index c9499f5a805ab3..721f8a50d0e464 100644
--- a/test/functional/apps/context/_filters.js
+++ b/test/functional/apps/context/_filters.js
@@ -64,7 +64,7 @@ export default function({ getService, getPageObjects }) {
await filterBar.toggleFilterEnabled(TEST_ANCHOR_FILTER_FIELD);
await PageObjects.context.waitUntilContextLoadingHasFinished();
- retry.try(async () => {
+ await retry.try(async () => {
expect(
await filterBar.hasFilter(TEST_ANCHOR_FILTER_FIELD, TEST_ANCHOR_FILTER_VALUE, false)
).to.be(true);
diff --git a/test/functional/apps/context/_size.js b/test/functional/apps/context/_size.js
index 5f3d1ebe409744..ea9b2c8cf18192 100644
--- a/test/functional/apps/context/_size.js
+++ b/test/functional/apps/context/_size.js
@@ -30,7 +30,8 @@ export default function({ getService, getPageObjects }) {
const docTable = getService('docTable');
const PageObjects = getPageObjects(['context']);
- describe('context size', function contextSize() {
+ // FLAKY: https://github.com/elastic/kibana/issues/53888
+ describe.skip('context size', function contextSize() {
before(async function() {
await kibanaServer.uiSettings.update({
'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`,
diff --git a/test/functional/apps/dashboard/panel_expand_toggle.js b/test/functional/apps/dashboard/panel_expand_toggle.js
index 930445a67aa203..5e7d55706968de 100644
--- a/test/functional/apps/dashboard/panel_expand_toggle.js
+++ b/test/functional/apps/dashboard/panel_expand_toggle.js
@@ -56,7 +56,7 @@ export default function({ getService, getPageObjects }) {
// Add a retry to fix https://github.com/elastic/kibana/issues/14574. Perhaps the recent changes to this
// being a CSS update is causing the UI to change slower than grabbing the panels?
- retry.try(async () => {
+ await retry.try(async () => {
const panelCountAfterMaxThenMinimize = await PageObjects.dashboard.getPanelCount();
expect(panelCountAfterMaxThenMinimize).to.be(panelCount);
});
diff --git a/test/functional/apps/visualize/_tsvb_markdown.ts b/test/functional/apps/visualize/_tsvb_markdown.ts
index b7307ac9c6cabc..d37404a3d60cb2 100644
--- a/test/functional/apps/visualize/_tsvb_markdown.ts
+++ b/test/functional/apps/visualize/_tsvb_markdown.ts
@@ -121,7 +121,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
await visualBuilder.markdownSwitchSubTab('data');
await visualBuilder.cloneSeries();
- retry.try(async function seriesCountCheck() {
+ await retry.try(async function seriesCountCheck() {
const seriesLength = (await visualBuilder.getSeries()).length;
expect(seriesLength).to.be.equal(2);
});
@@ -131,7 +131,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) {
await visualBuilder.markdownSwitchSubTab('data');
await visualBuilder.createNewAgg();
- retry.try(async function aggregationCountCheck() {
+ await retry.try(async function aggregationCountCheck() {
const aggregationLength = await visualBuilder.getAggregationCount();
expect(aggregationLength).to.be.equal(2);
});
diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh
index 3d30496ecb5822..b629e064b39b5e 100755
--- a/test/scripts/jenkins_xpack.sh
+++ b/test/scripts/jenkins_xpack.sh
@@ -11,7 +11,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then
echo " -> Running jest tests"
cd "$XPACK_DIR"
- checks-reporter-with-killswitch "X-Pack Jest" node scripts/jest --ci --verbose
+ checks-reporter-with-killswitch "X-Pack Jest" node scripts/jest --ci --verbose --detectOpenHandles
echo ""
echo ""
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index bb084b3bb72a18..66342266f1dbc6 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -26,7 +26,7 @@
"xpack.logstash": "legacy/plugins/logstash",
"xpack.main": "legacy/plugins/xpack_main",
"xpack.maps": "legacy/plugins/maps",
- "xpack.ml": "legacy/plugins/ml",
+ "xpack.ml": ["plugins/ml", "legacy/plugins/ml"],
"xpack.monitoring": "legacy/plugins/monitoring",
"xpack.remoteClusters": "plugins/remote_clusters",
"xpack.reporting": ["plugins/reporting", "legacy/plugins/reporting"],
diff --git a/x-pack/legacy/plugins/apm/readme.md b/x-pack/legacy/plugins/apm/readme.md
index a513249c296db3..addf73064716ce 100644
--- a/x-pack/legacy/plugins/apm/readme.md
+++ b/x-pack/legacy/plugins/apm/readme.md
@@ -74,10 +74,14 @@ node scripts/jest.js plugins/apm --updateSnapshot
### Functional tests
**Start server**
-`node scripts/functional_tests_server --config x-pack/test/functional/config.js`
+```
+node scripts/functional_tests_server --config x-pack/test/functional/config.js
+```
**Run tests**
-`node scripts/functional_test_runner --config x-pack/test/functional/config.js --grep='APM specs'`
+```
+node scripts/functional_test_runner --config x-pack/test/functional/config.js --grep='APM specs'
+```
APM tests are located in `x-pack/test/functional/apps/apm`.
For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
@@ -85,10 +89,14 @@ For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
### API integration tests
**Start server**
-`node scripts/functional_tests_server --config x-pack/test/api_integration/config.js`
+```
+node scripts/functional_tests_server --config x-pack/test/api_integration/config.js
+```
**Run tests**
-`node scripts/functional_test_runner --config x-pack/test/api_integration/config.js --grep='APM specs'`
+```
+node scripts/functional_test_runner --config x-pack/test/api_integration/config.js --grep='APM specs'
+```
APM tests are located in `x-pack/test/api_integration/apis/apm`.
For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme)
@@ -117,6 +125,6 @@ You can access the development environment at http://localhost:9001.
#### Further resources
-- [Cypress integration tests](cypress/README.md)
+- [Cypress integration tests](./e2e/README.md)
- [VSCode setup instructions](./dev_docs/vscode_setup.md)
- [Github PR commands](./dev_docs/github_commands.md)
diff --git a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json
index c2f87503b4548f..5021694ff04acd 100644
--- a/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json
+++ b/x-pack/legacy/plugins/apm/scripts/optimize-tsconfig/tsconfig.json
@@ -7,6 +7,6 @@
],
"exclude": [
"**/__fixtures__/**/*",
- "./cypress/**/*"
+ "./e2e/cypress/**/*"
]
}
diff --git a/x-pack/legacy/plugins/maps/public/index.ts b/x-pack/legacy/plugins/maps/public/index.ts
index f3213a36bb66d3..27cd64103eec98 100644
--- a/x-pack/legacy/plugins/maps/public/index.ts
+++ b/x-pack/legacy/plugins/maps/public/index.ts
@@ -11,7 +11,6 @@ import 'uiExports/inspectorViews';
import 'uiExports/search';
import 'uiExports/embeddableFactories';
import 'uiExports/embeddableActions';
-import 'ui/agg_types';
import 'ui/autoload/all';
import 'react-vis/dist/style.css';
diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js
index 28c199b64d3ef4..27ab8fc5bfb3ae 100644
--- a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js
+++ b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js
@@ -21,13 +21,17 @@ export class ESAggMetricField extends AbstractField {
}
getName() {
- return this._source.formatMetricKey(this.getAggType(), this.getESDocFieldName());
+ return this._source.getAggKey(this.getAggType(), this.getRootName());
+ }
+
+ getRootName() {
+ return this._getESDocFieldName();
}
async getLabel() {
return this._label
- ? await this._label
- : this._source.formatMetricLabel(this.getAggType(), this.getESDocFieldName());
+ ? this._label
+ : this._source.getAggLabel(this.getAggType(), this.getRootName());
}
getAggType() {
@@ -42,13 +46,13 @@ export class ESAggMetricField extends AbstractField {
return this.getAggType() === AGG_TYPE.TERMS ? 'string' : 'number';
}
- getESDocFieldName() {
+ _getESDocFieldName() {
return this._esDocField ? this._esDocField.getName() : '';
}
getRequestDescription() {
return this.getAggType() !== AGG_TYPE.COUNT
- ? `${this.getAggType()} ${this.getESDocFieldName()}`
+ ? `${this.getAggType()} ${this.getRootName()}`
: AGG_TYPE.COUNT;
}
@@ -64,7 +68,7 @@ export class ESAggMetricField extends AbstractField {
}
getValueAggDsl(indexPattern) {
- const field = getField(indexPattern, this.getESDocFieldName());
+ const field = getField(indexPattern, this.getRootName());
const aggType = this.getAggType();
const aggBody = aggType === AGG_TYPE.TERMS ? { size: 1, shard_size: 1 } : {};
return {
@@ -77,6 +81,11 @@ export class ESAggMetricField extends AbstractField {
return !isMetricCountable(this.getAggType());
}
+ canValueBeFormatted() {
+ // Do not use field formatters for counting metrics
+ return ![AGG_TYPE.COUNT, AGG_TYPE.UNIQUE_COUNT].includes(this.getAggType());
+ }
+
async getOrdinalFieldMetaRequest(config) {
return this._esDocField.getOrdinalFieldMetaRequest(config);
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/field.js b/x-pack/legacy/plugins/maps/public/layers/fields/field.js
index b5d157ad1697aa..2dd553f66755fc 100644
--- a/x-pack/legacy/plugins/maps/public/layers/fields/field.js
+++ b/x-pack/legacy/plugins/maps/public/layers/fields/field.js
@@ -17,6 +17,14 @@ export class AbstractField {
return this._fieldName;
}
+ getRootName() {
+ return this.getName();
+ }
+
+ canValueBeFormatted() {
+ return true;
+ }
+
getSource() {
return this._source;
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js
index 05b177b3614493..4a91ed3a3eafbc 100644
--- a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js
+++ b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js
@@ -7,12 +7,6 @@
import { InnerJoin } from './inner_join';
jest.mock('../../kibana_services', () => {});
-jest.mock('ui/agg_types', () => {
- class MockSchemas {}
- return {
- Schemas: MockSchemas,
- };
-});
jest.mock('ui/timefilter', () => {});
jest.mock('../vector_layer', () => {});
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js
index bee35216f59dab..775535d9e2299c 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { i18n } from '@kbn/i18n';
import { AbstractESSource } from './es_source';
import { ESAggMetricField } from '../fields/es_agg_field';
import { ESDocField } from '../fields/es_doc_field';
@@ -72,12 +73,22 @@ export class AbstractESAggSource extends AbstractESSource {
return metrics;
}
- formatMetricKey(aggType, fieldName) {
+ getAggKey(aggType, fieldName) {
return aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : COUNT_PROP_NAME;
}
- formatMetricLabel(aggType, fieldName) {
- return aggType !== AGG_TYPE.COUNT ? `${aggType} of ${fieldName}` : COUNT_PROP_LABEL;
+ getAggLabel(aggType, fieldName) {
+ switch (aggType) {
+ case AGG_TYPE.COUNT:
+ return COUNT_PROP_LABEL;
+ case AGG_TYPE.TERMS:
+ return i18n.translate('xpack.maps.source.esAggSource.topTermLabel', {
+ defaultMessage: `Top {fieldName}`,
+ values: { fieldName },
+ });
+ default:
+ return `${aggType} ${fieldName}`;
+ }
}
getValueAggsDsl(indexPattern) {
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
index 782f2845ceeffc..f575fd05c80613 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js
@@ -14,11 +14,10 @@ import {
import { createExtentFilter } from '../../elasticsearch_geo_utils';
import { timefilter } from 'ui/timefilter';
import _ from 'lodash';
-import { AggConfigs } from 'ui/agg_types';
import { i18n } from '@kbn/i18n';
import uuid from 'uuid/v4';
import { copyPersistentState } from '../../reducers/util';
-import { ES_GEO_FIELD_TYPE, AGG_TYPE } from '../../../common/constants';
+import { ES_GEO_FIELD_TYPE } from '../../../common/constants';
import { DataRequestAbortError } from '../util/data_request';
import { expandToTileBoundaries } from './es_geo_grid_source/geo_tile_utils';
@@ -73,10 +72,6 @@ export class AbstractESSource extends AbstractVectorSource {
return clonedDescriptor;
}
- getMetricFields() {
- return [];
- }
-
async _runEsQuery({
requestId,
requestName,
@@ -151,27 +146,18 @@ export class AbstractESSource extends AbstractVectorSource {
{ sourceQuery, query, timeFilters, filters, applyGlobalQuery },
0
);
- const geoField = await this._getGeoField();
- const indexPattern = await this.getIndexPattern();
-
- const geoBoundsAgg = [
- {
- type: 'geo_bounds',
- enabled: true,
- params: {
- field: geoField,
+ searchSource.setField('aggs', {
+ fitToBounds: {
+ geo_bounds: {
+ field: this._descriptor.geoField,
},
- schema: 'metric',
},
- ];
-
- const aggConfigs = new AggConfigs(indexPattern, geoBoundsAgg);
- searchSource.setField('aggs', aggConfigs.toDsl());
+ });
let esBounds;
try {
const esResp = await searchSource.fetch();
- esBounds = _.get(esResp, 'aggregations.1.bounds');
+ esBounds = _.get(esResp, 'aggregations.fitToBounds.bounds');
} catch (error) {
esBounds = {
top_left: {
@@ -264,23 +250,7 @@ export class AbstractESSource extends AbstractVectorSource {
return this._descriptor.id;
}
- async getFieldFormatter(fieldName) {
- const metricField = this.getMetricFields().find(field => field.getName() === fieldName);
-
- // Do not use field formatters for counting metrics
- if (
- metricField &&
- (metricField.type === AGG_TYPE.COUNT || metricField.type === AGG_TYPE.UNIQUE_COUNT)
- ) {
- return null;
- }
-
- // fieldName could be an aggregation so it needs to be unpacked to expose raw field.
- const realFieldName = metricField ? metricField.getESDocFieldName() : fieldName;
- if (!realFieldName) {
- return null;
- }
-
+ async createFieldFormatter(field) {
let indexPattern;
try {
indexPattern = await this.getIndexPattern();
@@ -288,7 +258,7 @@ export class AbstractESSource extends AbstractVectorSource {
return null;
}
- const fieldFromIndexPattern = indexPattern.fields.getByName(realFieldName);
+ const fieldFromIndexPattern = indexPattern.fields.getByName(field.getRootName());
if (!fieldFromIndexPattern) {
return null;
}
@@ -346,25 +316,19 @@ export class AbstractESSource extends AbstractVectorSource {
return resp.aggregations;
}
- getValueSuggestions = async (fieldName, query) => {
- // fieldName could be an aggregation so it needs to be unpacked to expose raw field.
- const metricField = this.getMetricFields().find(field => field.getName() === fieldName);
- const realFieldName = metricField ? metricField.getESDocFieldName() : fieldName;
- if (!realFieldName) {
- return [];
- }
-
+ getValueSuggestions = async (field, query) => {
try {
const indexPattern = await this.getIndexPattern();
- const field = indexPattern.fields.getByName(realFieldName);
return await autocompleteService.getValueSuggestions({
indexPattern,
- field,
+ field: indexPattern.fields.getByName(field.getRootName()),
query,
});
} catch (error) {
console.warn(
- `Unable to fetch suggestions for field: ${fieldName}, query: ${query}, error: ${error.message}`
+ `Unable to fetch suggestions for field: ${field.getRootName()}, query: ${query}, error: ${
+ error.message
+ }`
);
return [];
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js
index 9cc2919404a940..30f60f543d38df 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js
@@ -67,7 +67,7 @@ export class ESTermSource extends AbstractESAggSource {
return this._descriptor.whereQuery;
}
- formatMetricKey(aggType, fieldName) {
+ getAggKey(aggType, fieldName) {
const metricKey =
aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : aggType;
return `${FIELD_NAME_PREFIX}${metricKey}${GROUP_BY_DELIMITER}${
@@ -75,21 +75,13 @@ export class ESTermSource extends AbstractESAggSource {
}.${this._termField.getName()}`;
}
- formatMetricLabel(type, fieldName) {
- switch (type) {
- case AGG_TYPE.COUNT:
- return i18n.translate('xpack.maps.source.esJoin.countLabel', {
+ getAggLabel(aggType, fieldName) {
+ return aggType === AGG_TYPE.COUNT
+ ? i18n.translate('xpack.maps.source.esJoin.countLabel', {
defaultMessage: `Count of {indexPatternTitle}`,
values: { indexPatternTitle: this._descriptor.indexPatternTitle },
- });
- case AGG_TYPE.TERMS:
- return i18n.translate('xpack.maps.source.esJoin.topTermLabel', {
- defaultMessage: `Top {fieldName}`,
- values: { fieldName },
- });
- default:
- return `${type} ${fieldName}`;
- }
+ })
+ : super.getAggLabel(aggType, fieldName);
}
async getPropertiesMap(searchFilters, leftSourceName, leftFieldName, registerCancelCallback) {
@@ -116,7 +108,7 @@ export class ESTermSource extends AbstractESAggSource {
requestDescription: this._getRequestDescription(leftSourceName, leftFieldName),
});
- const countPropertyName = this.formatMetricKey(AGG_TYPE.COUNT);
+ const countPropertyName = this.getAggKey(AGG_TYPE.COUNT);
return {
propertiesMap: extractPropertiesMap(rawEsData, countPropertyName),
};
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js
index 39cc301d458cbe..d6f9f6d2911e91 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js
@@ -54,7 +54,7 @@ describe('getMetricFields', () => {
expect(metrics.length).toBe(2);
expect(metrics[0].getAggType()).toEqual('sum');
- expect(metrics[0].getESDocFieldName()).toEqual(sumFieldName);
+ expect(metrics[0].getRootName()).toEqual(sumFieldName);
expect(metrics[0].getName()).toEqual(
'__kbnjoin__sum_of_myFieldGettingSummed_groupby_myIndex.myTermField'
);
diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/legacy/plugins/maps/public/layers/sources/source.js
index 3c6ddb74bedeba..4fef52e731f9b2 100644
--- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js
+++ b/x-pack/legacy/plugins/maps/public/layers/sources/source.js
@@ -132,7 +132,7 @@ export class AbstractSource {
}
// Returns function used to format value
- async getFieldFormatter(/* fieldName */) {
+ async createFieldFormatter(/* field */) {
return null;
}
@@ -140,7 +140,7 @@ export class AbstractSource {
throw new Error(`Source#loadStylePropsMeta not implemented`);
}
- async getValueSuggestions(/* fieldName, query */) {
+ async getValueSuggestions(/* field, query */) {
return [];
}
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js
index 6b08fc2a105c3a..8648b073a7b79a 100644
--- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js
+++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js
@@ -25,6 +25,9 @@ const mockField = {
getName() {
return 'foobar';
},
+ getRootName() {
+ return 'foobar';
+ },
supportsFieldMeta() {
return true;
},
diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js
index af78c4c0e461e8..e40c82e6276c7e 100644
--- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js
+++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js
@@ -13,7 +13,6 @@ import React from 'react';
import { OrdinalLegend } from './components/ordinal_legend';
import { CategoricalLegend } from './components/categorical_legend';
import { OrdinalFieldMetaOptionsPopover } from '../components/ordinal_field_meta_options_popover';
-import { ESAggMetricField } from '../../../fields/es_agg_field';
export class DynamicStyleProperty extends AbstractStyleProperty {
static type = STYLE_TYPE.DYNAMIC;
@@ -26,9 +25,9 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
}
getValueSuggestions = query => {
- const fieldName = this.getFieldName();
+ const field = this.getField();
const fieldSource = this.getFieldSource();
- return fieldSource && fieldName ? fieldSource.getValueSuggestions(fieldName, query) : [];
+ return fieldSource && field ? fieldSource.getValueSuggestions(field, query) : [];
};
getFieldMeta() {
@@ -185,11 +184,7 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
}
_pluckOrdinalStyleMetaFromFieldMetaData(fieldMetaData) {
- const realFieldName =
- this._field instanceof ESAggMetricField
- ? this._field.getESDocFieldName()
- : this._field.getName();
- const stats = fieldMetaData[realFieldName];
+ const stats = fieldMetaData[this._field.getRootName()];
if (!stats) {
return null;
}
@@ -209,15 +204,12 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
}
_pluckCategoricalStyleMetaFromFieldMetaData(fieldMetaData) {
- const realFieldName =
- this._field instanceof ESAggMetricField
- ? this._field.getESDocFieldName()
- : this._field.getName();
- if (!fieldMetaData[realFieldName] || !fieldMetaData[realFieldName].buckets) {
+ const rootFieldName = this._field.getRootName();
+ if (!fieldMetaData[rootFieldName] || !fieldMetaData[rootFieldName].buckets) {
return null;
}
- const ordered = fieldMetaData[realFieldName].buckets.map(bucket => {
+ const ordered = fieldMetaData[rootFieldName].buckets.map(bucket => {
return {
key: bucket.key,
count: bucket.doc_count,
diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js b/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js
index 229c84fe234bd9..ea000a78331eb7 100644
--- a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js
+++ b/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js
@@ -27,9 +27,7 @@ export class ESAggMetricTooltipProperty extends ESTooltipProperty {
) {
return this._rawValue;
}
- const indexPatternField = this._indexPattern.fields.getByName(
- this._metricField.getESDocFieldName()
- );
+ const indexPatternField = this._indexPattern.fields.getByName(this._metricField.getRootName());
if (!indexPatternField) {
return this._rawValue;
}
diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
index e1a30c8aef1d37..c515feecc15513 100644
--- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
+++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js
@@ -561,10 +561,13 @@ export class VectorLayer extends AbstractLayer {
startLoading(dataRequestId, requestToken, nextMeta);
const formatters = {};
- const promises = fields.map(async field => {
- const fieldName = field.getName();
- formatters[fieldName] = await source.getFieldFormatter(fieldName);
- });
+ const promises = fields
+ .filter(field => {
+ return field.canValueBeFormatted();
+ })
+ .map(async field => {
+ formatters[field.getName()] = await source.createFieldFormatter(field);
+ });
await Promise.all(promises);
stopLoading(dataRequestId, requestToken, formatters, nextMeta);
diff --git a/x-pack/legacy/plugins/ml/common/constants/app.ts b/x-pack/legacy/plugins/ml/common/constants/app.ts
index 140a709b0c42be..bbec35a17faa54 100644
--- a/x-pack/legacy/plugins/ml/common/constants/app.ts
+++ b/x-pack/legacy/plugins/ml/common/constants/app.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const API_BASE_PATH = '/api/transform/';
+export const PLUGIN_ID = 'ml';
diff --git a/x-pack/legacy/plugins/ml/common/constants/license.ts b/x-pack/legacy/plugins/ml/common/constants/license.ts
index 2027e2c8b18653..183844c9ef980c 100644
--- a/x-pack/legacy/plugins/ml/common/constants/license.ts
+++ b/x-pack/legacy/plugins/ml/common/constants/license.ts
@@ -8,3 +8,5 @@ export enum LICENSE_TYPE {
BASIC,
FULL, // >= platinum
}
+
+export const VALID_FULL_LICENSE_MODES = ['platinum', 'enterprise', 'trial'];
diff --git a/x-pack/legacy/plugins/ml/index.ts b/x-pack/legacy/plugins/ml/index.ts
index 09f1b9ccedce4f..47df7c8c3e5e62 100755
--- a/x-pack/legacy/plugins/ml/index.ts
+++ b/x-pack/legacy/plugins/ml/index.ts
@@ -6,23 +6,13 @@
import { resolve } from 'path';
import { i18n } from '@kbn/i18n';
-import KbnServer, { Server } from 'src/legacy/server/kbn_server';
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
-import { plugin } from './server/new_platform';
-import { CloudSetup } from '../../../plugins/cloud/server';
+import { Server } from 'src/legacy/server/kbn_server';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils';
-import {
- MlInitializerContext,
- MlCoreSetup,
- MlHttpServiceSetup,
-} from './server/new_platform/plugin';
+// @ts-ignore: could not find declaration file for module
+import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
// @ts-ignore: could not find declaration file for module
import mappings from './mappings';
-interface MlServer extends Server {
- addAppLinksToSampleDataset: () => {};
-}
-
export const ml = (kibana: any) => {
return new kibana.Plugin({
require: ['kibana', 'elasticsearch', 'xpack_main'],
@@ -60,43 +50,8 @@ export const ml = (kibana: any) => {
},
},
- async init(server: MlServer) {
- const kbnServer = (server as unknown) as KbnServer;
-
- const initializerContext = ({
- legacyConfig: server.config(),
- logger: {
- get(...contextParts: string[]) {
- return kbnServer.newPlatform.coreContext.logger.get('plugins', 'ml', ...contextParts);
- },
- },
- } as unknown) as MlInitializerContext;
-
- const mlHttpService: MlHttpServiceSetup = {
- ...kbnServer.newPlatform.setup.core.http,
- route: server.route.bind(server),
- };
-
- const core: MlCoreSetup = {
- injectUiAppVars: server.injectUiAppVars,
- http: mlHttpService,
- savedObjects: server.savedObjects,
- coreSavedObjects: kbnServer.newPlatform.start.core.savedObjects,
- elasticsearch: kbnServer.newPlatform.setup.core.elasticsearch,
- };
- const { usageCollection, cloud, home } = kbnServer.newPlatform.setup.plugins;
- const plugins = {
- elasticsearch: server.plugins.elasticsearch, // legacy
- security: server.newPlatform.setup.plugins.security,
- xpackMain: server.plugins.xpack_main,
- spaces: server.plugins.spaces,
- home,
- usageCollection: usageCollection as UsageCollectionSetup,
- cloud: cloud as CloudSetup,
- ml: this,
- };
-
- plugin(initializerContext).setup(core, plugins);
+ async init(server: Server) {
+ mirrorPluginStatus(server.plugins.xpack_main, this);
},
});
};
diff --git a/x-pack/legacy/plugins/ml/kibana.json b/x-pack/legacy/plugins/ml/kibana.json
deleted file mode 100644
index f36b4848186906..00000000000000
--- a/x-pack/legacy/plugins/ml/kibana.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "id": "ml",
- "version": "0.0.1",
- "kibanaVersion": "kibana",
- "configPath": ["ml"],
- "server": true,
- "ui": true
-}
diff --git a/x-pack/legacy/plugins/ml/public/application/license/check_license.tsx b/x-pack/legacy/plugins/ml/public/application/license/check_license.tsx
index 96e6aab3779621..4af753ddb4d1f2 100644
--- a/x-pack/legacy/plugins/ml/public/application/license/check_license.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/license/check_license.tsx
@@ -82,9 +82,16 @@ function setLicenseExpired(features: any) {
}
}
}
-
+// Temporary hack for cutting over server to NP
function getFeatures() {
- return xpackInfo.get('features.ml');
+ return {
+ isAvailable: true,
+ showLinks: true,
+ enableLinks: true,
+ licenseType: 1,
+ hasExpired: false,
+ };
+ // return xpackInfo.get('features.ml');
}
function redirectToKibana() {
diff --git a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.d.ts b/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.d.ts
deleted file mode 100644
index bf2e656afff123..00000000000000
--- a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.d.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch';
-
-export function callWithInternalUserFactory(elasticsearchPlugin: ElasticsearchPlugin): any;
diff --git a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.js b/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.js
deleted file mode 100644
index 2e5431bdd6ce2d..00000000000000
--- a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { once } from 'lodash';
-
-const _callWithInternalUser = once(elasticsearchPlugin => {
- const { callWithInternalUser } = elasticsearchPlugin.getCluster('admin');
- return callWithInternalUser;
-});
-
-export const callWithInternalUserFactory = elasticsearchPlugin => {
- return (...args) => {
- return _callWithInternalUser(elasticsearchPlugin)(...args);
- };
-};
diff --git a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.test.ts b/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.test.ts
deleted file mode 100644
index be016cc13ed0f3..00000000000000
--- a/x-pack/legacy/plugins/ml/server/client/call_with_internal_user_factory.test.ts
+++ /dev/null
@@ -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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { callWithInternalUserFactory } from './call_with_internal_user_factory';
-
-describe('call_with_internal_user_factory', () => {
- describe('callWithInternalUserFactory', () => {
- let elasticsearchPlugin: any;
- let callWithInternalUser: any;
-
- beforeEach(() => {
- callWithInternalUser = jest.fn();
- elasticsearchPlugin = {
- getCluster: jest.fn(() => ({ callWithInternalUser })),
- };
- });
-
- it('should use internal user "admin"', () => {
- const callWithInternalUserInstance = callWithInternalUserFactory(elasticsearchPlugin);
- callWithInternalUserInstance();
-
- expect(elasticsearchPlugin.getCluster).toHaveBeenCalledWith('admin');
- });
- });
-});
diff --git a/x-pack/legacy/plugins/ml/server/client/call_with_request_factory.js b/x-pack/legacy/plugins/ml/server/client/call_with_request_factory.js
deleted file mode 100644
index b39a58b317500f..00000000000000
--- a/x-pack/legacy/plugins/ml/server/client/call_with_request_factory.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { once } from 'lodash';
-import { elasticsearchJsPlugin } from './elasticsearch_ml';
-
-const callWithRequest = once(elasticsearchPlugin => {
- const config = { plugins: [elasticsearchJsPlugin] };
- const cluster = elasticsearchPlugin.createCluster('ml', config);
-
- return cluster.callWithRequest;
-});
-
-export const callWithRequestFactory = (elasticsearchPlugin, request) => {
- return (...args) => {
- return callWithRequest(elasticsearchPlugin)(request, ...args);
- };
-};
diff --git a/x-pack/legacy/plugins/ml/server/lib/__tests__/security_utils.js b/x-pack/legacy/plugins/ml/server/lib/__tests__/security_utils.js
deleted file mode 100644
index 6e0181f49072e1..00000000000000
--- a/x-pack/legacy/plugins/ml/server/lib/__tests__/security_utils.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import expect from '@kbn/expect';
-import { isSecurityDisabled } from '../security_utils';
-
-describe('ML - security utils', () => {
- function mockXpackMainPluginFactory(isAvailable = true, isEnabled = true) {
- return {
- info: {
- isAvailable: () => isAvailable,
- feature: () => ({
- isEnabled: () => isEnabled,
- }),
- },
- };
- }
-
- describe('isSecurityDisabled', () => {
- it('returns not disabled for given mock server object #1', () => {
- expect(isSecurityDisabled(mockXpackMainPluginFactory())).to.be(false);
- });
-
- it('returns not disabled for given mock server object #2', () => {
- expect(isSecurityDisabled(mockXpackMainPluginFactory(false))).to.be(false);
- });
-
- it('returns disabled for given mock server object #3', () => {
- expect(isSecurityDisabled(mockXpackMainPluginFactory(true, false))).to.be(true);
- });
- });
-});
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_annotations/index.d.ts b/x-pack/legacy/plugins/ml/server/lib/check_annotations/index.d.ts
deleted file mode 100644
index dbd08eacd3ca2e..00000000000000
--- a/x-pack/legacy/plugins/ml/server/lib/check_annotations/index.d.ts
+++ /dev/null
@@ -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;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { IScopedClusterClient } from 'src/core/server';
-
-export function isAnnotationsFeatureAvailable(
- callAsCurrentUser: IScopedClusterClient['callAsCurrentUser']
-): boolean;
diff --git a/x-pack/legacy/plugins/ml/server/lib/security_utils.js b/x-pack/legacy/plugins/ml/server/lib/security_utils.js
deleted file mode 100644
index 27109e645b185b..00000000000000
--- a/x-pack/legacy/plugins/ml/server/lib/security_utils.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-/*
- * Contains utility functions related to x-pack security.
- */
-
-export function isSecurityDisabled(xpackMainPlugin) {
- const xpackInfo = xpackMainPlugin && xpackMainPlugin.info;
- // we assume that `xpack.isAvailable()` always returns `true` because we're inside x-pack
- // if for whatever reason it returns `false`, `isSecurityDisabled()` would also return `false`
- // which would result in follow-up behavior assuming security is enabled. This is intentional,
- // because it results in more defensive behavior.
- const securityInfo = xpackInfo && xpackInfo.isAvailable() && xpackInfo.feature('security');
- return securityInfo && securityInfo.isEnabled() === false;
-}
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts
deleted file mode 100644
index 43c276ac63a13d..00000000000000
--- a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import Boom from 'boom';
-import { i18n } from '@kbn/i18n';
-import { ServerRoute } from 'hapi';
-import { KibanaConfig, SavedObjectsLegacyService } from 'src/legacy/server/kbn_server';
-import {
- Logger,
- PluginInitializerContext,
- CoreSetup,
- IRouter,
- IScopedClusterClient,
- SavedObjectsServiceStart,
-} from 'src/core/server';
-import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch';
-import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
-import { ElasticsearchServiceSetup } from 'src/core/server';
-import { CloudSetup } from '../../../../../plugins/cloud/server';
-import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main';
-import { addLinksToSampleDatasets } from '../lib/sample_data_sets';
-import { checkLicense } from '../lib/check_license';
-// @ts-ignore: could not find declaration file for module
-import { mirrorPluginStatus } from '../../../../server/lib/mirror_plugin_status';
-import { LICENSE_TYPE } from '../../common/constants/license';
-import { annotationRoutes } from '../routes/annotations';
-import { jobRoutes } from '../routes/anomaly_detectors';
-import { dataFeedRoutes } from '../routes/datafeeds';
-import { indicesRoutes } from '../routes/indices';
-import { jobValidationRoutes } from '../routes/job_validation';
-import { makeMlUsageCollector } from '../lib/ml_telemetry';
-import { notificationRoutes } from '../routes/notification_settings';
-import { systemRoutes } from '../routes/system';
-import { dataFrameAnalyticsRoutes } from '../routes/data_frame_analytics';
-import { dataRecognizer } from '../routes/modules';
-import { dataVisualizerRoutes } from '../routes/data_visualizer';
-import { calendars } from '../routes/calendars';
-// @ts-ignore: could not find declaration file for module
-import { fieldsService } from '../routes/fields_service';
-import { filtersRoutes } from '../routes/filters';
-import { resultsServiceRoutes } from '../routes/results_service';
-import { jobServiceRoutes } from '../routes/job_service';
-import { jobAuditMessagesRoutes } from '../routes/job_audit_messages';
-import { fileDataVisualizerRoutes } from '../routes/file_data_visualizer';
-import { initMlServerLog, LogInitialization } from '../client/log';
-import { HomeServerPluginSetup } from '../../../../../../src/plugins/home/server';
-// @ts-ignore: could not find declaration file for module
-import { elasticsearchJsPlugin } from '../client/elasticsearch_ml';
-
-export const PLUGIN_ID = 'ml';
-
-type CoreHttpSetup = CoreSetup['http'];
-export interface MlHttpServiceSetup extends CoreHttpSetup {
- route(route: ServerRoute | ServerRoute[]): void;
-}
-
-export interface MlXpackMainPlugin extends XPackMainPlugin {
- status?: any;
-}
-
-export interface MlCoreSetup {
- injectUiAppVars: (id: string, callback: () => {}) => any;
- http: MlHttpServiceSetup;
- savedObjects: SavedObjectsLegacyService;
- coreSavedObjects: SavedObjectsServiceStart;
- elasticsearch: ElasticsearchServiceSetup;
-}
-export interface MlInitializerContext extends PluginInitializerContext {
- legacyConfig: KibanaConfig;
- log: Logger;
-}
-export interface PluginsSetup {
- elasticsearch: ElasticsearchPlugin;
- xpackMain: MlXpackMainPlugin;
- security: any;
- spaces: any;
- usageCollection?: UsageCollectionSetup;
- cloud?: CloudSetup;
- home?: HomeServerPluginSetup;
- // TODO: this is temporary for `mirrorPluginStatus`
- ml: any;
-}
-
-export interface RouteInitialization {
- commonRouteConfig: any;
- config?: any;
- elasticsearchPlugin: ElasticsearchPlugin;
- elasticsearchService: ElasticsearchServiceSetup;
- route(route: ServerRoute | ServerRoute[]): void;
- router: IRouter;
- xpackMainPlugin: MlXpackMainPlugin;
- savedObjects?: SavedObjectsServiceStart;
- spacesPlugin: any;
- securityPlugin: any;
- cloud?: CloudSetup;
-}
-
-declare module 'kibana/server' {
- interface RequestHandlerContext {
- ml?: {
- mlClient: IScopedClusterClient;
- };
- }
-}
-
-export class Plugin {
- private readonly pluginId: string = PLUGIN_ID;
- private config: any;
- private log: Logger;
-
- constructor(initializerContext: MlInitializerContext) {
- this.config = initializerContext.legacyConfig;
- this.log = initializerContext.logger.get();
- }
-
- public setup(core: MlCoreSetup, plugins: PluginsSetup) {
- const xpackMainPlugin: MlXpackMainPlugin = plugins.xpackMain;
- const { http, coreSavedObjects } = core;
- const pluginId = this.pluginId;
-
- mirrorPluginStatus(xpackMainPlugin, plugins.ml);
- xpackMainPlugin.status.once('green', () => {
- // Register a function that is called whenever the xpack info changes,
- // to re-compute the license check results for this plugin
- const mlFeature = xpackMainPlugin.info.feature(pluginId);
- mlFeature.registerLicenseCheckResultsGenerator(checkLicense);
-
- // Add links to the Kibana sample data sets if ml is enabled
- // and there is a full license (trial or platinum).
- if (mlFeature.isEnabled() === true && plugins.home) {
- const licenseCheckResults = mlFeature.getLicenseCheckResults();
- if (licenseCheckResults.licenseType === LICENSE_TYPE.FULL) {
- addLinksToSampleDatasets({
- addAppLinksToSampleDataset: plugins.home.sampleData.addAppLinksToSampleDataset,
- });
- }
- }
- });
-
- xpackMainPlugin.registerFeature({
- id: 'ml',
- name: i18n.translate('xpack.ml.featureRegistry.mlFeatureName', {
- defaultMessage: 'Machine Learning',
- }),
- icon: 'machineLearningApp',
- navLinkId: 'ml',
- app: ['ml', 'kibana'],
- catalogue: ['ml'],
- privileges: {},
- reserved: {
- privilege: {
- savedObject: {
- all: [],
- read: [],
- },
- ui: [],
- },
- description: i18n.translate('xpack.ml.feature.reserved.description', {
- defaultMessage:
- 'To grant users access, you should also assign either the machine_learning_user or machine_learning_admin role.',
- }),
- },
- });
-
- // Add server routes and initialize the plugin here
- const commonRouteConfig = {
- pre: [
- function forbidApiAccess() {
- const licenseCheckResults = xpackMainPlugin.info
- .feature(pluginId)
- .getLicenseCheckResults();
- if (licenseCheckResults.isAvailable) {
- return null;
- } else {
- throw Boom.forbidden(licenseCheckResults.message);
- }
- },
- ],
- };
-
- // Can access via new platform router's handler function 'context' parameter - context.ml.mlClient
- const mlClient = core.elasticsearch.createClient('ml', { plugins: [elasticsearchJsPlugin] });
- http.registerRouteHandlerContext('ml', (context, request) => {
- return {
- mlClient: mlClient.asScoped(request),
- };
- });
-
- const routeInitializationDeps: RouteInitialization = {
- commonRouteConfig,
- route: http.route,
- router: http.createRouter(),
- elasticsearchPlugin: plugins.elasticsearch,
- elasticsearchService: core.elasticsearch,
- xpackMainPlugin: plugins.xpackMain,
- spacesPlugin: plugins.spaces,
- securityPlugin: plugins.security,
- };
-
- const extendedRouteInitializationDeps: RouteInitialization = {
- ...routeInitializationDeps,
- config: this.config,
- savedObjects: coreSavedObjects,
- spacesPlugin: plugins.spaces,
- cloud: plugins.cloud,
- };
-
- const logInitializationDeps: LogInitialization = {
- log: this.log,
- };
-
- annotationRoutes(routeInitializationDeps);
- jobRoutes(routeInitializationDeps);
- dataFeedRoutes(routeInitializationDeps);
- dataFrameAnalyticsRoutes(routeInitializationDeps);
- indicesRoutes(routeInitializationDeps);
- jobValidationRoutes(extendedRouteInitializationDeps);
- notificationRoutes(routeInitializationDeps);
- systemRoutes(extendedRouteInitializationDeps);
- dataRecognizer(extendedRouteInitializationDeps);
- dataVisualizerRoutes(routeInitializationDeps);
- calendars(routeInitializationDeps);
- fieldsService(routeInitializationDeps);
- filtersRoutes(routeInitializationDeps);
- resultsServiceRoutes(routeInitializationDeps);
- jobServiceRoutes(routeInitializationDeps);
- jobAuditMessagesRoutes(routeInitializationDeps);
- fileDataVisualizerRoutes(extendedRouteInitializationDeps);
-
- initMlServerLog(logInitializationDeps);
- makeMlUsageCollector(plugins.usageCollection, coreSavedObjects);
- }
-
- public stop() {}
-}
diff --git a/x-pack/legacy/plugins/monitoring/common/constants.ts b/x-pack/legacy/plugins/monitoring/common/constants.ts
index 1fb6acdb915b88..9a4030f3eb2147 100644
--- a/x-pack/legacy/plugins/monitoring/common/constants.ts
+++ b/x-pack/legacy/plugins/monitoring/common/constants.ts
@@ -141,23 +141,12 @@ export const CLUSTER_ALERTS_ADDRESS_CONFIG_KEY = 'cluster_alerts.email_notificat
export const STANDALONE_CLUSTER_CLUSTER_UUID = '__standalone_cluster__';
-const INDEX_PATTERN_NEW = ',monitoring-*-7-*,monitoring-*-8-*';
-const INDEX_PATTERN_KIBANA_NEW = ',monitoring-kibana-7-*,monitoring-kibana-8-*';
-const INDEX_PATTERN_LOGSTASH_NEW = ',monitoring-logstash-7-*,monitoring-logstash-8-*';
-const INDEX_PATTERN_BEATS_NEW = ',monitoring-beats-7-*,monitoring-beats-8-*';
-const INDEX_ALERTS_NEW = ',monitoring-alerts-7,monitoring-alerts-8';
-const INDEX_PATTERN_ELASTICSEARCH_NEW = ',monitoring-es-7-*,monitoring-es-8-*';
-
-export const INDEX_PATTERN = '.monitoring-*-6-*,.monitoring-*-7-*' + INDEX_PATTERN_NEW;
-export const INDEX_PATTERN_KIBANA =
- '.monitoring-kibana-6-*,.monitoring-kibana-7-*' + INDEX_PATTERN_KIBANA_NEW;
-export const INDEX_PATTERN_LOGSTASH =
- '.monitoring-logstash-6-*,.monitoring-logstash-7-*' + INDEX_PATTERN_LOGSTASH_NEW;
-export const INDEX_PATTERN_BEATS =
- '.monitoring-beats-6-*,.monitoring-beats-7-*' + INDEX_PATTERN_BEATS_NEW;
-export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7' + INDEX_ALERTS_NEW;
-export const INDEX_PATTERN_ELASTICSEARCH =
- '.monitoring-es-6-*,.monitoring-es-7-*' + INDEX_PATTERN_ELASTICSEARCH_NEW;
+export const INDEX_PATTERN = '.monitoring-*-6-*,.monitoring-*-7-*';
+export const INDEX_PATTERN_KIBANA = '.monitoring-kibana-6-*,.monitoring-kibana-7-*';
+export const INDEX_PATTERN_LOGSTASH = '.monitoring-logstash-6-*,.monitoring-logstash-7-*';
+export const INDEX_PATTERN_BEATS = '.monitoring-beats-6-*,.monitoring-beats-7-*';
+export const INDEX_ALERTS = '.monitoring-alerts-6,.monitoring-alerts-7';
+export const INDEX_PATTERN_ELASTICSEARCH = '.monitoring-es-6-*,.monitoring-es-7-*';
// This is the unique token that exists in monitoring indices collected by metricbeat
export const METRICBEAT_INDEX_NAME_UNIQUE_TOKEN = '-mb-';
diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts
index ea29ac95eb03f3..a2ebe8231456f7 100644
--- a/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts
+++ b/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts
@@ -17,7 +17,6 @@ export { StateManagementConfigProvider } from 'ui/state_management/config_provid
export { AppStateProvider } from 'ui/state_management/app_state';
// @ts-ignore
export { EventsProvider } from 'ui/events';
-export { PersistedState } from 'ui/persisted_state';
// @ts-ignore
export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url';
export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router';
diff --git a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.js b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.test.js
similarity index 85%
rename from x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.js
rename to x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.test.js
index 8562bdb2b00293..75ca6434c4e7a1 100644
--- a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.js
+++ b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.test.js
@@ -6,11 +6,11 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
-import { getCollectionStatus } from '../';
+import { getCollectionStatus } from '..';
import { getIndexPatterns } from '../../../cluster/get_index_patterns';
const liveClusterUuid = 'a12';
-const mockReq = (searchResult = {}) => {
+const mockReq = (searchResult = {}, securityEnabled = true, userHasPermissions = true) => {
return {
server: {
newPlatform: {
@@ -40,6 +40,14 @@ const mockReq = (searchResult = {}) => {
},
},
plugins: {
+ xpack_main: {
+ info: {
+ isAvailable: () => true,
+ feature: () => ({
+ isEnabled: () => securityEnabled,
+ }),
+ },
+ },
elasticsearch: {
getCluster() {
return {
@@ -51,6 +59,13 @@ const mockReq = (searchResult = {}) => {
) {
return Promise.resolve({ cluster_uuid: liveClusterUuid });
}
+ if (
+ type === 'transport.request' &&
+ params &&
+ params.path === '/_security/user/_has_privileges'
+ ) {
+ return Promise.resolve({ has_all_requested: userHasPermissions });
+ }
if (type === 'transport.request' && params && params.path === '/_nodes') {
return Promise.resolve({ nodes: {} });
}
@@ -218,19 +233,7 @@ describe('getCollectionStatus', () => {
});
it('should detect products based on other indices', async () => {
- const req = mockReq(
- {},
- {
- responses: [
- { hits: { total: { value: 1 } } },
- { hits: { total: { value: 1 } } },
- { hits: { total: { value: 1 } } },
- { hits: { total: { value: 1 } } },
- { hits: { total: { value: 1 } } },
- ],
- }
- );
-
+ const req = mockReq({ hits: { total: { value: 1 } } });
const result = await getCollectionStatus(req, getIndexPatterns(req.server), liveClusterUuid);
expect(result.kibana.detected.doesExist).to.be(true);
@@ -238,4 +241,16 @@ describe('getCollectionStatus', () => {
expect(result.beats.detected.mightExist).to.be(true);
expect(result.logstash.detected.mightExist).to.be(true);
});
+
+ it('should work properly when security is disabled', async () => {
+ const req = mockReq({ hits: { total: { value: 1 } } }, false);
+ const result = await getCollectionStatus(req, getIndexPatterns(req.server), liveClusterUuid);
+ expect(result.kibana.detected.doesExist).to.be(true);
+ });
+
+ it('should not work if the user does not have the necessary permissions', async () => {
+ const req = mockReq({ hits: { total: { value: 1 } } }, true, false);
+ const result = await getCollectionStatus(req, getIndexPatterns(req.server), liveClusterUuid);
+ expect(result._meta.hasPermissions).to.be(false);
+ });
});
diff --git a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js
index 42d100b8af75e8..0029aaa9ce8eee 100644
--- a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js
+++ b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js
@@ -226,16 +226,25 @@ function isBeatFromAPM(bucket) {
}
async function hasNecessaryPermissions(req) {
- const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('data');
- const response = await callWithRequest(req, 'transport.request', {
- method: 'POST',
- path: '/_security/user/_has_privileges',
- body: {
- cluster: ['monitor'],
- },
- });
- // If there is some problem, assume they do not have access
- return get(response, 'has_all_requested', false);
+ try {
+ const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('data');
+ const response = await callWithRequest(req, 'transport.request', {
+ method: 'POST',
+ path: '/_security/user/_has_privileges',
+ body: {
+ cluster: ['monitor'],
+ },
+ });
+ // If there is some problem, assume they do not have access
+ return get(response, 'has_all_requested', false);
+ } catch (err) {
+ if (
+ err.message === 'no handler found for uri [/_security/user/_has_privileges] and method [POST]'
+ ) {
+ return true;
+ }
+ return false;
+ }
}
/**
diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/index.ts b/x-pack/legacy/plugins/siem/public/components/link_to/index.ts
index c93b415e017bb7..a1c1f78e398e33 100644
--- a/x-pack/legacy/plugins/siem/public/components/link_to/index.ts
+++ b/x-pack/legacy/plugins/siem/public/components/link_to/index.ts
@@ -17,6 +17,8 @@ export {
getCaseDetailsUrl,
getCaseUrl,
getCreateCaseUrl,
+ getConfigureCasesUrl,
RedirectToCasePage,
RedirectToCreatePage,
+ RedirectToConfigureCasesPage,
} from './redirect_to_case';
diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx
index c08b429dc4625e..08e4d1a3494e06 100644
--- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx
@@ -20,7 +20,11 @@ import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_ho
import { RedirectToNetworkPage } from './redirect_to_network';
import { RedirectToOverviewPage } from './redirect_to_overview';
import { RedirectToTimelinesPage } from './redirect_to_timelines';
-import { RedirectToCasePage, RedirectToCreatePage } from './redirect_to_case';
+import {
+ RedirectToCasePage,
+ RedirectToCreatePage,
+ RedirectToConfigureCasesPage,
+} from './redirect_to_case';
import { DetectionEngineTab } from '../../pages/detection_engine/types';
interface LinkToPageProps {
@@ -43,6 +47,11 @@ export const LinkToPage = React.memo(({ match }) => (
component={RedirectToCreatePage}
path={`${match.url}/:pageName(${SiemPageName.case})/create`}
/>
+
;
+export const RedirectToConfigureCasesPage = () => (
+
+);
const baseCaseUrl = `#/link-to/${SiemPageName.case}`;
export const getCaseUrl = () => baseCaseUrl;
export const getCaseDetailsUrl = (detailName: string) => `${baseCaseUrl}/${detailName}`;
export const getCreateCaseUrl = () => `${baseCaseUrl}/create`;
+export const getConfigureCasesUrl = () => `${baseCaseUrl}/configure`;
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap
index c883983f8cf010..0a60c8facff9c0 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap
@@ -498,3974 +498,3 @@ exports[`ZeekDetails rendering it renders the default ZeekDetails 1`] = `
timelineId="test"
/>
`;
-
-exports[`ZeekDetails rendering it returns zeek.files if the data does contain zeek.files data 1`] = `
-.c3,
-.c3::before,
-.c3::after {
- -webkit-transition: background 150ms ease, color 150ms ease;
- transition: background 150ms ease, color 150ms ease;
-}
-
-.c3 {
- border-radius: 2px;
- padding: 0 4px 0 8px;
- position: relative;
- z-index: 0 !important;
-}
-
-.c3::before {
- background-image: linear-gradient( 135deg, #535966 25%, transparent 25% ), linear-gradient( -135deg, #535966 25%, transparent 25% ), linear-gradient( 135deg, transparent 75%, #535966 75% ), linear-gradient( -135deg, transparent 75%, #535966 75% );
- background-position: 0 0,1px 0,1px -1px,0px 1px;
- background-size: 2px 2px;
- bottom: 2px;
- content: '';
- display: block;
- left: 2px;
- position: absolute;
- top: 2px;
- width: 4px;
-}
-
-.c3:hover,
-.c3:hover .euiBadge,
-.c3:hover .euiBadge__text {
- cursor: move;
- cursor: -webkit-grab;
- cursor: -moz-grab;
- cursor: grab;
-}
-
-.event-column-view:hover .c3,
-tr:hover .c3 {
- background-color: #343741;
-}
-
-.event-column-view:hover .c3::before,
-tr:hover .c3::before {
- background-image: linear-gradient( 135deg, #98a2b3 25%, transparent 25% ), linear-gradient( -135deg, #98a2b3 25%, transparent 25% ), linear-gradient( 135deg, transparent 75%, #98a2b3 75% ), linear-gradient( -135deg, transparent 75%, #98a2b3 75% );
-}
-
-.c3:hover,
-.c3:focus,
-.event-column-view:hover .c3:hover,
-.event-column-view:focus .c3:focus,
-tr:hover .c3:hover,
-tr:hover .c3:focus {
- background-color: #1ba9f5;
-}
-
-.c3:hover,
-.c3:focus,
-.event-column-view:hover .c3:hover,
-.event-column-view:focus .c3:focus,
-tr:hover .c3:hover,
-tr:hover .c3:focus,
-.c3:hover a,
-.c3:focus a,
-.event-column-view:hover .c3:hover a,
-.event-column-view:focus .c3:focus a,
-tr:hover .c3:hover a,
-tr:hover .c3:focus a,
-.c3:hover a:hover,
-.c3:focus a:hover,
-.event-column-view:hover .c3:hover a:hover,
-.event-column-view:focus .c3:focus a:hover,
-tr:hover .c3:hover a:hover,
-tr:hover .c3:focus a:hover {
- color: #1d1e24;
-}
-
-.c3:hover::before,
-.c3:focus::before,
-.event-column-view:hover .c3:hover::before,
-.event-column-view:focus .c3:focus::before,
-tr:hover .c3:hover::before,
-tr:hover .c3:focus::before {
- background-image: linear-gradient( 135deg, #1d1e24 25%, transparent 25% ), linear-gradient( -135deg, #1d1e24 25%, transparent 25% ), linear-gradient( 135deg, transparent 75%, #1d1e24 75% ), linear-gradient( -135deg, transparent 75%, #1d1e24 75% );
-}
-
-.c2 {
- display: inline-block;
- max-width: 100%;
-}
-
-.c2 [data-rbd-placeholder-context-id] {
- display: none !important;
-}
-
-.c4 > span.euiToolTipAnchor {
- display: block;
-}
-
-.c8 {
- margin: 0 2px;
-}
-
-.c7 {
- margin-top: 3px;
-}
-
-.c6 {
- margin-right: 10px;
-}
-
-.c1 {
- margin-left: 3px;
-}
-
-.c5 {
- margin-left: 6px;
-}
-
-.c0 {
- margin: 5px 0;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cu0n232QMyvNtzb75j
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- files
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- sha1: fa5195a...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- md5: f7653f1...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx
index db51ade6df4c5a..b45e4c41762bc7 100644
--- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx
@@ -113,7 +113,6 @@ describe('ZeekDetails', () => {
/>
);
- expect(wrapper).toMatchSnapshot();
expect(wrapper.text()).toEqual('Cu0n232QMyvNtzb75jfilessha1: fa5195a...md5: f7653f1...');
});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
index 1206ec950deed1..15a6d076f10095 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/case.tsx
@@ -6,30 +6,29 @@
import React from 'react';
-import { EuiButton, EuiFlexGroup } from '@elastic/eui';
-import { HeaderPage } from '../../components/header_page';
+import { EuiButton, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { CaseHeaderPage } from './components/case_header_page';
import { WrapperPage } from '../../components/wrapper_page';
import { AllCases } from './components/all_cases';
import { SpyRoute } from '../../utils/route/spy_routes';
import * as i18n from './translations';
-import { getCreateCaseUrl } from '../../components/link_to';
-
-const badgeOptions = {
- beta: true,
- text: i18n.PAGE_BADGE_LABEL,
- tooltip: i18n.PAGE_BADGE_TOOLTIP,
-};
+import { getCreateCaseUrl, getConfigureCasesUrl } from '../../components/link_to';
export const CasesPage = React.memo(() => (
<>
-
+
-
- {i18n.CREATE_TITLE}
-
+
+
+ {i18n.CREATE_TITLE}
+
+
+
+
+
-
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_header_page/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_header_page/index.tsx
new file mode 100644
index 00000000000000..ae2664ca6e839d
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_header_page/index.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+
+import { HeaderPage, HeaderPageProps } from '../../../../components/header_page';
+import * as i18n from './translations';
+
+const CaseHeaderPageComponent: React.FC = props => ;
+
+CaseHeaderPageComponent.defaultProps = {
+ badgeOptions: {
+ beta: true,
+ text: i18n.PAGE_BADGE_LABEL,
+ tooltip: i18n.PAGE_BADGE_TOOLTIP,
+ },
+};
+
+export const CaseHeaderPage = React.memo(CaseHeaderPageComponent);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_header_page/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/case_header_page/translations.ts
new file mode 100644
index 00000000000000..9fcad926c03b84
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_header_page/translations.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const PAGE_BADGE_LABEL = i18n.translate('xpack.siem.case.caseView.pageBadgeLabel', {
+ defaultMessage: 'Beta',
+});
+
+export const PAGE_BADGE_TOOLTIP = i18n.translate('xpack.siem.case.caseView.pageBadgeTooltip', {
+ defaultMessage:
+ 'Case Workflow is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.',
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
index 5cd71c5855d34f..df3e30a698b56e 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx
@@ -34,6 +34,7 @@ import { UserActionTree } from '../user_action_tree';
import { UserList } from '../user_list';
import { useUpdateCase } from '../../../../containers/case/use_update_case';
import { WrapperPage } from '../../../../components/wrapper_page';
+import { WhitePageWrapper } from '../wrappers';
interface Props {
caseId: string;
@@ -52,14 +53,6 @@ const MyWrapper = styled(WrapperPage)`
padding-bottom: 0;
`;
-const BackgroundWrapper = styled.div`
- ${({ theme }) => css`
- background-color: ${theme.eui.euiColorEmptyShade};
- border-top: ${theme.eui.euiBorderThin};
- height: 100%;
- `}
-`;
-
export interface CaseProps {
caseId: string;
initialData: Case;
@@ -279,7 +272,7 @@ export const CaseComponent = React.memo(({ caseId, initialData, isLoa
-
+
@@ -305,7 +298,7 @@ export const CaseComponent = React.memo(({ caseId, initialData, isLoa
-
+
>
);
});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx
new file mode 100644
index 00000000000000..561464e44c7032
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import {
+ EuiDescribedFormGroup,
+ EuiFormRow,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLink,
+} from '@elastic/eui';
+
+import styled from 'styled-components';
+
+import { ConnectorsDropdown } from './connectors_dropdown';
+import * as i18n from './translations';
+
+const EuiFormRowExtended = styled(EuiFormRow)`
+ .euiFormRow__labelWrapper {
+ .euiFormRow__label {
+ width: 100%;
+ }
+ }
+`;
+
+const ConnectorsComponent: React.FC = () => {
+ const dropDownLabel = (
+
+ {i18n.INCIDENT_MANAGEMENT_SYSTEM_LABEL}
+
+ {i18n.ADD_NEW_CONNECTOR}
+
+
+ );
+
+ return (
+ {i18n.INCIDENT_MANAGEMENT_SYSTEM_TITLE}}
+ description={i18n.INCIDENT_MANAGEMENT_SYSTEM_DESC}
+ >
+
+
+
+
+ );
+};
+
+export const Connectors = React.memo(ConnectorsComponent);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown/index.tsx
new file mode 100644
index 00000000000000..c00baa04d78a0b
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown/index.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState, useCallback } from 'react';
+import { EuiSuperSelect, EuiIcon, EuiSuperSelectOption } from '@elastic/eui';
+import styled from 'styled-components';
+
+import * as i18n from '../translations';
+
+const ICON_SIZE = 'm';
+
+const EuiIconExtended = styled(EuiIcon)`
+ margin-right: 13px;
+`;
+
+const connectors: Array> = [
+ {
+ value: 'no-connector',
+ inputDisplay: (
+ <>
+
+ {i18n.NO_CONNECTOR}
+ >
+ ),
+ 'data-test-subj': 'no-connector',
+ },
+ {
+ value: 'servicenow-connector',
+ inputDisplay: (
+ <>
+
+ {'My ServiceNow connector'}
+ >
+ ),
+ 'data-test-subj': 'servicenow-connector',
+ },
+];
+
+const ConnectorsDropdownComponent: React.FC = () => {
+ const [selectedConnector, selectConnector] = useState(connectors[0].value);
+ const onChange = useCallback(connector => selectConnector(connector), [selectedConnector]);
+
+ return (
+
+ );
+};
+
+export const ConnectorsDropdown = React.memo(ConnectorsDropdownComponent);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts
new file mode 100644
index 00000000000000..54d256b143f603
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const INCIDENT_MANAGEMENT_SYSTEM_TITLE = i18n.translate(
+ 'xpack.siem.case.configureCases.incidentManagementSystemTitle',
+ {
+ defaultMessage: 'Connect to third-party incident management system',
+ }
+);
+
+export const INCIDENT_MANAGEMENT_SYSTEM_DESC = i18n.translate(
+ 'xpack.siem.case.configureCases.incidentManagementSystemDesc',
+ {
+ defaultMessage:
+ 'You may optionally connect SIEM cases to a third-party incident management system of your choosing. This will allow you to push case data as an incident in your chosen third-party system.',
+ }
+);
+
+export const INCIDENT_MANAGEMENT_SYSTEM_LABEL = i18n.translate(
+ 'xpack.siem.case.configureCases.incidentManagementSystemLabel',
+ {
+ defaultMessage: 'Incident management system',
+ }
+);
+
+export const NO_CONNECTOR = i18n.translate('xpack.siem.case.configureCases.noConnector', {
+ defaultMessage: 'No connector selected',
+});
+
+export const ADD_NEW_CONNECTOR = i18n.translate('xpack.siem.case.configureCases.addNewConnector', {
+ defaultMessage: 'Add new connector option',
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/wrappers/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/wrappers/index.tsx
new file mode 100644
index 00000000000000..772d78f948b798
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/components/wrappers/index.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import styled, { css } from 'styled-components';
+
+export const WhitePageWrapper = styled.div`
+ ${({ theme }) => css`
+ background-color: ${theme.eui.euiColorEmptyShade};
+ border-top: ${theme.eui.euiBorderThin};
+ height: 100%;
+ min-height: 100vh;
+ `}
+`;
+
+export const SectionWrapper = styled.div`
+ box-sizing: content-box;
+ margin: 0 auto;
+ max-width: 1175px;
+`;
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx b/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx
new file mode 100644
index 00000000000000..018f9dc9ade52a
--- /dev/null
+++ b/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx
@@ -0,0 +1,54 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import styled, { css } from 'styled-components';
+
+import { WrapperPage } from '../../components/wrapper_page';
+import { CaseHeaderPage } from './components/case_header_page';
+import { SpyRoute } from '../../utils/route/spy_routes';
+import { getCaseUrl } from '../../components/link_to';
+import { WhitePageWrapper, SectionWrapper } from './components/wrappers';
+import { Connectors } from './components/configure_cases/connectors';
+import * as i18n from './translations';
+
+const backOptions = {
+ href: getCaseUrl(),
+ text: i18n.BACK_TO_ALL,
+};
+
+const wrapperPageStyle: Record = {
+ paddingLeft: '0',
+ paddingRight: '0',
+ paddingBottom: '0',
+};
+
+export const FormWrapper = styled.div`
+ ${({ theme }) => css`
+ padding-top: ${theme.eui.paddingSizes.l};
+ padding-bottom: ${theme.eui.paddingSizes.l};
+ `}
+`;
+
+const ConfigureCasesPageComponent: React.FC = () => (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+);
+
+export const ConfigureCasesPage = React.memo(ConfigureCasesPageComponent);
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx b/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx
index 9bc356517cc686..2c7525264f71be 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/create_case.tsx
@@ -9,7 +9,7 @@ import React from 'react';
import { WrapperPage } from '../../components/wrapper_page';
import { Create } from './components/create';
import { SpyRoute } from '../../utils/route/spy_routes';
-import { HeaderPage } from '../../components/header_page';
+import { CaseHeaderPage } from './components/case_header_page';
import * as i18n from './translations';
import { getCaseUrl } from '../../components/link_to';
@@ -17,15 +17,11 @@ const backOptions = {
href: getCaseUrl(),
text: i18n.BACK_TO_ALL,
};
-const badgeOptions = {
- beta: true,
- text: i18n.PAGE_BADGE_LABEL,
- tooltip: i18n.PAGE_BADGE_TOOLTIP,
-};
+
export const CreateCasePage = React.memo(() => (
<>
-
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/index.tsx
index 9bd91b1c6d62dd..1bde9de1535b52 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/case/index.tsx
@@ -11,10 +11,12 @@ import { SiemPageName } from '../home/types';
import { CaseDetailsPage } from './case_details';
import { CasesPage } from './case';
import { CreateCasePage } from './create_case';
+import { ConfigureCasesPage } from './configure_cases';
const casesPagePath = `/:pageName(${SiemPageName.case})`;
const caseDetailsPagePath = `${casesPagePath}/:detailName`;
const createCasePagePath = `${casesPagePath}/create`;
+const configureCasesPagePath = `${casesPagePath}/configure`;
const CaseContainerComponent: React.FC = () => (
@@ -24,6 +26,9 @@ const CaseContainerComponent: React.FC = () => (
+
+
+
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
index 4e878ba58411e4..265af0bde547f2 100644
--- a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
+++ b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts
@@ -57,15 +57,6 @@ export const LAST_UPDATED = i18n.translate('xpack.siem.case.caseView.updatedAt',
defaultMessage: 'Last updated',
});
-export const PAGE_BADGE_LABEL = i18n.translate('xpack.siem.case.caseView.pageBadgeLabel', {
- defaultMessage: 'Beta',
-});
-
-export const PAGE_BADGE_TOOLTIP = i18n.translate('xpack.siem.case.caseView.pageBadgeTooltip', {
- defaultMessage:
- 'Case Workflow is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.',
-});
-
export const PAGE_SUBTITLE = i18n.translate('xpack.siem.case.caseView.pageSubtitle', {
defaultMessage: 'Case Workflow Management within the Elastic SIEM',
});
@@ -102,3 +93,14 @@ export const NO_TAGS = i18n.translate('xpack.siem.case.caseView.noTags', {
export const TITLE_REQUIRED = i18n.translate('xpack.siem.case.createCase.titleFieldRequiredError', {
defaultMessage: 'A title is required.',
});
+
+export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate(
+ 'xpack.siem.case.configureCases.headerTitle',
+ {
+ defaultMessage: 'Configure cases',
+ }
+);
+
+export const CONFIGURE_CASES_BUTTON = i18n.translate('xpack.siem.case.configureCasesButton', {
+ defaultMessage: 'Configure cases',
+});
diff --git a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx
index 806f6c7937077c..5d20993144af96 100644
--- a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx
+++ b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx
@@ -21,6 +21,7 @@ import { AutoSaveWarningMsg } from '../../components/timeline/auto_save_warning'
import { UseUrlState } from '../../components/url_state';
import { useWithSource } from '../../containers/source';
import { SpyRoute } from '../../utils/route/spy_routes';
+import { useShowTimeline } from '../../utils/timeline/use_show_timeline';
import { NotFoundPage } from '../404';
import { DetectionEngineContainer } from '../detection_engine';
import { HostsContainer } from '../hosts';
@@ -74,6 +75,8 @@ export const HomePageComponent = () => {
const { browserFields, indexPattern, contentAvailable } = useWithSource();
+ const [showTimeline] = useShowTimeline();
+
return (
@@ -81,7 +84,7 @@ export const HomePageComponent = () => {
- {contentAvailable && (
+ {contentAvailable && showTimeline && (
<>
{
+ const currentLocation = useLocation();
+ const [showTimeline, setShowTimeline] = useState(
+ !hideTimelineForRoutes.includes(currentLocation.pathname)
+ );
+
+ useEffect(() => {
+ if (hideTimelineForRoutes.includes(currentLocation.pathname)) {
+ if (showTimeline) {
+ setShowTimeline(false);
+ }
+ } else if (!showTimeline) {
+ setShowTimeline(true);
+ }
+ }, [currentLocation.pathname]);
+
+ return [showTimeline];
+};
diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts
index ab3388ae964759..757c1eb557c543 100644
--- a/x-pack/legacy/plugins/spaces/index.ts
+++ b/x-pack/legacy/plugins/spaces/index.ts
@@ -34,19 +34,6 @@ export const spaces = (kibana: Record) =>
publicDir: resolve(__dirname, 'public'),
require: ['kibana', 'elasticsearch', 'xpack_main'],
- uiCapabilities() {
- return {
- spaces: {
- manage: true,
- },
- management: {
- kibana: {
- spaces: true,
- },
- },
- };
- },
-
uiExports: {
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
managementSections: [],
@@ -110,14 +97,9 @@ export const spaces = (kibana: Record) =>
throw new Error('New Platform XPack Spaces plugin is not available.');
}
- const config = server.config();
-
const { registerLegacyAPI, createDefaultSpace } = spacesPlugin.__legacyCompat;
registerLegacyAPI({
- legacyConfig: {
- kibanaIndex: config.get('kibana.index'),
- },
savedObjects: server.savedObjects,
auditLogger: {
create: (pluginId: string) =>
diff --git a/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts
index b55a4cd5c7bd6b..aa130b5030fc76 100644
--- a/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts
+++ b/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts
@@ -4,7 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
+jest.mock('ui/new_platform');
+
export function XJsonMode() {}
export function setDependencyCache() {}
+export const useRequest = () => ({
+ isLoading: false,
+ error: null,
+ data: undefined,
+});
export { mlInMemoryTableBasicFactory } from '../../../ml/public/application/components/ml_in_memory_table';
export const SORT_DIRECTION = { ASC: 'asc' };
diff --git a/x-pack/legacy/plugins/transform/public/app/app.tsx b/x-pack/legacy/plugins/transform/public/app/app.tsx
index 0f21afbcccca86..efbaabe447efa5 100644
--- a/x-pack/legacy/plugins/transform/public/app/app.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/app.tsx
@@ -5,8 +5,8 @@
*/
import React, { useContext, FC } from 'react';
-import { render } from 'react-dom';
-import { Redirect, Route, Switch } from 'react-router-dom';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { HashRouter, Redirect, Route, Switch } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -22,8 +22,7 @@ import { TransformManagementSection } from './sections/transform_management';
export const App: FC = () => {
const { apiError } = useContext(AuthorizationContext);
-
- if (apiError) {
+ if (apiError !== null) {
return (
{
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
);
};
-export const renderReact = (elem: Element, appDependencies: AppDependencies) => {
+export const renderApp = (element: HTMLElement, appDependencies: AppDependencies) => {
const Providers = getAppProviders(appDependencies);
render(
,
- elem
+ element
);
+
+ return () => {
+ unmountComponentAtNode(element);
+ };
};
diff --git a/x-pack/legacy/plugins/transform/public/app/app_dependencies.tsx b/x-pack/legacy/plugins/transform/public/app/app_dependencies.tsx
index 282d1380b396b0..21ffbf5911a216 100644
--- a/x-pack/legacy/plugins/transform/public/app/app_dependencies.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/app_dependencies.tsx
@@ -7,9 +7,6 @@
import React, { createContext, useContext, ReactNode } from 'react';
import { HashRouter } from 'react-router-dom';
-import chrome from 'ui/chrome';
-import { metadata } from 'ui/metadata';
-
import { API_BASE_PATH } from '../../common/constants';
import { setDependencyCache } from '../shared_imports';
@@ -17,24 +14,20 @@ import { AppDependencies } from '../shim';
import { AuthorizationProvider } from './lib/authorization';
-const legacyBasePath = {
- prepend: chrome.addBasePath,
- get: chrome.getBasePath,
- remove: () => {},
-};
-const legacyDocLinks = {
- ELASTIC_WEBSITE_URL: 'https://www.elastic.co/',
- DOC_LINK_VERSION: metadata.branch,
-};
-
let DependenciesContext: React.Context;
const setAppDependencies = (deps: AppDependencies) => {
+ const legacyBasePath = {
+ prepend: deps.core.http.basePath.prepend,
+ get: deps.core.http.basePath.get,
+ remove: () => {},
+ };
+
setDependencyCache({
autocomplete: deps.plugins.data.autocomplete,
- docLinks: legacyDocLinks as any,
+ docLinks: deps.core.docLinks,
basePath: legacyBasePath as any,
- XSRF: chrome.getXsrfToken(),
+ XSRF: deps.plugins.xsrfToken,
});
DependenciesContext = createContext(deps);
return DependenciesContext.Provider;
@@ -48,6 +41,22 @@ export const useAppDependencies = () => {
return useContext(DependenciesContext);
};
+export const useDocumentationLinks = () => {
+ const {
+ core: { documentation },
+ } = useAppDependencies();
+ return documentation;
+};
+
+export const useToastNotifications = () => {
+ const {
+ core: {
+ notifications: { toasts: toastNotifications },
+ },
+ } = useAppDependencies();
+ return toastNotifications;
+};
+
export const getAppProviders = (deps: AppDependencies) => {
const I18nContext = deps.core.i18n.Context;
@@ -55,9 +64,7 @@ export const getAppProviders = (deps: AppDependencies) => {
const AppDependenciesProvider = setAppDependencies(deps);
return ({ children }: { children: ReactNode }) => (
-
+
{children}
diff --git a/x-pack/legacy/plugins/transform/public/app/common/navigation.tsx b/x-pack/legacy/plugins/transform/public/app/common/navigation.tsx
index ac98d92fdba837..15966a93e1f429 100644
--- a/x-pack/legacy/plugins/transform/public/app/common/navigation.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/common/navigation.tsx
@@ -29,9 +29,9 @@ export function getDiscoverUrl(indexPatternId: string, baseUrl: string): string
}
export const RedirectToTransformManagement: FC = () => (
-
+
);
export const RedirectToCreateTransform: FC<{ savedObjectId: string }> = ({ savedObjectId }) => (
-
+
);
diff --git a/x-pack/legacy/plugins/transform/public/app/components/section_error.tsx b/x-pack/legacy/plugins/transform/public/app/components/section_error.tsx
index 2ad6f0870c140d..4bef917a91a18b 100644
--- a/x-pack/legacy/plugins/transform/public/app/components/section_error.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/components/section_error.tsx
@@ -4,18 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiCallOut, EuiSpacer } from '@elastic/eui';
-import React, { Fragment } from 'react';
+import { EuiCallOut } from '@elastic/eui';
+import React from 'react';
interface Props {
title: React.ReactNode;
- error: {
- data: {
- error: string;
- cause?: string[];
- message?: string;
- };
- };
+ error: Error | null;
actions?: JSX.Element;
}
@@ -25,25 +19,11 @@ export const SectionError: React.FunctionComponent = ({
actions,
...rest
}) => {
- const {
- error: errorString,
- cause, // wrapEsError() on the server adds a "cause" array
- message,
- } = error.data;
+ const errorMessage = error?.message ?? JSON.stringify(error, null, 2);
return (
- {cause ? message || errorString : {message || errorString}
}
- {cause && (
-
-
-
- {cause.map((causeMsg, i) => (
- - {causeMsg}
- ))}
-
-
- )}
+ {errorMessage}
{actions ? actions : null}
);
diff --git a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.test.tsx b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.test.tsx
index 904d788b04e2c7..81af5c974fe04a 100644
--- a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.test.tsx
@@ -6,23 +6,44 @@
import React from 'react';
import { render } from '@testing-library/react';
+
+import { KibanaContext } from '../lib/kibana';
+import { createPublicShim } from '../../shim';
+import { getAppProviders } from '../app_dependencies';
+
import { ToastNotificationText } from './toast_notification_text';
+jest.mock('../../shared_imports');
+
describe('ToastNotificationText', () => {
test('should render the text as plain text', () => {
+ const Providers = getAppProviders(createPublicShim());
const props = {
text: 'a short text message',
};
- const { container } = render();
+ const { container } = render(
+
+
+
+
+
+ );
expect(container.textContent).toBe('a short text message');
});
test('should render the text within a modal', () => {
+ const Providers = getAppProviders(createPublicShim());
const props = {
text:
'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 characters. ',
};
- const { container } = render();
+ const { container } = render(
+
+
+
+
+
+ );
expect(container.textContent).toBe(
'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 ...View details'
);
diff --git a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx
index c79bf52a86642a..4e0a0a12558d82 100644
--- a/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/components/toast_notification_text.tsx
@@ -18,12 +18,17 @@ import {
import { i18n } from '@kbn/i18n';
-import { npStart } from 'ui/new_platform';
import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public';
+import { useAppDependencies } from '../app_dependencies';
+
const MAX_SIMPLE_MESSAGE_LENGTH = 140;
export const ToastNotificationText: FC<{ text: any }> = ({ text }) => {
+ const {
+ core: { overlays },
+ } = useAppDependencies();
+
if (typeof text === 'string' && text.length <= MAX_SIMPLE_MESSAGE_LENGTH) {
return text;
}
@@ -43,7 +48,7 @@ export const ToastNotificationText: FC<{ text: any }> = ({ text }) => {
}`;
const openModal = () => {
- const modal = npStart.core.overlays.openModal(
+ const modal = overlays.openModal(
toMountPoint(
modal.close()}>
diff --git a/x-pack/legacy/plugins/transform/public/app/constants/index.ts b/x-pack/legacy/plugins/transform/public/app/constants/index.ts
index 78b5f018dd782e..5d71980c83714a 100644
--- a/x-pack/legacy/plugins/transform/public/app/constants/index.ts
+++ b/x-pack/legacy/plugins/transform/public/app/constants/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const CLIENT_BASE_PATH = '/management/elasticsearch/transform';
+export const CLIENT_BASE_PATH = '/management/elasticsearch/transform/';
export enum SECTION_SLUG {
HOME = 'transform_management',
diff --git a/x-pack/legacy/plugins/transform/public/app/hooks/index.ts b/x-pack/legacy/plugins/transform/public/app/hooks/index.ts
index 7981f560a525fa..a36550bcd8e570 100644
--- a/x-pack/legacy/plugins/transform/public/app/hooks/index.ts
+++ b/x-pack/legacy/plugins/transform/public/app/hooks/index.ts
@@ -9,3 +9,4 @@ export { useGetTransforms } from './use_get_transforms';
export { useDeleteTransforms } from './use_delete_transform';
export { useStartTransforms } from './use_start_transform';
export { useStopTransforms } from './use_stop_transform';
+export { useRequest } from './use_request';
diff --git a/x-pack/legacy/plugins/transform/public/app/hooks/use_api.ts b/x-pack/legacy/plugins/transform/public/app/hooks/use_api.ts
index c71299eccb34d3..802599aaedd4f8 100644
--- a/x-pack/legacy/plugins/transform/public/app/hooks/use_api.ts
+++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_api.ts
@@ -5,14 +5,12 @@
*/
import { useAppDependencies } from '../app_dependencies';
-
import { PreviewRequestBody, TransformId } from '../common';
-
-import { http } from '../services/http_service';
+import { httpFactory, Http } from '../services/http_service';
import { EsIndex, TransformEndpointRequest, TransformEndpointResult } from './use_api_types';
-const apiFactory = (basePath: string, indicesBasePath: string) => ({
+const apiFactory = (basePath: string, indicesBasePath: string, http: Http) => ({
getTransforms(transformId?: TransformId): Promise {
const transformIdString = transformId !== undefined ? `/${transformId}` : '';
return http({
@@ -98,6 +96,8 @@ export const useApi = () => {
const basePath = appDeps.core.http.basePath.prepend('/api/transform');
const indicesBasePath = appDeps.core.http.basePath.prepend('/api');
+ const xsrfToken = appDeps.plugins.xsrfToken;
+ const http = httpFactory(xsrfToken);
- return apiFactory(basePath, indicesBasePath);
+ return apiFactory(basePath, indicesBasePath, http);
};
diff --git a/x-pack/legacy/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/legacy/plugins/transform/public/app/hooks/use_delete_transform.tsx
index e23151900447c2..83f456231cb856 100644
--- a/x-pack/legacy/plugins/transform/public/app/hooks/use_delete_transform.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_delete_transform.tsx
@@ -7,9 +7,9 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
-import { toastNotifications } from 'ui/notify';
import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public';
+import { useToastNotifications } from '../app_dependencies';
import { TransformListRow, refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common';
import { ToastNotificationText } from '../components';
@@ -17,6 +17,7 @@ import { useApi } from './use_api';
import { TransformEndpointRequest, TransformEndpointResult } from './use_api_types';
export const useDeleteTransforms = () => {
+ const toastNotifications = useToastNotifications();
const api = useApi();
return async (transforms: TransformListRow[]) => {
@@ -54,7 +55,9 @@ export const useDeleteTransforms = () => {
title: i18n.translate('xpack.transform.transformList.deleteTransformGenericErrorMessage', {
defaultMessage: 'An error occurred calling the API endpoint to delete transforms.',
}),
- text: toMountPoint(),
+ text: toMountPoint(
+
+ ),
});
}
};
diff --git a/x-pack/legacy/plugins/transform/public/app/hooks/use_request.ts b/x-pack/legacy/plugins/transform/public/app/hooks/use_request.ts
new file mode 100644
index 00000000000000..8c489048a77ef8
--- /dev/null
+++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_request.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { UseRequestConfig, useRequest as _useRequest } from '../../shared_imports';
+
+import { useAppDependencies } from '../app_dependencies';
+
+export const useRequest = (config: UseRequestConfig) => {
+ const {
+ core: { http },
+ } = useAppDependencies();
+ return _useRequest(http, config);
+};
diff --git a/x-pack/legacy/plugins/transform/public/app/hooks/use_start_transform.ts b/x-pack/legacy/plugins/transform/public/app/hooks/use_start_transform.ts
index d6b216accebd94..f460d8200c6e44 100644
--- a/x-pack/legacy/plugins/transform/public/app/hooks/use_start_transform.ts
+++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_start_transform.ts
@@ -5,14 +5,15 @@
*/
import { i18n } from '@kbn/i18n';
-import { toastNotifications } from 'ui/notify';
+import { useToastNotifications } from '../app_dependencies';
import { TransformListRow, refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common';
import { useApi } from './use_api';
import { TransformEndpointRequest, TransformEndpointResult } from './use_api_types';
export const useStartTransforms = () => {
+ const toastNotifications = useToastNotifications();
const api = useApi();
return async (transforms: TransformListRow[]) => {
diff --git a/x-pack/legacy/plugins/transform/public/app/hooks/use_stop_transform.ts b/x-pack/legacy/plugins/transform/public/app/hooks/use_stop_transform.ts
index bf6edf995bb1fe..758c574a3f7cdc 100644
--- a/x-pack/legacy/plugins/transform/public/app/hooks/use_stop_transform.ts
+++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_stop_transform.ts
@@ -5,14 +5,15 @@
*/
import { i18n } from '@kbn/i18n';
-import { toastNotifications } from 'ui/notify';
+import { useToastNotifications } from '../app_dependencies';
import { TransformListRow, refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common';
import { useApi } from './use_api';
import { TransformEndpointRequest, TransformEndpointResult } from './use_api_types';
export const useStopTransforms = () => {
+ const toastNotifications = useToastNotifications();
const api = useApi();
return async (transforms: TransformListRow[]) => {
diff --git a/x-pack/legacy/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx b/x-pack/legacy/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx
index 8060659c78b80e..dde63710f56aae 100644
--- a/x-pack/legacy/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/lib/authorization/components/authorization_provider.tsx
@@ -5,20 +5,12 @@
*/
import React, { createContext } from 'react';
-import { useRequest } from '../../../services/http/use_request';
+import { useRequest } from '../../../hooks';
import { hasPrivilegeFactory, Capabilities, Privileges } from './common';
-interface ApiError {
- data: {
- error: string;
- cause?: string[];
- message?: string;
- };
-}
-
interface Authorization {
isLoading: boolean;
- apiError: ApiError | null;
+ apiError: Error | null;
privileges: Privileges;
capabilities: Capabilities;
}
@@ -58,7 +50,7 @@ export const AuthorizationProvider = ({ privilegesEndpoint, children }: Props) =
isLoading,
privileges: isLoading ? { ...initialValue.privileges } : privilegesData,
capabilities: { ...initialCapabalities },
- apiError: error ? (error as ApiError) : null,
+ apiError: error ? (error as Error) : null,
};
const hasPrivilege = hasPrivilegeFactory(value.privileges);
diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx
index b0a0371d2de86f..3acec1ea0e809c 100644
--- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx
@@ -22,7 +22,6 @@ export interface InitializedKibanaContextValue {
combinedQuery: any;
indexPatterns: IndexPatternsContract;
initialized: true;
- kbnBaseUrl: string;
kibanaConfig: IUiSettingsClient;
currentIndexPattern: IndexPattern;
currentSavedSearch?: SavedSearch;
diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx
index d2cf5f2b32910a..f2574a4a85f296 100644
--- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx
@@ -6,8 +6,6 @@
import React, { useEffect, useState, FC } from 'react';
-import { npStart } from 'ui/new_platform';
-
import { useAppDependencies } from '../../app_dependencies';
import {
@@ -19,15 +17,14 @@ import {
import { InitializedKibanaContextValue, KibanaContext, KibanaContextValue } from './kibana_context';
-const indexPatterns = npStart.plugins.data.indexPatterns;
-const savedObjectsClient = npStart.core.savedObjects.client;
-
interface Props {
savedObjectId: string;
}
export const KibanaProvider: FC = ({ savedObjectId, children }) => {
const appDeps = useAppDependencies();
+ const indexPatterns = appDeps.plugins.data.indexPatterns;
+ const savedObjectsClient = appDeps.core.savedObjects.client;
const savedSearches = appDeps.plugins.savedSearches.getClient();
const [contextValue, setContextValue] = useState({ initialized: false });
@@ -50,7 +47,7 @@ export const KibanaProvider: FC = ({ savedObjectId, children }) => {
// Just let fetchedSavedSearch stay undefined in case it doesn't exist.
}
- const kibanaConfig = npStart.core.uiSettings;
+ const kibanaConfig = appDeps.core.uiSettings;
const {
indexPattern: currentIndexPattern,
@@ -61,7 +58,6 @@ export const KibanaProvider: FC = ({ savedObjectId, children }) => {
const kibanaContext: InitializedKibanaContextValue = {
indexPatterns,
initialized: true,
- kbnBaseUrl: npStart.core.injectedMetadata.getBasePath(),
kibanaConfig,
combinedQuery,
currentIndexPattern,
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
index de96a4de329623..8f58bc94e7c121 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx
@@ -22,14 +22,13 @@ import {
EuiTitle,
} from '@elastic/eui';
-import { npStart } from 'ui/new_platform';
-
import { useApi } from '../../hooks/use_api';
import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
+
+import { useAppDependencies, useDocumentationLinks } from '../../app_dependencies';
import { TransformPivotConfig } from '../../common';
import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation';
-import { documentationLinksService } from '../../services/documentation';
import { PrivilegesWrapper } from '../../lib/authorization';
import {
getIndexPatternIdByTitle,
@@ -40,9 +39,6 @@ import {
import { Wizard } from '../create_transform/components/wizard';
-const indexPatterns = npStart.plugins.data.indexPatterns;
-const savedObjectsClient = npStart.core.savedObjects.client;
-
interface GetTransformsResponseOk {
count: number;
transforms: TransformPivotConfig[];
@@ -74,6 +70,12 @@ export const CloneTransformSection: FC = ({ match }) => {
const api = useApi();
+ const appDeps = useAppDependencies();
+ const savedObjectsClient = appDeps.core.savedObjects.client;
+ const indexPatterns = appDeps.plugins.data.indexPatterns;
+
+ const { esTransform } = useDocumentationLinks();
+
const transformId = match.params.transformId;
const [transformConfig, setTransformConfig] = useState();
@@ -154,7 +156,7 @@ export const CloneTransformSection: FC = ({ match }) => {
{
const r = jest.requireActual('react');
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx
index a0c91c070844bf..625c545ee8c46a 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx
@@ -11,8 +11,6 @@ import { KibanaContext } from '../../../../lib/kibana';
import { StepCreateForm } from './step_create_form';
-jest.mock('ui/new_platform');
-
// workaround to make React.memo() work with enzyme
jest.mock('react', () => {
const r = jest.requireActual('react');
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
index 2ca3253d72b449..312d8a30dab779 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx
@@ -6,7 +6,6 @@
import React, { Fragment, FC, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
-import { toastNotifications } from 'ui/notify';
import {
EuiButton,
@@ -30,13 +29,15 @@ import {
} from '@elastic/eui';
import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_react/public';
-import { ToastNotificationText } from '../../../../components';
-import { useApi } from '../../../../hooks/use_api';
-import { useKibanaContext } from '../../../../lib/kibana';
-import { RedirectToTransformManagement } from '../../../../common/navigation';
+
import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants';
import { getTransformProgress, getDiscoverUrl } from '../../../../common';
+import { useApi } from '../../../../hooks/use_api';
+import { useKibanaContext } from '../../../../lib/kibana';
+import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies';
+import { RedirectToTransformManagement } from '../../../../common/navigation';
+import { ToastNotificationText } from '../../../../components';
export interface StepDetailsExposedState {
created: boolean;
@@ -73,7 +74,9 @@ export const StepCreateForm: FC = React.memo(
undefined
);
+ const deps = useAppDependencies();
const kibanaContext = useKibanaContext();
+ const toastNotifications = useToastNotifications();
useEffect(() => {
onChange({ created, started, indexPatternId });
@@ -437,7 +440,7 @@ export const StepCreateForm: FC = React.memo(
defaultMessage: 'Use Discover to explore the transform.',
}
)}
- href={getDiscoverUrl(indexPatternId, kibanaContext.kbnBaseUrl)}
+ href={getDiscoverUrl(indexPatternId, deps.core.http.basePath.get())}
data-test-subj="transformWizardCardDiscover"
/>
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx
index a2aa056c1634dd..2ac4295da1eedc 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx
@@ -19,8 +19,6 @@ import {
import { PivotPreview } from './pivot_preview';
-jest.mock('ui/new_platform');
-
// workaround to make React.memo() work with enzyme
jest.mock('react', () => {
const r = jest.requireActual('react');
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx
index 0311b26304c308..44edd1340e8d68 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx
@@ -15,9 +15,7 @@ import {
PIVOT_SUPPORTED_AGGS,
PIVOT_SUPPORTED_GROUP_BY_AGGS,
} from '../../../../common';
-import { StepDefineForm, isAggNameConflict } from './step_define_form';
-
-jest.mock('ui/new_platform');
+import { StepDefineForm, getAggNameConflictToastMessages } from './step_define_form';
// workaround to make React.memo() work with enzyme
jest.mock('react', () => {
@@ -76,38 +74,78 @@ describe('Transform: isAggNameConflict()', () => {
};
// no conflict, completely different name, no namespacing involved
- expect(isAggNameConflict('the-other-agg-name', aggList, groupByList)).toBe(false);
+ expect(
+ getAggNameConflictToastMessages('the-other-agg-name', aggList, groupByList)
+ ).toHaveLength(0);
// no conflict, completely different name and no conflicting namespace
- expect(isAggNameConflict('the-other-agg-name.namespace', aggList, groupByList)).toBe(false);
+ expect(
+ getAggNameConflictToastMessages('the-other-agg-name.namespace', aggList, groupByList)
+ ).toHaveLength(0);
// exact match conflict on aggregation name
- expect(isAggNameConflict('the-agg-name', aggList, groupByList)).toBe(true);
+ expect(getAggNameConflictToastMessages('the-agg-name', aggList, groupByList)).toStrictEqual([
+ `An aggregation configuration with the name 'the-agg-name' already exists.`,
+ ]);
// namespace conflict with `the-agg-name` aggregation
- expect(isAggNameConflict('the-agg-name.namespace', aggList, groupByList)).toBe(true);
+ expect(
+ getAggNameConflictToastMessages('the-agg-name.namespace', aggList, groupByList)
+ ).toStrictEqual([
+ `Couldn't add configuration 'the-agg-name.namespace' because of a nesting conflict with 'the-agg-name'.`,
+ ]);
// exact match conflict on group-by name
- expect(isAggNameConflict('the-group-by-agg-name', aggList, groupByList)).toBe(true);
+ expect(
+ getAggNameConflictToastMessages('the-group-by-agg-name', aggList, groupByList)
+ ).toStrictEqual([
+ `A group by configuration with the name 'the-group-by-agg-name' already exists.`,
+ ]);
// namespace conflict with `the-group-by-agg-name` group-by
- expect(isAggNameConflict('the-group-by-agg-name.namespace', aggList, groupByList)).toBe(true);
+ expect(
+ getAggNameConflictToastMessages('the-group-by-agg-name.namespace', aggList, groupByList)
+ ).toStrictEqual([
+ `Couldn't add configuration 'the-group-by-agg-name.namespace' because of a nesting conflict with 'the-group-by-agg-name'.`,
+ ]);
// exact match conflict on namespaced agg name
- expect(isAggNameConflict('the-namespaced-agg-name.namespace', aggList, groupByList)).toBe(true);
+ expect(
+ getAggNameConflictToastMessages('the-namespaced-agg-name.namespace', aggList, groupByList)
+ ).toStrictEqual([
+ `An aggregation configuration with the name 'the-namespaced-agg-name.namespace' already exists.`,
+ ]);
// no conflict, same base agg name but different namespace
- expect(isAggNameConflict('the-namespaced-agg-name.namespace2', aggList, groupByList)).toBe(
- false
- );
+ expect(
+ getAggNameConflictToastMessages('the-namespaced-agg-name.namespace2', aggList, groupByList)
+ ).toHaveLength(0);
// namespace conflict because the new agg name is base name of existing nested field
- expect(isAggNameConflict('the-namespaced-agg-name', aggList, groupByList)).toBe(true);
+ expect(
+ getAggNameConflictToastMessages('the-namespaced-agg-name', aggList, groupByList)
+ ).toStrictEqual([
+ `Couldn't add configuration 'the-namespaced-agg-name' because of a nesting conflict with 'the-namespaced-agg-name.namespace'.`,
+ ]);
// exact match conflict on namespaced group-by name
expect(
- isAggNameConflict('the-namespaced-group-by-agg-name.namespace', aggList, groupByList)
- ).toBe(true);
+ getAggNameConflictToastMessages(
+ 'the-namespaced-group-by-agg-name.namespace',
+ aggList,
+ groupByList
+ )
+ ).toStrictEqual([
+ `A group by configuration with the name 'the-namespaced-group-by-agg-name.namespace' already exists.`,
+ ]);
// no conflict, same base group-by name but different namespace
expect(
- isAggNameConflict('the-namespaced-group-by-agg-name.namespace2', aggList, groupByList)
- ).toBe(false);
+ getAggNameConflictToastMessages(
+ 'the-namespaced-group-by-agg-name.namespace2',
+ aggList,
+ groupByList
+ )
+ ).toHaveLength(0);
// namespace conflict because the new group-by name is base name of existing nested field
- expect(isAggNameConflict('the-namespaced-group-by-agg-name', aggList, groupByList)).toBe(true);
+ expect(
+ getAggNameConflictToastMessages('the-namespaced-group-by-agg-name', aggList, groupByList)
+ ).toStrictEqual([
+ `Couldn't add configuration 'the-namespaced-group-by-agg-name' because of a nesting conflict with 'the-namespaced-group-by-agg-name.namespace'.`,
+ ]);
});
});
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
index 1499f99f828248..3adb74e4704dcd 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx
@@ -9,9 +9,6 @@ import React, { Fragment, FC, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
-import { metadata } from 'ui/metadata';
-import { toastNotifications } from 'ui/notify';
-
import {
EuiButton,
EuiCodeEditor,
@@ -29,6 +26,7 @@ import {
} from '@elastic/eui';
import { useXJsonMode, xJsonMode } from '../../../../hooks/use_x_json_mode';
+import { useDocumentationLinks, useToastNotifications } from '../../../../app_dependencies';
import { TransformPivotConfig } from '../../../../common';
import { dictionaryToArray, Dictionary } from '../../../../../../common/types/common';
import { DropDown } from '../aggregation_dropdown';
@@ -147,32 +145,30 @@ export function applyTransformConfigToDefineState(
return state;
}
-export function isAggNameConflict(
+export function getAggNameConflictToastMessages(
aggName: AggName,
aggList: PivotAggsConfigDict,
groupByList: PivotGroupByConfigDict
-) {
+): string[] {
if (aggList[aggName] !== undefined) {
- toastNotifications.addDanger(
+ return [
i18n.translate('xpack.transform.stepDefineForm.aggExistsErrorMessage', {
defaultMessage: `An aggregation configuration with the name '{aggName}' already exists.`,
values: { aggName },
- })
- );
- return true;
+ }),
+ ];
}
if (groupByList[aggName] !== undefined) {
- toastNotifications.addDanger(
+ return [
i18n.translate('xpack.transform.stepDefineForm.groupByExistsErrorMessage', {
defaultMessage: `A group by configuration with the name '{aggName}' already exists.`,
values: { aggName },
- })
- );
- return true;
+ }),
+ ];
}
- let conflict = false;
+ const conflicts: string[] = [];
// check the new aggName against existing aggs and groupbys
const aggNameSplit = aggName.split('.');
@@ -180,29 +176,28 @@ export function isAggNameConflict(
aggNameSplit.forEach(aggNamePart => {
aggNameCheck = aggNameCheck === undefined ? aggNamePart : `${aggNameCheck}.${aggNamePart}`;
if (aggList[aggNameCheck] !== undefined || groupByList[aggNameCheck] !== undefined) {
- toastNotifications.addDanger(
+ conflicts.push(
i18n.translate('xpack.transform.stepDefineForm.nestedConflictErrorMessage', {
defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{aggNameCheck}'.`,
values: { aggName, aggNameCheck },
})
);
- conflict = true;
}
});
- if (conflict) {
- return true;
+ if (conflicts.length > 0) {
+ return conflicts;
}
// check all aggs against new aggName
- conflict = Object.keys(aggList).some(aggListName => {
+ Object.keys(aggList).some(aggListName => {
const aggListNameSplit = aggListName.split('.');
let aggListNameCheck: string;
return aggListNameSplit.some(aggListNamePart => {
aggListNameCheck =
aggListNameCheck === undefined ? aggListNamePart : `${aggListNameCheck}.${aggListNamePart}`;
if (aggListNameCheck === aggName) {
- toastNotifications.addDanger(
+ conflicts.push(
i18n.translate('xpack.transform.stepDefineForm.nestedAggListConflictErrorMessage', {
defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{aggListName}'.`,
values: { aggName, aggListName },
@@ -214,12 +209,12 @@ export function isAggNameConflict(
});
});
- if (conflict) {
- return true;
+ if (conflicts.length > 0) {
+ return conflicts;
}
// check all group-bys against new aggName
- conflict = Object.keys(groupByList).some(groupByListName => {
+ Object.keys(groupByList).some(groupByListName => {
const groupByListNameSplit = groupByListName.split('.');
let groupByListNameCheck: string;
return groupByListNameSplit.some(groupByListNamePart => {
@@ -228,7 +223,7 @@ export function isAggNameConflict(
? groupByListNamePart
: `${groupByListNameCheck}.${groupByListNamePart}`;
if (groupByListNameCheck === aggName) {
- toastNotifications.addDanger(
+ conflicts.push(
i18n.translate('xpack.transform.stepDefineForm.nestedGroupByListConflictErrorMessage', {
defaultMessage: `Couldn't add configuration '{aggName}' because of a nesting conflict with '{groupByListName}'.`,
values: { aggName, groupByListName },
@@ -240,7 +235,7 @@ export function isAggNameConflict(
});
});
- return conflict;
+ return conflicts;
}
interface Props {
@@ -250,6 +245,8 @@ interface Props {
export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange }) => {
const kibanaContext = useKibanaContext();
+ const toastNotifications = useToastNotifications();
+ const { esQueryDsl, esTransformPivot } = useDocumentationLinks();
const defaults = { ...getDefaultStepDefineState(kibanaContext), ...overrides };
@@ -288,7 +285,9 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
const config: PivotGroupByConfig = groupByOptionsData[label];
const aggName: AggName = config.aggName;
- if (isAggNameConflict(aggName, aggList, groupByList)) {
+ const aggNameConflictMessages = getAggNameConflictToastMessages(aggName, aggList, groupByList);
+ if (aggNameConflictMessages.length > 0) {
+ aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m));
return;
}
@@ -300,7 +299,13 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
const groupByListWithoutPrevious = { ...groupByList };
delete groupByListWithoutPrevious[previousAggName];
- if (isAggNameConflict(item.aggName, aggList, groupByListWithoutPrevious)) {
+ const aggNameConflictMessages = getAggNameConflictToastMessages(
+ item.aggName,
+ aggList,
+ groupByListWithoutPrevious
+ );
+ if (aggNameConflictMessages.length > 0) {
+ aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m));
return;
}
@@ -321,7 +326,9 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
const config: PivotAggsConfig = aggOptionsData[label];
const aggName: AggName = config.aggName;
- if (isAggNameConflict(aggName, aggList, groupByList)) {
+ const aggNameConflictMessages = getAggNameConflictToastMessages(aggName, aggList, groupByList);
+ if (aggNameConflictMessages.length > 0) {
+ aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m));
return;
}
@@ -333,7 +340,13 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
const aggListWithoutPrevious = { ...aggList };
delete aggListWithoutPrevious[previousAggName];
- if (isAggNameConflict(item.aggName, aggListWithoutPrevious, groupByList)) {
+ const aggNameConflictMessages = getAggNameConflictToastMessages(
+ item.aggName,
+ aggListWithoutPrevious,
+ groupByList
+ );
+ if (aggNameConflictMessages.length > 0) {
+ aggNameConflictMessages.forEach(m => toastNotifications.addDanger(m));
return;
}
@@ -477,15 +490,13 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
setAdvancedSourceEditorApplyButtonEnabled(false);
};
- // metadata.branch corresponds to the version used in documentation links.
- const docsUrl = `https://www.elastic.co/guide/en/elasticsearch/reference/${metadata.branch}/transform-resource.html#transform-pivot`;
const advancedEditorHelpText = (
{i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpText', {
defaultMessage:
'The advanced editor allows you to edit the pivot configuration of the transform.',
})}{' '}
-
+
{i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpTextLink', {
defaultMessage: 'Learn more about available options.',
})}
@@ -493,14 +504,13 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange
);
- const sourceDocsUrl = `https://www.elastic.co/guide/en/elasticsearch/reference/${metadata.branch}/query-dsl.html`;
const advancedSourceEditorHelpText = (
{i18n.translate('xpack.transform.stepDefineForm.advancedSourceEditorHelpText', {
defaultMessage:
'The advanced editor allows you to edit the source query clause of the transform.',
})}{' '}
-
+
{i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpTextLink', {
defaultMessage: 'Learn more about available options.',
})}
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx
index aae366e6008d55..78f6fc30f91911 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx
@@ -18,8 +18,6 @@ import {
import { StepDefineExposedState } from './step_define_form';
import { StepDefineSummary } from './step_define_summary';
-jest.mock('ui/new_platform');
-
// workaround to make React.memo() work with enzyme
jest.mock('react', () => {
const r = jest.requireActual('react');
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
index 220923f88ed363..5ae2180bfe7790 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx
@@ -7,8 +7,6 @@
import React, { Fragment, FC, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
-import { metadata } from 'ui/metadata';
-import { toastNotifications } from 'ui/notify';
import { EuiLink, EuiSwitch, EuiFieldText, EuiForm, EuiFormRow, EuiSelect } from '@elastic/eui';
@@ -16,6 +14,7 @@ import { toMountPoint } from '../../../../../../../../../../src/plugins/kibana_r
import { useKibanaContext } from '../../../../lib/kibana';
import { isValidIndexName } from '../../../../../../common/utils/es_utils';
+import { useDocumentationLinks, useToastNotifications } from '../../../../app_dependencies';
import { ToastNotificationText } from '../../../../components';
import { useApi } from '../../../../hooks/use_api';
@@ -72,6 +71,8 @@ interface Props {
export const StepDetailsForm: FC = React.memo(({ overrides = {}, onChange }) => {
const kibanaContext = useKibanaContext();
+ const toastNotifications = useToastNotifications();
+ const { esIndicesCreateIndex } = useDocumentationLinks();
const defaults = { ...getDefaultStepDetailsState(), ...overrides };
@@ -274,10 +275,7 @@ export const StepDetailsForm: FC = React.memo(({ overrides = {}, onChange
defaultMessage: 'Invalid destination index name.',
})}
-
+
{i18n.translate(
'xpack.transform.stepDetailsForm.destinationIndexInvalidErrorLink',
{
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx
index ae82f03718c02f..e92ba256256a4a 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/create_transform_section.tsx
@@ -22,8 +22,9 @@ import {
} from '@elastic/eui';
import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
+
+import { useDocumentationLinks } from '../../app_dependencies';
import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation';
-import { documentationLinksService } from '../../services/documentation';
import { PrivilegesWrapper } from '../../lib/authorization';
import { KibanaProvider, RenderOnlyWithInitializedKibanaContext } from '../../lib/kibana';
@@ -37,6 +38,8 @@ export const CreateTransformSection: FC = ({ match }) => {
docTitleService.setTitle('createTransform');
}, []);
+ const { esTransform } = useDocumentationLinks();
+
return (
@@ -65,7 +68,7 @@ export const CreateTransformSection: FC = ({ match }) => {
', () => {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx
index 40098ac7ef72ad..4b333f73f048c1 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx
@@ -30,7 +30,7 @@ export const CloneAction: FC = ({ itemId }) => {
});
function clickHandler() {
- history.push(`${CLIENT_BASE_PATH}/${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`);
+ history.push(`${CLIENT_BASE_PATH}${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`);
}
const cloneButton = (
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx
index 4795a2eb7d7bc2..82b9f0a292bb97 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx
@@ -16,7 +16,6 @@ import { DeleteAction } from './action_delete';
import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
jest.mock('ui/new_platform');
-
jest.mock('../../../../../shared_imports');
describe('Transform: Transform List Actions ', () => {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx
index 5f4d4a71c71eba..002b4ea19f9678 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx
@@ -16,7 +16,6 @@ import { StartAction } from './action_start';
import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
jest.mock('ui/new_platform');
-
jest.mock('../../../../../shared_imports');
describe('Transform: Transform List Actions ', () => {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx
index f6bb1c8b60667a..e2a22765dfb981 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx
@@ -16,7 +16,6 @@ import { StopAction } from './action_stop';
import transformListRow from '../../../../common/__mocks__/transform_list_row.json';
jest.mock('ui/new_platform');
-
jest.mock('../../../../../shared_imports');
describe('Transform: Transform List Actions ', () => {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx
index 12e1ba5528c43a..e8ac2fa057ad8e 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx
@@ -6,8 +6,6 @@
import { getActions } from './actions';
-jest.mock('ui/new_platform');
-
jest.mock('../../../../../shared_imports');
describe('Transform: Transform List Actions', () => {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx
index 42f04ed101ad6b..b4198ce3c7244d 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx
@@ -6,8 +6,6 @@
import { getColumns } from './columns';
-jest.mock('ui/new_platform');
-
jest.mock('../../../../../shared_imports');
describe('Transform: Job List Columns', () => {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.mocks.ts b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.mocks.ts
deleted file mode 100644
index 1d20965526115f..00000000000000
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.mocks.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { chromeServiceMock } from '../../../../../../../../../../src/core/public/mocks';
-
-jest.doMock('ui/new_platform', () => ({
- npStart: {
- core: {
- chrome: chromeServiceMock.createStartContract(),
- },
- },
-}));
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx
index e1a19ddd3c742e..5e0363d0a7a152 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx
@@ -7,11 +7,8 @@
import { shallow } from 'enzyme';
import React from 'react';
-import './transform_list.test.mocks';
import { TransformList } from './transform_list';
-jest.mock('ui/new_platform');
-
jest.mock('../../../../../shared_imports');
describe('Transform: Transform List ', () => {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts
index 8e505c7cccc022..812e636a3b338c 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts
@@ -6,12 +6,7 @@
import React, { useEffect } from 'react';
-import { timefilter } from 'ui/timefilter';
-
-import {
- DEFAULT_REFRESH_INTERVAL_MS,
- MINIMUM_REFRESH_INTERVAL_MS,
-} from '../../../../../../common/constants';
+import { DEFAULT_REFRESH_INTERVAL_MS } from '../../../../../../common/constants';
import { useRefreshTransformList } from '../../../../common';
@@ -20,62 +15,11 @@ export const useRefreshInterval = (
) => {
const { refresh } = useRefreshTransformList();
useEffect(() => {
- let transformRefreshInterval: null | number = null;
- const refreshIntervalSubscription = timefilter
- .getRefreshIntervalUpdate$()
- .subscribe(setAutoRefresh);
-
- timefilter.disableTimeRangeSelector();
- timefilter.enableAutoRefreshSelector();
-
- initAutoRefresh();
-
- function initAutoRefresh() {
- const { value } = timefilter.getRefreshInterval();
- if (value === 0) {
- // the auto refresher starts in an off state
- // so switch it on and set the interval to 30s
- timefilter.setRefreshInterval({
- pause: false,
- value: DEFAULT_REFRESH_INTERVAL_MS,
- });
- }
-
- setAutoRefresh();
- }
-
- function setAutoRefresh() {
- const { value, pause } = timefilter.getRefreshInterval();
- if (pause) {
- clearRefreshInterval();
- } else {
- setRefreshInterval(value);
- }
- refresh();
- }
-
- function setRefreshInterval(interval: number) {
- clearRefreshInterval();
- if (interval >= MINIMUM_REFRESH_INTERVAL_MS) {
- setBlockRefresh(false);
- const intervalId = window.setInterval(() => {
- refresh();
- }, interval);
- transformRefreshInterval = intervalId;
- }
- }
-
- function clearRefreshInterval() {
- setBlockRefresh(true);
- if (transformRefreshInterval !== null) {
- window.clearInterval(transformRefreshInterval);
- }
- }
+ const interval = setInterval(refresh, DEFAULT_REFRESH_INTERVAL_MS);
// useEffect cleanup
return () => {
- refreshIntervalSubscription.unsubscribe();
- clearRefreshInterval();
+ clearInterval(interval);
};
// custom comparison
// eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx
index f68670f0b38b2b..b1ca9e370c99e3 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx
@@ -9,11 +9,6 @@ import React from 'react';
import { TransformManagementSection } from './transform_management_section';
-jest.mock('ui/new_platform');
-jest.mock('ui/timefilter', () => {
- return {};
-});
-
jest.mock('../../../shared_imports');
describe('Transform: ', () => {
diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
index 6eb03c6537e0e1..1573d4c53c0cf4 100644
--- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
+++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.tsx
@@ -24,12 +24,12 @@ import {
} from '@elastic/eui';
import { APP_GET_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants';
+import { useDocumentationLinks } from '../../app_dependencies';
import { useRefreshTransformList, TransformListRow } from '../../common';
import { useGetTransforms } from '../../hooks';
import { RedirectToCreateTransform } from '../../common/navigation';
import { PrivilegesWrapper } from '../../lib/authorization';
import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation';
-import { documentationLinksService } from '../../services/documentation';
import { useRefreshInterval } from './components/transform_list/use_refresh_interval';
import { SearchSelection } from './components/search_selection';
@@ -37,6 +37,8 @@ import { TransformList } from './components/transform_list';
import { TransformStatsBar } from './components/transform_list/transforms_stats_bar';
export const TransformManagement: FC = () => {
+ const { esTransform } = useDocumentationLinks();
+
const [transformsLoading, setTransformsLoading] = useState(false);
const [isInitialized, setIsInitialized] = useState(false);
const [blockRefresh, setBlockRefresh] = useState(false);
@@ -98,7 +100,7 @@ export const TransformManagement: FC = () => {
> => {
- return _sendRequest(httpService.httpClient, config);
-};
-
-export const useRequest = (config: UseRequestConfig) => {
- return _useRequest(httpService.httpClient, config);
-};
diff --git a/x-pack/legacy/plugins/transform/public/app/services/http_service.ts b/x-pack/legacy/plugins/transform/public/app/services/http_service.ts
index 11ba5119b1394d..fa4c8d1ba78447 100644
--- a/x-pack/legacy/plugins/transform/public/app/services/http_service.ts
+++ b/x-pack/legacy/plugins/transform/public/app/services/http_service.ts
@@ -5,48 +5,47 @@
*/
// service for interacting with the server
-
-import chrome from 'ui/chrome';
-
-// @ts-ignore
-import { addSystemApiHeader } from 'ui/system_api';
-
import { Dictionary } from '../../../common/types/common';
-export function http(options: Dictionary) {
- return new Promise((resolve, reject) => {
- if (options && options.url) {
- let url = '';
- url = url + (options.url || '');
- const headers = addSystemApiHeader({
- 'Content-Type': 'application/json',
- 'kbn-version': chrome.getXsrfToken(),
- ...options.headers,
- });
-
- const allHeaders =
- options.headers === undefined ? headers : { ...options.headers, ...headers };
- const body = options.data === undefined ? null : JSON.stringify(options.data);
-
- const payload: Dictionary = {
- method: options.method || 'GET',
- headers: allHeaders,
- credentials: 'same-origin',
- };
-
- if (body !== null) {
- payload.body = body;
+export type Http = (options: Dictionary) => Promise;
+
+export function httpFactory(xsrfToken: string) {
+ return function http(options: Dictionary) {
+ return new Promise((resolve, reject) => {
+ if (options && options.url) {
+ let url = '';
+ url = url + (options.url || '');
+ const headers = {
+ 'kbn-system-request': true,
+ 'Content-Type': 'application/json',
+ 'kbn-version': xsrfToken,
+ ...options.headers,
+ };
+
+ const allHeaders =
+ options.headers === undefined ? headers : { ...options.headers, ...headers };
+ const body = options.data === undefined ? null : JSON.stringify(options.data);
+
+ const payload: Dictionary = {
+ method: options.method || 'GET',
+ headers: allHeaders,
+ credentials: 'same-origin',
+ };
+
+ if (body !== null) {
+ payload.body = body;
+ }
+
+ fetch(url, payload)
+ .then(resp => {
+ resp.json().then(resp.ok === true ? resolve : reject);
+ })
+ .catch(resp => {
+ reject(resp);
+ });
+ } else {
+ reject();
}
-
- fetch(url, payload)
- .then(resp => {
- resp.json().then(resp.ok === true ? resolve : reject);
- })
- .catch(resp => {
- reject(resp);
- });
- } else {
- reject();
- }
- });
+ });
+ };
}
diff --git a/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts b/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts
index 5a2f698b351544..aa8041a1cbe239 100644
--- a/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts
+++ b/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts
@@ -7,6 +7,10 @@
import { textService } from '../text';
import { linkToHome } from './links';
+import { ManagementAppMountParams } from '../../../../../../../../src/plugins/management/public';
+
+type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs'];
+
export enum BREADCRUMB_SECTION {
MANAGEMENT = 'management',
HOME = 'home',
@@ -24,17 +28,16 @@ type Breadcrumbs = {
};
class BreadcrumbService {
- private chrome: any;
private breadcrumbs: Breadcrumbs = {
management: [],
home: [],
cloneTransform: [],
createTransform: [],
};
+ private setBreadcrumbsHandler?: SetBreadcrumbs;
- public init(chrome: any, managementBreadcrumb: any): void {
- this.chrome = chrome;
- this.breadcrumbs.management = [managementBreadcrumb];
+ public setup(setBreadcrumbsHandler: SetBreadcrumbs): void {
+ this.setBreadcrumbsHandler = setBreadcrumbsHandler;
// Home and sections
this.breadcrumbs.home = [
@@ -59,12 +62,19 @@ class BreadcrumbService {
}
public setBreadcrumbs(type: BREADCRUMB_SECTION): void {
+ if (!this.setBreadcrumbsHandler) {
+ throw new Error(`BreadcrumbService#setup() must be called first!`);
+ }
+
const newBreadcrumbs = this.breadcrumbs[type]
? [...this.breadcrumbs[type]]
: [...this.breadcrumbs.home];
// Pop off last breadcrumb
- const lastBreadcrumb = newBreadcrumbs.pop() as BreadcrumbItem;
+ const lastBreadcrumb = newBreadcrumbs.pop() as {
+ text: string;
+ href?: string;
+ };
// Put last breadcrumb back without href
newBreadcrumbs.push({
@@ -72,7 +82,7 @@ class BreadcrumbService {
href: undefined,
});
- this.chrome.setBreadcrumbs(newBreadcrumbs);
+ this.setBreadcrumbsHandler(newBreadcrumbs);
}
}
diff --git a/x-pack/legacy/plugins/transform/public/plugin.ts b/x-pack/legacy/plugins/transform/public/plugin.ts
index d55695f891bb7f..23fad00fb0786f 100644
--- a/x-pack/legacy/plugins/transform/public/plugin.ts
+++ b/x-pack/legacy/plugins/transform/public/plugin.ts
@@ -3,121 +3,89 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { unmountComponentAtNode } from 'react-dom';
import { i18n } from '@kbn/i18n';
-import { PLUGIN } from '../common/constants';
-import { CLIENT_BASE_PATH } from './app/constants';
-import { renderReact } from './app/app';
-import { Core, Plugins } from './shim';
+import { renderApp } from './app/app';
+import { ShimCore, ShimPlugins } from './shim';
-import { breadcrumbService, docTitleService } from './app/services/navigation';
-import { documentationLinksService } from './app/services/documentation';
-import { httpService } from './app/services/http';
+import { breadcrumbService } from './app/services/navigation';
+import { docTitleService } from './app/services/navigation';
import { textService } from './app/services/text';
import { uiMetricService } from './app/services/ui_metric';
import { createSavedSearchesLoader } from '../../../../../src/plugins/discover/public';
-const REACT_ROOT_ID = 'transformReactRoot';
-const KBN_MANAGEMENT_SECTION = 'elasticsearch/transform';
-
-const template = ``;
-
export class Plugin {
- public start(core: Core, plugins: Plugins): void {
+ public start(core: ShimCore, plugins: ShimPlugins): void {
const {
http,
- routing,
- legacyHttp,
chrome,
documentation,
+ docLinks,
docTitle,
+ injectedMetadata,
+ notifications,
uiSettings,
savedObjects,
overlays,
} = core;
- const { data, management, savedSearches: coreSavedSearches, uiMetric } = plugins;
+ const { data, management, savedSearches: coreSavedSearches, uiMetric, xsrfToken } = plugins;
// AppCore/AppPlugins to be passed on as React context
const appDependencies = {
- core: { chrome, http, i18n: core.i18n, uiSettings, savedObjects, overlays },
+ core: {
+ chrome,
+ documentation,
+ docLinks,
+ http,
+ i18n: core.i18n,
+ injectedMetadata,
+ notifications,
+ uiSettings,
+ savedObjects,
+ overlays,
+ },
plugins: {
data,
- management: { sections: management.sections },
+ management,
savedSearches: coreSavedSearches,
+ xsrfToken,
},
};
// Register management section
const esSection = management.sections.getSection('elasticsearch');
- esSection.register(PLUGIN.ID, {
- visible: true,
- display: i18n.translate('xpack.transform.appName', {
- defaultMessage: 'Transforms',
- }),
- order: 3,
- url: `#${CLIENT_BASE_PATH}`,
- });
+ if (esSection !== undefined) {
+ esSection.registerApp({
+ id: 'transform',
+ title: i18n.translate('xpack.transform.appTitle', {
+ defaultMessage: 'Transforms',
+ }),
+ order: 3,
+ mount(params) {
+ const savedSearches = createSavedSearchesLoader({
+ savedObjectsClient: core.savedObjects.client,
+ indexPatterns: plugins.data.indexPatterns,
+ chrome: core.chrome,
+ overlays: core.overlays,
+ });
+ coreSavedSearches.setClient(savedSearches);
+
+ breadcrumbService.setup(params.setBreadcrumbs);
+ params.setBreadcrumbs([
+ {
+ text: i18n.translate('xpack.transform.breadcrumbsTitle', {
+ defaultMessage: 'Transforms',
+ }),
+ },
+ ]);
+
+ return renderApp(params.element, appDependencies);
+ },
+ });
+ }
// Initialize services
textService.init();
- breadcrumbService.init(chrome, management.constants.BREADCRUMB);
uiMetricService.init(uiMetric.createUiStatsReporter);
- documentationLinksService.init(documentation.esDocBasePath);
docTitleService.init(docTitle.change);
-
- const unmountReactApp = (): void => {
- const elem = document.getElementById(REACT_ROOT_ID);
- if (elem) {
- unmountComponentAtNode(elem);
- }
- };
-
- // Register react root
- routing.registerAngularRoute(`${CLIENT_BASE_PATH}/:section?/:subsection?/:view?/:id?`, {
- template,
- controllerAs: 'transformController',
- controller: ($scope: any, $route: any, $http: ng.IHttpService) => {
- const savedSearches = createSavedSearchesLoader({
- savedObjectsClient: core.savedObjects.client,
- indexPatterns: plugins.data.indexPatterns,
- chrome: core.chrome,
- overlays: core.overlays,
- });
- // NOTE: We depend upon Angular's $http service because it's decorated with interceptors,
- // e.g. to check license status per request.
- legacyHttp.setClient($http);
- httpService.init(legacyHttp.getClient());
- coreSavedSearches.setClient(savedSearches);
-
- // Angular Lifecycle
- const appRoute = $route.current;
- const stopListeningForLocationChange = $scope.$on('$locationChangeSuccess', () => {
- const currentRoute = $route.current;
- const isNavigationInApp = currentRoute.$$route.template === appRoute.$$route.template;
-
- // When we navigate within Transform, prevent Angular from re-matching the route and rebuild the app
- if (isNavigationInApp) {
- $route.current = appRoute;
- } else {
- // Any clean up when user leaves Transform
- }
-
- $scope.$on('$destroy', () => {
- if (stopListeningForLocationChange) {
- stopListeningForLocationChange();
- }
- unmountReactApp();
- });
- });
-
- $scope.$$postDigest(() => {
- unmountReactApp();
- const elem = document.getElementById(REACT_ROOT_ID);
- if (elem) {
- renderReact(elem, appDependencies);
- }
- });
- },
- });
}
}
diff --git a/x-pack/legacy/plugins/transform/public/shared_imports.ts b/x-pack/legacy/plugins/transform/public/shared_imports.ts
index 248eb00c67dff6..b077cd8836c4b5 100644
--- a/x-pack/legacy/plugins/transform/public/shared_imports.ts
+++ b/x-pack/legacy/plugins/transform/public/shared_imports.ts
@@ -16,7 +16,7 @@ export {
UseRequestConfig,
sendRequest,
useRequest,
-} from '../../../../../src/plugins/es_ui_shared/public/request';
+} from '../../../../../src/plugins/es_ui_shared/public/request/np_ready_request';
export {
CronEditor,
diff --git a/x-pack/legacy/plugins/transform/public/shim.ts b/x-pack/legacy/plugins/transform/public/shim.ts
index 38bb072ff9eb7c..95f54605377a86 100644
--- a/x-pack/legacy/plugins/transform/public/shim.ts
+++ b/x-pack/legacy/plugins/transform/public/shim.ts
@@ -6,77 +6,69 @@
import { npStart } from 'ui/new_platform';
-import { management, MANAGEMENT_BREADCRUMB } from 'ui/management';
-import routes from 'ui/routes';
+import chrome from 'ui/chrome';
import { docTitle } from 'ui/doc_title/doc_title';
-import { CoreStart } from 'kibana/public';
// @ts-ignore: allow traversal to fail on x-pack build
import { createUiStatsReporter } from '../../../../../src/legacy/core_plugins/ui_metric/public';
import { SavedSearchLoader } from '../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/types';
-import { DataPublicPluginStart } from '../../../../../src/plugins/data/public';
-export type npCore = typeof npStart.core;
+import { TRANSFORM_DOC_PATHS } from './app/constants';
+
+export type NpCore = typeof npStart.core;
+export type NpPlugins = typeof npStart.plugins;
// AppCore/AppPlugins is the set of core features/plugins
// we pass on via context/hooks to the app and its components.
export type AppCore = Pick<
- CoreStart,
- 'chrome' | 'http' | 'i18n' | 'savedObjects' | 'uiSettings' | 'overlays'
+ ShimCore,
+ | 'chrome'
+ | 'documentation'
+ | 'docLinks'
+ | 'http'
+ | 'i18n'
+ | 'injectedMetadata'
+ | 'savedObjects'
+ | 'uiSettings'
+ | 'overlays'
+ | 'notifications'
>;
-
-export interface AppPlugins {
- data: DataPublicPluginStart;
- management: {
- sections: typeof management;
- };
- savedSearches: {
- getClient(): any;
- setClient(client: any): void;
- };
-}
+export type AppPlugins = Pick;
export interface AppDependencies {
core: AppCore;
plugins: AppPlugins;
}
-export interface Core extends npCore {
- legacyHttp: {
- getClient(): any;
- setClient(client: any): void;
- };
- routing: {
- registerAngularRoute(path: string, config: object): void;
- };
- documentation: {
- esDocBasePath: string;
- esPluginDocBasePath: string;
- esStackOverviewDocBasePath: string;
- esMLDocBasePath: string;
- };
+export interface ShimCore extends NpCore {
+ documentation: Record<
+ | 'esDocBasePath'
+ | 'esIndicesCreateIndex'
+ | 'esPluginDocBasePath'
+ | 'esQueryDsl'
+ | 'esStackOverviewDocBasePath'
+ | 'esTransform'
+ | 'esTransformPivot'
+ | 'mlDocBasePath',
+ string
+ >;
docTitle: {
change: typeof docTitle.change;
};
}
-export interface Plugins extends AppPlugins {
- management: {
- sections: typeof management;
- constants: {
- BREADCRUMB: typeof MANAGEMENT_BREADCRUMB;
- };
- };
+export interface ShimPlugins extends NpPlugins {
uiMetric: {
createUiStatsReporter: typeof createUiStatsReporter;
};
- data: DataPublicPluginStart;
+ savedSearches: {
+ getClient(): any;
+ setClient(client: any): void;
+ };
+ xsrfToken: string;
}
-export function createPublicShim(): { core: Core; plugins: Plugins } {
- // This is an Angular service, which is why we use this provider pattern
- // to access it within our React app.
- let httpClient: ng.IHttpService;
+export function createPublicShim(): { core: ShimCore; plugins: ShimPlugins } {
// This is an Angular service, which is why we use this provider pattern
// to access it within our React app.
let savedSearches: SavedSearchLoader;
@@ -86,35 +78,22 @@ export function createPublicShim(): { core: Core; plugins: Plugins } {
return {
core: {
...npStart.core,
- routing: {
- registerAngularRoute: (path: string, config: object): void => {
- routes.when(path, config);
- },
- },
- legacyHttp: {
- setClient: (client: any): void => {
- httpClient = client;
- },
- getClient: (): any => httpClient,
- },
documentation: {
esDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`,
+ esIndicesCreateIndex: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/indices-create-index.html#indices-create-index`,
esPluginDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`,
+ esQueryDsl: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/query-dsl.html`,
esStackOverviewDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack-overview/${DOC_LINK_VERSION}/`,
- esMLDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/`,
+ esTransform: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/${TRANSFORM_DOC_PATHS.transforms}`,
+ esTransformPivot: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/put-transform.html#put-transform-request-body`,
+ mlDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/`,
},
docTitle: {
change: docTitle.change,
},
},
plugins: {
- data: npStart.plugins.data,
- management: {
- sections: management,
- constants: {
- BREADCRUMB: MANAGEMENT_BREADCRUMB,
- },
- },
+ ...npStart.plugins,
savedSearches: {
setClient: (client: any): void => {
savedSearches = client;
@@ -124,6 +103,7 @@ export function createPublicShim(): { core: Core; plugins: Plugins } {
uiMetric: {
createUiStatsReporter,
},
+ xsrfToken: chrome.getXsrfToken(),
},
};
}
diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts
index b1e4906317f815..a7476bf564a16b 100644
--- a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts
+++ b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts
@@ -62,8 +62,7 @@ export const getDynamicIndexPattern = async ({
cache.set(CACHE_KEY, undefined);
const notExists = e.output?.statusCode === 404;
if (notExists) {
- // eslint-disable-next-line no-console
- console.error(
+ context.logger.error(
`Could not get dynamic index pattern because indices "${indexPatternTitle}" don't exist`
);
return;
diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
index af2d2a13eaa2fa..8cfb7e7edb4c69 100644
--- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
+++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts
@@ -4,17 +4,19 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { IClusterClient } from 'src/core/server';
+import { IClusterClient, Logger } from 'src/core/server';
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
import { APMConfig } from '../../..';
import { getApmIndicesConfig } from '../apm_indices/get_apm_indices';
export async function createApmAgentConfigurationIndex({
esClient,
- config
+ config,
+ logger
}: {
esClient: IClusterClient;
config: APMConfig;
+ logger: Logger;
}) {
try {
const index = getApmIndicesConfig(config).apmAgentConfigurationIndex;
@@ -32,8 +34,7 @@ export async function createApmAgentConfigurationIndex({
);
}
} catch (e) {
- // eslint-disable-next-line no-console
- console.error('Could not create APM Agent configuration:', e.message);
+ logger.error(`Could not create APM Agent configuration: ${e.message}`);
}
}
diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts
index adc80cb43620bf..773f0d4e6fac56 100644
--- a/x-pack/plugins/apm/server/plugin.ts
+++ b/x-pack/plugins/apm/server/plugin.ts
@@ -5,12 +5,12 @@
*/
import { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server';
import { Observable, combineLatest, AsyncSubject } from 'rxjs';
-import { map } from 'rxjs/operators';
+import { map, take } from 'rxjs/operators';
import { Server } from 'hapi';
import { once } from 'lodash';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { APMOSSPluginSetup } from '../../../../src/plugins/apm_oss/server';
import { makeApmUsageCollector } from './lib/apm_telemetry';
-import { Plugin as APMOSSPlugin } from '../../../../src/plugins/apm_oss/server';
import { createApmAgentConfigurationIndex } from './lib/settings/agent_configuration/create_agent_config_index';
import { createApmApi } from './routes/create_apm_api';
import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices';
@@ -33,26 +33,23 @@ export interface APMPluginContract {
export class APMPlugin implements Plugin {
legacySetup$: AsyncSubject;
- currentConfig: APMConfig;
constructor(private readonly initContext: PluginInitializerContext) {
this.initContext = initContext;
this.legacySetup$ = new AsyncSubject();
- this.currentConfig = {} as APMConfig;
}
public async setup(
core: CoreSetup,
plugins: {
- apm_oss: APMOSSPlugin extends Plugin ? TSetup : never;
+ apm_oss: APMOSSPluginSetup;
home: HomeServerPluginSetup;
licensing: LicensingPluginSetup;
cloud?: CloudSetup;
usageCollection?: UsageCollectionSetup;
}
) {
- const config$ = this.initContext.config.create();
const logger = this.initContext.logger.get('apm');
-
+ const config$ = this.initContext.config.create();
const mergedConfig$ = combineLatest(plugins.apm_oss.config$, config$).pipe(
map(([apmOssConfig, apmConfig]) => mergeConfigs(apmOssConfig, apmConfig))
);
@@ -61,28 +58,26 @@ export class APMPlugin implements Plugin {
createApmApi().init(core, { config$: mergedConfig$, logger, __LEGACY });
});
- await new Promise(resolve => {
- mergedConfig$.subscribe(async config => {
- this.currentConfig = config;
- await createApmAgentConfigurationIndex({
- esClient: core.elasticsearch.dataClient,
- config
- });
- resolve();
- });
+ const currentConfig = await mergedConfig$.pipe(take(1)).toPromise();
+
+ // create agent configuration index without blocking setup lifecycle
+ createApmAgentConfigurationIndex({
+ esClient: core.elasticsearch.dataClient,
+ config: currentConfig,
+ logger
});
plugins.home.tutorials.registerTutorial(
tutorialProvider({
- isEnabled: this.currentConfig['xpack.apm.ui.enabled'],
- indexPatternTitle: this.currentConfig['apm_oss.indexPattern'],
+ isEnabled: currentConfig['xpack.apm.ui.enabled'],
+ indexPatternTitle: currentConfig['apm_oss.indexPattern'],
cloud: plugins.cloud,
indices: {
- errorIndices: this.currentConfig['apm_oss.errorIndices'],
- metricsIndices: this.currentConfig['apm_oss.metricsIndices'],
- onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'],
- sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'],
- transactionIndices: this.currentConfig['apm_oss.transactionIndices']
+ errorIndices: currentConfig['apm_oss.errorIndices'],
+ metricsIndices: currentConfig['apm_oss.metricsIndices'],
+ onboardingIndices: currentConfig['apm_oss.onboardingIndices'],
+ sourcemapIndices: currentConfig['apm_oss.sourcemapIndices'],
+ transactionIndices: currentConfig['apm_oss.transactionIndices']
}
})
);
@@ -108,7 +103,7 @@ export class APMPlugin implements Plugin {
getApmIndices: async () =>
getApmIndices({
savedObjectsClient: await getInternalSavedObjectsClient(core),
- config: this.currentConfig
+ config: currentConfig
})
};
}
diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts
index 94ce9627b9ac64..17a25184826378 100644
--- a/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts
+++ b/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts
@@ -3,33 +3,22 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { Authentication } from '../../../../../security/server';
+import { AuthenticatedUser } from '../../../../../security/server';
+import { securityMock } from '../../../../../security/server/mocks';
-const getCurrentUser = jest.fn().mockReturnValue({
- username: 'awesome',
- full_name: 'Awesome D00d',
-});
-const getCurrentUserThrow = jest.fn().mockImplementation(() => {
- throw new Error('Bad User - the user is not authenticated');
-});
+function createAuthenticationMock({
+ currentUser,
+}: { currentUser?: AuthenticatedUser | null } = {}) {
+ const { authc } = securityMock.createSetup();
+ authc.getCurrentUser.mockReturnValue(
+ currentUser !== undefined
+ ? currentUser
+ : ({ username: 'awesome', full_name: 'Awesome D00d' } as AuthenticatedUser)
+ );
+ return authc;
+}
export const authenticationMock = {
- create: (): jest.Mocked => ({
- login: jest.fn(),
- createAPIKey: jest.fn(),
- getCurrentUser,
- invalidateAPIKey: jest.fn(),
- isAuthenticated: jest.fn(),
- logout: jest.fn(),
- getSessionInfo: jest.fn(),
- }),
- createInvalid: (): jest.Mocked => ({
- login: jest.fn(),
- createAPIKey: jest.fn(),
- getCurrentUser: getCurrentUserThrow,
- invalidateAPIKey: jest.fn(),
- isAuthenticated: jest.fn(),
- logout: jest.fn(),
- getSessionInfo: jest.fn(),
- }),
+ create: () => createAuthenticationMock(),
+ createInvalid: () => createAuthenticationMock({ currentUser: null }),
};
diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts
index d6d4bd606676c8..e6416e268e30bf 100644
--- a/x-pack/plugins/case/server/services/index.ts
+++ b/x-pack/plugins/case/server/services/index.ts
@@ -149,14 +149,8 @@ export class CaseService {
}
},
getUser: async ({ request, response }: GetUserArgs) => {
- let user;
- try {
- this.log.debug(`Attempting to authenticate a user`);
- user = await authentication!.getCurrentUser(request);
- } catch (error) {
- this.log.debug(`Error on GET user: ${error}`);
- throw error;
- }
+ this.log.debug(`Attempting to authenticate a user`);
+ const user = authentication!.getCurrentUser(request);
if (!user) {
this.log.debug(`Error on GET user: Bad User`);
throw new Error('Bad User - the user is not authenticated');
diff --git a/x-pack/plugins/console_extensions/server/spec/generated/monitoring.bulk.json b/x-pack/plugins/console_extensions/server/spec/generated/monitoring.bulk.json
index 2b27950e7b0974..26a9078f73ce82 100644
--- a/x-pack/plugins/console_extensions/server/spec/generated/monitoring.bulk.json
+++ b/x-pack/plugins/console_extensions/server/spec/generated/monitoring.bulk.json
@@ -12,6 +12,6 @@
"patterns": [
"_monitoring/bulk"
],
- "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/es-monitoring.html"
+ "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/monitor-elasticsearch-cluster.html"
}
}
diff --git a/x-pack/plugins/features/server/index.ts b/x-pack/plugins/features/server/index.ts
index 2b4f85aa04f046..48ef97a494f7ea 100644
--- a/x-pack/plugins/features/server/index.ts
+++ b/x-pack/plugins/features/server/index.ts
@@ -14,7 +14,7 @@ import { Plugin } from './plugin';
export { uiCapabilitiesRegex } from './feature_schema';
export { Feature, FeatureWithAllOrReadPrivileges, FeatureKibanaPrivileges } from '../common';
-export { PluginSetupContract } from './plugin';
+export { PluginSetupContract, PluginStartContract } from './plugin';
export const plugin = (initializerContext: PluginInitializerContext) =>
new Plugin(initializerContext);
diff --git a/x-pack/plugins/features/server/mocks.ts b/x-pack/plugins/features/server/mocks.ts
new file mode 100644
index 00000000000000..ebaa5f1a504ca9
--- /dev/null
+++ b/x-pack/plugins/features/server/mocks.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { PluginSetupContract, PluginStartContract } from './plugin';
+
+const createSetup = (): jest.Mocked => {
+ return {
+ getFeatures: jest.fn(),
+ getFeaturesUICapabilities: jest.fn(),
+ registerFeature: jest.fn(),
+ registerLegacyAPI: jest.fn(),
+ };
+};
+
+const createStart = (): jest.Mocked => {
+ return {
+ getFeatures: jest.fn(),
+ };
+};
+
+export const featuresPluginMock = {
+ createSetup,
+ createStart,
+};
diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts
index 96a8e68f8326d5..e77fa218c06815 100644
--- a/x-pack/plugins/features/server/plugin.ts
+++ b/x-pack/plugins/features/server/plugin.ts
@@ -30,6 +30,10 @@ export interface PluginSetupContract {
registerLegacyAPI: (legacyAPI: LegacyAPI) => void;
}
+export interface PluginStartContract {
+ getFeatures(): Feature[];
+}
+
/**
* Describes a set of APIs that are available in the legacy platform only and required by this plugin
* to function properly.
@@ -45,6 +49,8 @@ export interface LegacyAPI {
export class Plugin {
private readonly logger: Logger;
+ private readonly featureRegistry: FeatureRegistry = new FeatureRegistry();
+
private legacyAPI?: LegacyAPI;
private readonly getLegacyAPI = () => {
if (!this.legacyAPI) {
@@ -61,18 +67,16 @@ export class Plugin {
core: CoreSetup,
{ timelion }: { timelion?: TimelionSetupContract }
): Promise> {
- const featureRegistry = new FeatureRegistry();
-
defineRoutes({
router: core.http.createRouter(),
- featureRegistry,
+ featureRegistry: this.featureRegistry,
getLegacyAPI: this.getLegacyAPI,
});
return deepFreeze({
- registerFeature: featureRegistry.register.bind(featureRegistry),
- getFeatures: featureRegistry.getAll.bind(featureRegistry),
- getFeaturesUICapabilities: () => uiCapabilitiesForFeatures(featureRegistry.getAll()),
+ registerFeature: this.featureRegistry.register.bind(this.featureRegistry),
+ getFeatures: this.featureRegistry.getAll.bind(this.featureRegistry),
+ getFeaturesUICapabilities: () => uiCapabilitiesForFeatures(this.featureRegistry.getAll()),
registerLegacyAPI: (legacyAPI: LegacyAPI) => {
this.legacyAPI = legacyAPI;
@@ -82,14 +86,17 @@ export class Plugin {
savedObjectTypes: this.legacyAPI.savedObjectTypes,
includeTimelion: timelion !== undefined && timelion.uiEnabled,
})) {
- featureRegistry.register(feature);
+ this.featureRegistry.register(feature);
}
},
});
}
- public start() {
+ public start(): RecursiveReadonly {
this.logger.debug('Starting plugin');
+ return deepFreeze({
+ getFeatures: this.featureRegistry.getAll.bind(this.featureRegistry),
+ });
}
public stop() {
diff --git a/x-pack/plugins/infra/kibana.json b/x-pack/plugins/infra/kibana.json
index 7ba81127c1e676..f12bbd6cf77235 100644
--- a/x-pack/plugins/infra/kibana.json
+++ b/x-pack/plugins/infra/kibana.json
@@ -2,7 +2,17 @@
"id": "infra",
"version": "8.0.0",
"kibanaVersion": "kibana",
- "requiredPlugins": ["features", "apm", "usageCollection", "spaces", "home", "data", "data_enhanced", "metrics"],
+ "requiredPlugins": [
+ "features",
+ "apm",
+ "usageCollection",
+ "spaces",
+ "home",
+ "data",
+ "data_enhanced",
+ "metrics",
+ "alerting"
+ ],
"server": true,
"ui": true,
"configPath": ["xpack", "infra"]
diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
index f8177365c9bdd8..c7ac577dd8a81f 100644
--- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
+++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts
@@ -13,6 +13,7 @@ import { SpacesPluginSetup } from '../../../../../../plugins/spaces/server';
import { VisTypeTimeseriesSetup } from '../../../../../../../src/plugins/vis_type_timeseries/server';
import { APMPluginContract } from '../../../../../../plugins/apm/server';
import { HomeServerPluginSetup } from '../../../../../../../src/plugins/home/server';
+import { PluginSetupContract as AlertingPluginContract } from '../../../../../../plugins/alerting/server';
// NP_TODO: Compose real types from plugins we depend on, no "any"
export interface InfraServerPluginDeps {
@@ -25,6 +26,7 @@ export interface InfraServerPluginDeps {
};
features: FeaturesPluginSetup;
apm: APMPluginContract;
+ alerting: AlertingPluginContract;
}
export interface CallWithRequestParams extends GenericParams {
diff --git a/x-pack/legacy/plugins/transform/public/app/services/http/index.ts b/x-pack/plugins/infra/server/lib/alerting/index.ts
similarity index 79%
rename from x-pack/legacy/plugins/transform/public/app/services/http/index.ts
rename to x-pack/plugins/infra/server/lib/alerting/index.ts
index a129998e48e1ff..90287d8f219f95 100644
--- a/x-pack/legacy/plugins/transform/public/app/services/http/index.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/index.ts
@@ -3,4 +3,5 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-export { httpService } from './http';
+
+export { registerAlertTypes } from './register_alert_types';
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
new file mode 100644
index 00000000000000..9bc54c1a49c8f7
--- /dev/null
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts
@@ -0,0 +1,132 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import uuid from 'uuid';
+import { i18n } from '@kbn/i18n';
+import { schema } from '@kbn/config-schema';
+import {
+ MetricThresholdAlertTypeParams,
+ Comparator,
+ AlertStates,
+ METRIC_THRESHOLD_ALERT_TYPE_ID,
+} from './types';
+import { AlertServices, PluginSetupContract } from '../../../../../alerting/server';
+
+const FIRED_ACTIONS = {
+ id: 'metrics.threshold.fired',
+ name: i18n.translate('xpack.infra.metrics.alerting.threshold.fired', {
+ defaultMessage: 'Fired',
+ }),
+};
+
+async function getMetric(
+ { callCluster }: AlertServices,
+ { metric, aggType, timeUnit, timeSize, indexPattern }: MetricThresholdAlertTypeParams
+) {
+ const interval = `${timeSize}${timeUnit}`;
+ const searchBody = {
+ query: {
+ bool: {
+ filter: [
+ {
+ range: {
+ '@timestamp': {
+ gte: `now-${interval}`,
+ },
+ },
+ exists: {
+ field: metric,
+ },
+ },
+ ],
+ },
+ },
+ size: 0,
+ aggs: {
+ aggregatedIntervals: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: interval,
+ },
+ aggregations: {
+ aggregatedValue: {
+ [aggType]: {
+ field: metric,
+ },
+ },
+ },
+ },
+ },
+ };
+
+ const result = await callCluster('search', {
+ body: searchBody,
+ index: indexPattern,
+ });
+
+ const { buckets } = result.aggregations.aggregatedIntervals;
+ const { value } = buckets[buckets.length - 1].aggregatedValue;
+ return value;
+}
+
+const comparatorMap = {
+ [Comparator.BETWEEN]: (value: number, [a, b]: number[]) =>
+ value >= Math.min(a, b) && value <= Math.max(a, b),
+ // `threshold` is always an array of numbers in case the BETWEEN comparator is
+ // used; all other compartors will just destructure the first value in the array
+ [Comparator.GT]: (a: number, [b]: number[]) => a > b,
+ [Comparator.LT]: (a: number, [b]: number[]) => a < b,
+ [Comparator.GT_OR_EQ]: (a: number, [b]: number[]) => a >= b,
+ [Comparator.LT_OR_EQ]: (a: number, [b]: number[]) => a <= b,
+};
+
+export async function registerMetricThresholdAlertType(alertingPlugin: PluginSetupContract) {
+ if (!alertingPlugin) {
+ throw new Error(
+ 'Cannot register metric threshold alert type. Both the actions and alerting plugins need to be enabled.'
+ );
+ }
+ const alertUUID = uuid.v4();
+
+ alertingPlugin.registerType({
+ id: METRIC_THRESHOLD_ALERT_TYPE_ID,
+ name: 'Metric Alert - Threshold',
+ validate: {
+ params: schema.object({
+ threshold: schema.arrayOf(schema.number()),
+ comparator: schema.string(),
+ aggType: schema.string(),
+ metric: schema.string(),
+ timeUnit: schema.string(),
+ timeSize: schema.number(),
+ indexPattern: schema.string(),
+ }),
+ },
+ defaultActionGroupId: FIRED_ACTIONS.id,
+ actionGroups: [FIRED_ACTIONS],
+ async executor({ services, params }) {
+ const { threshold, comparator } = params as MetricThresholdAlertTypeParams;
+ const alertInstance = services.alertInstanceFactory(alertUUID);
+ const currentValue = await getMetric(services, params as MetricThresholdAlertTypeParams);
+ if (typeof currentValue === 'undefined')
+ throw new Error('Could not get current value of metric');
+
+ const comparisonFunction = comparatorMap[comparator];
+
+ const isValueInAlertState = comparisonFunction(currentValue, threshold);
+
+ if (isValueInAlertState) {
+ alertInstance.scheduleActions(FIRED_ACTIONS.id, {
+ value: currentValue,
+ });
+ }
+
+ // Future use: ability to fetch display current alert state
+ alertInstance.replaceState({
+ alertState: isValueInAlertState ? AlertStates.ALERT : AlertStates.OK,
+ });
+ },
+ });
+}
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts
new file mode 100644
index 00000000000000..2618469ff693c7
--- /dev/null
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { MetricsExplorerAggregation } from '../../../../common/http_api/metrics_explorer';
+
+export const METRIC_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.threshold';
+
+export enum Comparator {
+ GT = '>',
+ LT = '<',
+ GT_OR_EQ = '>=',
+ LT_OR_EQ = '<=',
+ BETWEEN = 'between',
+}
+
+export enum AlertStates {
+ OK,
+ ALERT,
+}
+
+export type TimeUnit = 's' | 'm' | 'h' | 'd';
+
+export interface MetricThresholdAlertTypeParams {
+ aggType: MetricsExplorerAggregation;
+ metric: string;
+ timeSize: number;
+ timeUnit: TimeUnit;
+ indexPattern: string;
+ threshold: number[];
+ comparator: Comparator;
+}
diff --git a/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts
new file mode 100644
index 00000000000000..6ec6f31256b787
--- /dev/null
+++ b/x-pack/plugins/infra/server/lib/alerting/register_alert_types.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { PluginSetupContract } from '../../../../alerting/server';
+import { registerMetricThresholdAlertType } from './metric_threshold/register_metric_threshold_alert_type';
+
+const registerAlertTypes = (alertingPlugin: PluginSetupContract) => {
+ if (alertingPlugin) {
+ const registerFns = [registerMetricThresholdAlertType];
+
+ registerFns.forEach(fn => {
+ fn(alertingPlugin);
+ });
+ }
+};
+
+export { registerAlertTypes };
diff --git a/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts b/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts
index 6f036475a1e1cf..cf2b1e59b2a225 100644
--- a/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts
+++ b/x-pack/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts
@@ -21,7 +21,7 @@ export const createTimeRangeWithInterval = async (
): Promise => {
const aggregations = getMetricsAggregations(options);
const modules = await aggregationsToModules(framework, requestContext, aggregations, options);
- const interval =
+ const interval = Math.max(
(await calculateMetricInterval(
framework,
requestContext,
@@ -32,7 +32,9 @@ export const createTimeRangeWithInterval = async (
},
modules,
options.nodeType
- )) || 60000;
+ )) || 60,
+ 60
+ );
return {
interval: `${interval}s`,
from: options.timerange.to - interval * 5000, // We need at least 5 buckets worth of data
diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts
index bcdbccf6f22945..64fc496f3597e1 100644
--- a/x-pack/plugins/infra/server/plugin.ts
+++ b/x-pack/plugins/infra/server/plugin.ts
@@ -27,6 +27,7 @@ import { InfraServerPluginDeps } from './lib/adapters/framework';
import { METRICS_FEATURE, LOGS_FEATURE } from './features';
import { UsageCollector } from './usage/usage_collector';
import { InfraStaticSourceConfiguration } from './lib/sources/types';
+import { registerAlertTypes } from './lib/alerting';
export const config = {
schema: schema.object({
@@ -146,6 +147,7 @@ export class InfraServerPlugin {
]);
initInfraServer(this.libs);
+ registerAlertTypes(plugins.alerting);
// Telemetry
UsageCollector.registerUsageCollector(plugins.usageCollection);
diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json
new file mode 100644
index 00000000000000..e944af6821c0b6
--- /dev/null
+++ b/x-pack/plugins/ml/kibana.json
@@ -0,0 +1,9 @@
+{
+ "id": "ml",
+ "version": "0.0.1",
+ "kibanaVersion": "kibana",
+ "configPath": ["ml"],
+ "requiredPlugins": ["cloud", "features", "home", "licensing", "security", "spaces", "usageCollection"],
+ "server": true,
+ "ui": false
+}
diff --git a/x-pack/legacy/plugins/ml/server/client/__tests__/elasticsearch_ml.js b/x-pack/plugins/ml/server/client/__tests__/elasticsearch_ml.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/client/__tests__/elasticsearch_ml.js
rename to x-pack/plugins/ml/server/client/__tests__/elasticsearch_ml.js
diff --git a/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js b/x-pack/plugins/ml/server/client/elasticsearch_ml.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js
rename to x-pack/plugins/ml/server/client/elasticsearch_ml.js
diff --git a/x-pack/legacy/plugins/ml/server/client/error_wrapper.ts b/x-pack/plugins/ml/server/client/error_wrapper.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/client/error_wrapper.ts
rename to x-pack/plugins/ml/server/client/error_wrapper.ts
diff --git a/x-pack/legacy/plugins/ml/server/client/errors.js b/x-pack/plugins/ml/server/client/errors.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/client/errors.js
rename to x-pack/plugins/ml/server/client/errors.js
diff --git a/x-pack/legacy/plugins/ml/server/client/log.ts b/x-pack/plugins/ml/server/client/log.ts
similarity index 94%
rename from x-pack/legacy/plugins/ml/server/client/log.ts
rename to x-pack/plugins/ml/server/client/log.ts
index ae82383ead6050..8ee5882f6c2c16 100644
--- a/x-pack/legacy/plugins/ml/server/client/log.ts
+++ b/x-pack/plugins/ml/server/client/log.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Logger } from '../../../../../../src/core/server';
+import { Logger } from '../../../../../src/core/server';
export interface LogInitialization {
log: Logger;
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/index.ts b/x-pack/plugins/ml/server/index.ts
similarity index 57%
rename from x-pack/legacy/plugins/ml/server/new_platform/index.ts
rename to x-pack/plugins/ml/server/index.ts
index b03f2dac613b03..55e87ed6f0c6a3 100644
--- a/x-pack/legacy/plugins/ml/server/new_platform/index.ts
+++ b/x-pack/plugins/ml/server/index.ts
@@ -4,8 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Plugin, MlInitializerContext } from './plugin';
+import { PluginInitializerContext } from 'kibana/server';
+import { MlServerPlugin } from './plugin';
-export function plugin(initializerContext: MlInitializerContext) {
- return new Plugin(initializerContext);
-}
+export const plugin = (ctx: PluginInitializerContext) => new MlServerPlugin(ctx);
diff --git a/x-pack/legacy/plugins/ml/server/lib/__tests__/query_utils.js b/x-pack/plugins/ml/server/lib/__tests__/query_utils.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/__tests__/query_utils.js
rename to x-pack/plugins/ml/server/lib/__tests__/query_utils.js
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_annotations/index.js b/x-pack/plugins/ml/server/lib/check_annotations/index.ts
similarity index 74%
rename from x-pack/legacy/plugins/ml/server/lib/check_annotations/index.js
rename to x-pack/plugins/ml/server/lib/check_annotations/index.ts
index 186c27b0326d70..8d9d56ad665c43 100644
--- a/x-pack/legacy/plugins/ml/server/lib/check_annotations/index.js
+++ b/x-pack/plugins/ml/server/lib/check_annotations/index.ts
@@ -4,35 +4,44 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { APICaller } from 'src/core/server';
import { mlLog } from '../../client/log';
import {
ML_ANNOTATIONS_INDEX_ALIAS_READ,
ML_ANNOTATIONS_INDEX_ALIAS_WRITE,
ML_ANNOTATIONS_INDEX_PATTERN,
-} from '../../../common/constants/index_patterns';
+} from '../../../../../legacy/plugins/ml/common/constants/index_patterns';
// Annotations Feature is available if:
// - ML_ANNOTATIONS_INDEX_PATTERN index is present
// - ML_ANNOTATIONS_INDEX_ALIAS_READ alias is present
// - ML_ANNOTATIONS_INDEX_ALIAS_WRITE alias is present
-export async function isAnnotationsFeatureAvailable(callAsCurrentUser) {
+export async function isAnnotationsFeatureAvailable(callAsCurrentUser: APICaller) {
try {
const indexParams = { index: ML_ANNOTATIONS_INDEX_PATTERN };
const annotationsIndexExists = await callAsCurrentUser('indices.exists', indexParams);
- if (!annotationsIndexExists) return false;
+ if (!annotationsIndexExists) {
+ return false;
+ }
const annotationsReadAliasExists = await callAsCurrentUser('indices.existsAlias', {
+ index: ML_ANNOTATIONS_INDEX_ALIAS_READ,
name: ML_ANNOTATIONS_INDEX_ALIAS_READ,
});
- if (!annotationsReadAliasExists) return false;
+ if (!annotationsReadAliasExists) {
+ return false;
+ }
const annotationsWriteAliasExists = await callAsCurrentUser('indices.existsAlias', {
+ index: ML_ANNOTATIONS_INDEX_ALIAS_WRITE,
name: ML_ANNOTATIONS_INDEX_ALIAS_WRITE,
});
- if (!annotationsWriteAliasExists) return false;
+ if (!annotationsWriteAliasExists) {
+ return false;
+ }
} catch (err) {
mlLog.info('Disabling ML annotations feature because the index/alias integrity check failed.');
return false;
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_license/check_license.test.ts b/x-pack/plugins/ml/server/lib/check_license/check_license.test.ts
similarity index 81%
rename from x-pack/legacy/plugins/ml/server/lib/check_license/check_license.test.ts
rename to x-pack/plugins/ml/server/lib/check_license/check_license.test.ts
index 1d80a226486bb1..942dbe37226175 100644
--- a/x-pack/legacy/plugins/ml/server/lib/check_license/check_license.test.ts
+++ b/x-pack/plugins/ml/server/lib/check_license/check_license.test.ts
@@ -7,12 +7,12 @@
import expect from '@kbn/expect';
import sinon from 'sinon';
import { set } from 'lodash';
-import { XPackInfo } from '../../../../xpack_main/server/lib/xpack_info';
+import { LicenseCheckResult } from '../../types';
import { checkLicense } from './check_license';
describe('check_license', () => {
- let mockLicenseInfo: XPackInfo;
- beforeEach(() => (mockLicenseInfo = {} as XPackInfo));
+ let mockLicenseInfo: LicenseCheckResult;
+ beforeEach(() => (mockLicenseInfo = {} as LicenseCheckResult));
describe('license information is undefined', () => {
it('should set isAvailable to false', () => {
@@ -33,7 +33,9 @@ describe('check_license', () => {
});
describe('license information is not available', () => {
- beforeEach(() => (mockLicenseInfo.isAvailable = () => false));
+ beforeEach(() => {
+ mockLicenseInfo.isAvailable = false;
+ });
it('should set isAvailable to false', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false);
@@ -54,8 +56,8 @@ describe('check_license', () => {
describe('license information is available', () => {
beforeEach(() => {
- mockLicenseInfo.isAvailable = () => true;
- set(mockLicenseInfo, 'license.getType', () => 'basic');
+ mockLicenseInfo.isAvailable = true;
+ mockLicenseInfo.type = 'basic';
});
describe('& ML is disabled in Elasticsearch', () => {
@@ -66,7 +68,7 @@ describe('check_license', () => {
sinon
.stub()
.withArgs('ml')
- .returns({ isEnabled: () => false })
+ .returns({ isEnabled: false })
);
});
@@ -89,21 +91,17 @@ describe('check_license', () => {
describe('& ML is enabled in Elasticsearch', () => {
beforeEach(() => {
- set(
- mockLicenseInfo,
- 'feature',
- sinon
- .stub()
- .withArgs('ml')
- .returns({ isEnabled: () => true })
- );
+ mockLicenseInfo.isEnabled = true;
});
describe('& license is >= platinum', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => true));
-
+ beforeEach(() => {
+ mockLicenseInfo.type = 'platinum';
+ });
describe('& license is active', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true));
+ beforeEach(() => {
+ mockLicenseInfo.isActive = true;
+ });
it('should set isAvailable to true', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true);
@@ -123,7 +121,9 @@ describe('check_license', () => {
});
describe('& license is expired', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false));
+ beforeEach(() => {
+ mockLicenseInfo.isActive = false;
+ });
it('should set isAvailable to true', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true);
@@ -144,10 +144,14 @@ describe('check_license', () => {
});
describe('& license is basic', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isOneOf', () => false));
+ beforeEach(() => {
+ mockLicenseInfo.type = 'basic';
+ });
describe('& license is active', () => {
- beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true));
+ beforeEach(() => {
+ mockLicenseInfo.isActive = true;
+ });
it('should set isAvailable to true', () => {
expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true);
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_license/check_license.ts b/x-pack/plugins/ml/server/lib/check_license/check_license.ts
similarity index 75%
rename from x-pack/legacy/plugins/ml/server/lib/check_license/check_license.ts
rename to x-pack/plugins/ml/server/lib/check_license/check_license.ts
index c88ab087a81985..5bf3d590a1912d 100644
--- a/x-pack/legacy/plugins/ml/server/lib/check_license/check_license.ts
+++ b/x-pack/plugins/ml/server/lib/check_license/check_license.ts
@@ -5,8 +5,11 @@
*/
import { i18n } from '@kbn/i18n';
-import { LICENSE_TYPE } from '../../../common/constants/license';
-import { XPackInfo } from '../../../../../../legacy/plugins/xpack_main/server/lib/xpack_info';
+import {
+ LICENSE_TYPE,
+ VALID_FULL_LICENSE_MODES,
+} from '../../../../../legacy/plugins/ml/common/constants/license';
+import { LicenseCheckResult } from '../../types';
interface Response {
isAvailable: boolean;
@@ -17,10 +20,10 @@ interface Response {
message?: string;
}
-export function checkLicense(xpackLicenseInfo: XPackInfo): Response {
+export function checkLicense(licenseCheckResult: LicenseCheckResult): Response {
// If, for some reason, we cannot get the license information
// from Elasticsearch, assume worst case and disable the Machine Learning UI
- if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) {
+ if (licenseCheckResult === undefined || !licenseCheckResult.isAvailable) {
return {
isAvailable: false,
showLinks: true,
@@ -35,7 +38,7 @@ export function checkLicense(xpackLicenseInfo: XPackInfo): Response {
};
}
- const featureEnabled = xpackLicenseInfo.feature('ml').isEnabled();
+ const featureEnabled = licenseCheckResult.isEnabled;
if (!featureEnabled) {
return {
isAvailable: false,
@@ -47,12 +50,11 @@ export function checkLicense(xpackLicenseInfo: XPackInfo): Response {
};
}
- const VALID_FULL_LICENSE_MODES = ['platinum', 'enterprise', 'trial'];
-
- const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_FULL_LICENSE_MODES);
+ const isLicenseModeValid =
+ licenseCheckResult.type && VALID_FULL_LICENSE_MODES.includes(licenseCheckResult.type);
const licenseType = isLicenseModeValid === true ? LICENSE_TYPE.FULL : LICENSE_TYPE.BASIC;
- const isLicenseActive = xpackLicenseInfo.license.isActive();
- const licenseTypeName = xpackLicenseInfo.license.getType();
+ const isLicenseActive = licenseCheckResult.isActive;
+ const licenseTypeName = licenseCheckResult.type;
// Platinum or trial license is valid but not active, i.e. expired
if (licenseType === LICENSE_TYPE.FULL && isLicenseActive === false) {
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_license/index.ts b/x-pack/plugins/ml/server/lib/check_license/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/check_license/index.ts
rename to x-pack/plugins/ml/server/lib/check_license/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts b/x-pack/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts
rename to x-pack/plugins/ml/server/lib/check_privileges/__mocks__/call_with_request.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.test.ts b/x-pack/plugins/ml/server/lib/check_privileges/check_privileges.test.ts
similarity index 91%
rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.test.ts
rename to x-pack/plugins/ml/server/lib/check_privileges/check_privileges.test.ts
index da8ef25b2f4dff..0690aa53576a5a 100644
--- a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.test.ts
+++ b/x-pack/plugins/ml/server/lib/check_privileges/check_privileges.test.ts
@@ -8,81 +8,29 @@ import { callWithRequestProvider } from './__mocks__/call_with_request';
import { privilegesProvider } from './check_privileges';
import { mlPrivileges } from './privileges';
-const xpackMainPluginWithSecurity = {
- info: {
- isAvailable: () => true,
- feature: (f: string) => {
- switch (f) {
- case 'ml':
- return { isEnabled: () => true };
- case 'security':
- return { isEnabled: () => true };
- }
- },
- license: {
- isOneOf: () => true,
- isActive: () => true,
- getType: () => 'platinum',
- },
- },
-} as any;
+const licenseCheckResultWithSecurity = {
+ isAvailable: true,
+ isEnabled: true,
+ isSecurityDisabled: false,
+ type: 'platinum',
+ isActive: true,
+};
-const xpackMainPluginWithOutSecurity = {
- info: {
- isAvailable: () => true,
- feature: (f: string) => {
- switch (f) {
- case 'ml':
- return { isEnabled: () => true };
- case 'security':
- return { isEnabled: () => false };
- }
- },
- license: {
- isOneOf: () => true,
- isActive: () => true,
- getType: () => 'platinum',
- },
- },
-} as any;
+const licenseCheckResultWithOutSecurity = {
+ ...licenseCheckResultWithSecurity,
+ isSecurityDisabled: true,
+};
-const xpackMainPluginWithOutSecurityBasicLicense = {
- info: {
- isAvailable: () => true,
- feature: (f: string) => {
- switch (f) {
- case 'ml':
- return { isEnabled: () => true };
- case 'security':
- return { isEnabled: () => false };
- }
- },
- license: {
- isOneOf: () => false,
- isActive: () => true,
- getType: () => 'basic',
- },
- },
-} as any;
+const licenseCheckResultWithOutSecurityBasicLicense = {
+ ...licenseCheckResultWithSecurity,
+ isSecurityDisabled: true,
+ type: 'basic',
+};
-const xpackMainPluginWithSecurityBasicLicense = {
- info: {
- isAvailable: () => true,
- feature: (f: string) => {
- switch (f) {
- case 'ml':
- return { isEnabled: () => true };
- case 'security':
- return { isEnabled: () => true };
- }
- },
- license: {
- isOneOf: () => false,
- isActive: () => true,
- getType: () => 'basic',
- },
- },
-} as any;
+const licenseCheckResultWithSecurityBasicLicense = {
+ ...licenseCheckResultWithSecurity,
+ type: 'basic',
+};
const mlIsEnabled = async () => true;
const mlIsNotEnabled = async () => false;
@@ -99,7 +47,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithSecurity,
+ licenseCheckResultWithSecurity,
mlIsEnabled
);
const { capabilities } = await getPrivileges();
@@ -114,7 +62,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithSecurity,
+ licenseCheckResultWithSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -149,7 +97,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithSecurity,
+ licenseCheckResultWithSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -184,7 +132,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('upgradeWithFullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithSecurity,
+ licenseCheckResultWithSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -219,7 +167,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('upgradeWithPartialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithSecurity,
+ licenseCheckResultWithSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -254,7 +202,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithSecurityBasicLicense,
+ licenseCheckResultWithSecurityBasicLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -289,7 +237,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithSecurityBasicLicense,
+ licenseCheckResultWithSecurityBasicLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -324,7 +272,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithSecurity,
+ licenseCheckResultWithSecurity,
mlIsNotEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -361,7 +309,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithOutSecurity,
+ licenseCheckResultWithOutSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -396,7 +344,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('upgradeWithFullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithOutSecurity,
+ licenseCheckResultWithOutSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -431,7 +379,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('upgradeWithPartialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithOutSecurity,
+ licenseCheckResultWithOutSecurity,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -466,7 +414,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithOutSecurityBasicLicense,
+ licenseCheckResultWithOutSecurityBasicLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -501,7 +449,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithOutSecurityBasicLicense,
+ licenseCheckResultWithOutSecurityBasicLicense,
mlIsEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
@@ -536,7 +484,7 @@ describe('check_privileges', () => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(
callWithRequest,
- xpackMainPluginWithOutSecurity,
+ licenseCheckResultWithOutSecurity,
mlIsNotEnabled
);
const { capabilities, upgradeInProgress, mlFeatureEnabledInSpace } = await getPrivileges();
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts b/x-pack/plugins/ml/server/lib/check_privileges/check_privileges.ts
similarity index 94%
rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts
rename to x-pack/plugins/ml/server/lib/check_privileges/check_privileges.ts
index 617778afbe121b..a427780d13344a 100644
--- a/x-pack/legacy/plugins/ml/server/lib/check_privileges/check_privileges.ts
+++ b/x-pack/plugins/ml/server/lib/check_privileges/check_privileges.ts
@@ -5,12 +5,14 @@
*/
import { IScopedClusterClient } from 'kibana/server';
-import { Privileges, getDefaultPrivileges } from '../../../common/types/privileges';
-import { XPackMainPlugin } from '../../../../xpack_main/server/xpack_main';
-import { isSecurityDisabled } from '../../lib/security_utils';
+import {
+ Privileges,
+ getDefaultPrivileges,
+} from '../../../../../legacy/plugins/ml/common/types/privileges';
import { upgradeCheckProvider } from './upgrade';
import { checkLicense } from '../check_license';
-import { LICENSE_TYPE } from '../../../common/constants/license';
+import { LICENSE_TYPE } from '../../../../../legacy/plugins/ml/common/constants/license';
+import { LicenseCheckResult } from '../../types';
import { mlPrivileges } from './privileges';
@@ -25,7 +27,7 @@ interface Response {
export function privilegesProvider(
callAsCurrentUser: IScopedClusterClient['callAsCurrentUser'],
- xpackMainPlugin: XPackMainPlugin,
+ licenseCheckResult: LicenseCheckResult,
isMlEnabledInSpace: () => Promise,
ignoreSpaces: boolean = false
) {
@@ -35,8 +37,8 @@ export function privilegesProvider(
const privileges = getDefaultPrivileges();
const upgradeInProgress = await isUpgradeInProgress();
- const securityDisabled = isSecurityDisabled(xpackMainPlugin);
- const license = checkLicense(xpackMainPlugin.info);
+ const securityDisabled = licenseCheckResult.isSecurityDisabled;
+ const license = checkLicense(licenseCheckResult);
const isPlatinumOrTrialLicense = license.licenseType === LICENSE_TYPE.FULL;
const mlFeatureEnabledInSpace = await isMlEnabledInSpace();
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/index.ts b/x-pack/plugins/ml/server/lib/check_privileges/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/index.ts
rename to x-pack/plugins/ml/server/lib/check_privileges/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/privileges.ts b/x-pack/plugins/ml/server/lib/check_privileges/privileges.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/privileges.ts
rename to x-pack/plugins/ml/server/lib/check_privileges/privileges.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/check_privileges/upgrade.ts b/x-pack/plugins/ml/server/lib/check_privileges/upgrade.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/check_privileges/upgrade.ts
rename to x-pack/plugins/ml/server/lib/check_privileges/upgrade.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/index.ts b/x-pack/plugins/ml/server/lib/ml_telemetry/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/ml_telemetry/index.ts
rename to x-pack/plugins/ml/server/lib/ml_telemetry/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts b/x-pack/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts
rename to x-pack/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts b/x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts
similarity index 85%
rename from x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts
rename to x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts
index 9d14ffb31be631..c03396445f8687 100644
--- a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts
+++ b/x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.test.ts
@@ -55,10 +55,9 @@ describe('ml_telemetry', () => {
});
describe('incrementFileDataVisualizerIndexCreationCount', () => {
- let savedObjects: any;
- let internalRepository: any;
+ let savedObjectsClient: any;
- function createInternalRepositoryInstance(
+ function createSavedObjectsClientInstance(
telemetryEnabled?: boolean,
indexCreationCount?: number
) {
@@ -93,42 +92,39 @@ describe('ml_telemetry', () => {
}
function mockInit(telemetryEnabled?: boolean, indexCreationCount?: number): void {
- internalRepository = createInternalRepositoryInstance(telemetryEnabled, indexCreationCount);
- savedObjects = {
- createInternalRepository: jest.fn(() => internalRepository),
- };
+ savedObjectsClient = createSavedObjectsClientInstance(telemetryEnabled, indexCreationCount);
}
it('should not increment if telemetry status cannot be determined', async () => {
mockInit();
- await incrementFileDataVisualizerIndexCreationCount(savedObjects);
+ await incrementFileDataVisualizerIndexCreationCount(savedObjectsClient);
- expect(internalRepository.create.mock.calls).toHaveLength(0);
+ expect(savedObjectsClient.create.mock.calls).toHaveLength(0);
});
it('should not increment if telemetry status is disabled', async () => {
mockInit(false);
- await incrementFileDataVisualizerIndexCreationCount(savedObjects);
+ await incrementFileDataVisualizerIndexCreationCount(savedObjectsClient);
- expect(internalRepository.create.mock.calls).toHaveLength(0);
+ expect(savedObjectsClient.create.mock.calls).toHaveLength(0);
});
it('should initialize index_creation_count with 1', async () => {
mockInit(true);
- await incrementFileDataVisualizerIndexCreationCount(savedObjects);
+ await incrementFileDataVisualizerIndexCreationCount(savedObjectsClient);
- expect(internalRepository.create.mock.calls[0][0]).toBe('ml-telemetry');
- expect(internalRepository.create.mock.calls[0][1]).toEqual({
+ expect(savedObjectsClient.create.mock.calls[0][0]).toBe('ml-telemetry');
+ expect(savedObjectsClient.create.mock.calls[0][1]).toEqual({
file_data_visualizer: { index_creation_count: 1 },
});
});
it('should increment index_creation_count to 2', async () => {
mockInit(true, 1);
- await incrementFileDataVisualizerIndexCreationCount(savedObjects);
+ await incrementFileDataVisualizerIndexCreationCount(savedObjectsClient);
- expect(internalRepository.create.mock.calls[0][0]).toBe('ml-telemetry');
- expect(internalRepository.create.mock.calls[0][1]).toEqual({
+ expect(savedObjectsClient.create.mock.calls[0][0]).toBe('ml-telemetry');
+ expect(savedObjectsClient.create.mock.calls[0][1]).toEqual({
file_data_visualizer: { index_creation_count: 2 },
});
});
diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts b/x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts
similarity index 74%
rename from x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts
rename to x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts
index d76b1ee94e21e9..8cf24213961b18 100644
--- a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts
+++ b/x-pack/plugins/ml/server/lib/ml_telemetry/ml_telemetry.ts
@@ -4,11 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import {
- SavedObjectAttributes,
- SavedObjectsServiceStart,
- ISavedObjectsRepository,
-} from 'src/core/server';
+import { SavedObjectAttributes, SavedObjectsClientContract } from 'src/core/server';
export interface MlTelemetry extends SavedObjectAttributes {
file_data_visualizer: {
@@ -31,21 +27,20 @@ export function createMlTelemetry(count: number = 0): MlTelemetry {
}
// savedObjects
export function storeMlTelemetry(
- internalRepository: ISavedObjectsRepository,
+ savedObjectsClient: SavedObjectsClientContract,
mlTelemetry: MlTelemetry
): void {
- internalRepository.create('ml-telemetry', mlTelemetry, {
+ savedObjectsClient.create('ml-telemetry', mlTelemetry, {
id: ML_TELEMETRY_DOC_ID,
overwrite: true,
});
}
export async function incrementFileDataVisualizerIndexCreationCount(
- savedObjects: SavedObjectsServiceStart
+ savedObjectsClient: SavedObjectsClientContract
): Promise {
- const internalRepository = await savedObjects.createInternalRepository();
try {
- const { attributes } = await internalRepository.get('telemetry', 'telemetry');
+ const { attributes } = await savedObjectsClient.get('telemetry', 'telemetry');
if (attributes.enabled === false) {
return;
@@ -59,7 +54,7 @@ export async function incrementFileDataVisualizerIndexCreationCount(
let indicesCount = 1;
try {
- const { attributes } = (await internalRepository.get(
+ const { attributes } = (await savedObjectsClient.get(
'ml-telemetry',
ML_TELEMETRY_DOC_ID
)) as MlTelemetrySavedObject;
@@ -69,5 +64,5 @@ export async function incrementFileDataVisualizerIndexCreationCount(
}
const mlTelemetry = createMlTelemetry(indicesCount);
- storeMlTelemetry(internalRepository, mlTelemetry);
+ storeMlTelemetry(savedObjectsClient, mlTelemetry);
}
diff --git a/x-pack/legacy/plugins/ml/server/lib/query_utils.ts b/x-pack/plugins/ml/server/lib/query_utils.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/query_utils.ts
rename to x-pack/plugins/ml/server/lib/query_utils.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/sample_data_sets/index.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/sample_data_sets/index.ts
rename to x-pack/plugins/ml/server/lib/sample_data_sets/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts b/x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts
rename to x-pack/plugins/ml/server/lib/sample_data_sets/sample_data_sets.ts
diff --git a/x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts b/x-pack/plugins/ml/server/lib/spaces_utils.ts
similarity index 75%
rename from x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts
rename to x-pack/plugins/ml/server/lib/spaces_utils.ts
index 92373bae4ea1d0..ed684eadb95704 100644
--- a/x-pack/legacy/plugins/ml/server/lib/spaces_utils.ts
+++ b/x-pack/plugins/ml/server/lib/spaces_utils.ts
@@ -5,20 +5,19 @@
*/
import { Request } from 'hapi';
-import { Space } from '../../../../../plugins/spaces/server';
-import { LegacySpacesPlugin } from '../../../spaces';
+import { Space, SpacesPluginSetup } from '../../../spaces/server';
interface GetActiveSpaceResponse {
valid: boolean;
space?: Space;
}
-export function spacesUtilsProvider(spacesPlugin: LegacySpacesPlugin, request: Request) {
+export function spacesUtilsProvider(spacesPlugin: SpacesPluginSetup, request: Request) {
async function activeSpace(): Promise {
try {
return {
valid: true,
- space: await spacesPlugin.getActiveSpace(request),
+ space: await spacesPlugin.spacesService.getActiveSpace(request),
};
} catch (e) {
return {
diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json b/x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json
rename to x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_request.json
diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json b/x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json
rename to x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json
diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.test.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts
similarity index 96%
rename from x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.test.ts
rename to x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts
index 7e0649d15bfb0e..d7a13154a6f378 100644
--- a/x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.test.ts
+++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts
@@ -8,9 +8,12 @@ import getAnnotationsRequestMock from './__mocks__/get_annotations_request.json'
import getAnnotationsResponseMock from './__mocks__/get_annotations_response.json';
import { RequestHandlerContext } from 'src/core/server';
-import { ANNOTATION_TYPE } from '../../../common/constants/annotations';
-import { ML_ANNOTATIONS_INDEX_ALIAS_WRITE } from '../../../common/constants/index_patterns';
-import { Annotation, isAnnotations } from '../../../common/types/annotations';
+import { ANNOTATION_TYPE } from '../../../../../legacy/plugins/ml/common/constants/annotations';
+import { ML_ANNOTATIONS_INDEX_ALIAS_WRITE } from '../../../../../legacy/plugins/ml/common/constants/index_patterns';
+import {
+ Annotation,
+ isAnnotations,
+} from '../../../../../legacy/plugins/ml/common/types/annotations';
import { DeleteParams, GetResponse, IndexAnnotationArgs } from './annotation';
import { annotationServiceProvider } from './index';
diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts
similarity index 96%
rename from x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.ts
rename to x-pack/plugins/ml/server/models/annotation_service/annotation.ts
index 399305ea2603eb..042d7bbc80653a 100644
--- a/x-pack/legacy/plugins/ml/server/models/annotation_service/annotation.ts
+++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts
@@ -8,18 +8,18 @@ import Boom from 'boom';
import _ from 'lodash';
import { RequestHandlerContext } from 'src/core/server';
-import { ANNOTATION_TYPE } from '../../../common/constants/annotations';
+import { ANNOTATION_TYPE } from '../../../../../legacy/plugins/ml/common/constants/annotations';
import {
ML_ANNOTATIONS_INDEX_ALIAS_READ,
ML_ANNOTATIONS_INDEX_ALIAS_WRITE,
-} from '../../../common/constants/index_patterns';
+} from '../../../../../legacy/plugins/ml/common/constants/index_patterns';
import {
Annotation,
Annotations,
isAnnotation,
isAnnotations,
-} from '../../../common/types/annotations';
+} from '../../../../../legacy/plugins/ml/common/types/annotations';
// TODO All of the following interface/type definitions should
// eventually be replaced by the proper upstream definitions
diff --git a/x-pack/legacy/plugins/ml/server/models/annotation_service/index.ts b/x-pack/plugins/ml/server/models/annotation_service/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/annotation_service/index.ts
rename to x-pack/plugins/ml/server/models/annotation_service/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js
rename to x-pack/plugins/ml/server/models/bucket_span_estimator/__tests__/bucket_span_estimator.js
diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts
similarity index 75%
rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts
rename to x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts
index ea986feab4e997..e39a0177c31b95 100644
--- a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts
+++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.d.ts
@@ -5,10 +5,10 @@
*/
import { APICaller } from 'src/core/server';
-import { BucketSpanEstimatorData } from '../../../public/application/services/ml_api_service';
+import { BucketSpanEstimatorData } from '../../../../../legacy/plugins/ml/public/application/services/ml_api_service';
export function estimateBucketSpanFactory(
callAsCurrentUser: APICaller,
callAsInternalUser: APICaller,
- xpackMainPlugin: any
+ isSecurityDisabled: boolean
): (config: BucketSpanEstimatorData) => Promise;
diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js
similarity index 98%
rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js
rename to x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js
index aec677dd57d614..53b9d75304963b 100644
--- a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js
+++ b/x-pack/plugins/ml/server/models/bucket_span_estimator/bucket_span_estimator.js
@@ -12,9 +12,11 @@ import { INTERVALS } from './intervals';
import { singleSeriesCheckerFactory } from './single_series_checker';
import { polledDataCheckerFactory } from './polled_data_checker';
-import { isSecurityDisabled } from '../../lib/security_utils';
-
-export function estimateBucketSpanFactory(callAsCurrentUser, callAsInternalUser, xpackMainPlugin) {
+export function estimateBucketSpanFactory(
+ callAsCurrentUser,
+ callAsInternalUser,
+ isSecurityDisabled
+) {
const PolledDataChecker = polledDataCheckerFactory(callAsCurrentUser);
const SingleSeriesChecker = singleSeriesCheckerFactory(callAsCurrentUser);
@@ -384,7 +386,7 @@ export function estimateBucketSpanFactory(callAsCurrentUser, callAsInternalUser,
});
}
- if (isSecurityDisabled(xpackMainPlugin)) {
+ if (isSecurityDisabled) {
getBucketSpanEstimation();
} else {
// if security is enabled, check that the user has permission to
diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/index.ts b/x-pack/plugins/ml/server/models/bucket_span_estimator/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/index.ts
rename to x-pack/plugins/ml/server/models/bucket_span_estimator/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/intervals.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/intervals.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/intervals.js
rename to x-pack/plugins/ml/server/models/bucket_span_estimator/intervals.js
diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js
rename to x-pack/plugins/ml/server/models/bucket_span_estimator/polled_data_checker.js
diff --git a/x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js b/x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js
rename to x-pack/plugins/ml/server/models/bucket_span_estimator/single_series_checker.js
diff --git a/x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts
rename to x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.d.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js
rename to x-pack/plugins/ml/server/models/calculate_model_memory_limit/calculate_model_memory_limit.js
diff --git a/x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/index.ts b/x-pack/plugins/ml/server/models/calculate_model_memory_limit/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/calculate_model_memory_limit/index.ts
rename to x-pack/plugins/ml/server/models/calculate_model_memory_limit/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/calendar_manager.ts b/x-pack/plugins/ml/server/models/calendar/calendar_manager.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/calendar/calendar_manager.ts
rename to x-pack/plugins/ml/server/models/calendar/calendar_manager.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts b/x-pack/plugins/ml/server/models/calendar/event_manager.ts
similarity index 94%
rename from x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts
rename to x-pack/plugins/ml/server/models/calendar/event_manager.ts
index 488839f68b3fe2..0a3108016da0e3 100644
--- a/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts
+++ b/x-pack/plugins/ml/server/models/calendar/event_manager.ts
@@ -6,7 +6,7 @@
import Boom from 'boom';
-import { GLOBAL_CALENDAR } from '../../../common/constants/calendars';
+import { GLOBAL_CALENDAR } from '../../../../../legacy/plugins/ml/common/constants/calendars';
export interface CalendarEvent {
calendar_id?: string;
diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/index.ts b/x-pack/plugins/ml/server/models/calendar/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/calendar/index.ts
rename to x-pack/plugins/ml/server/models/calendar/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts
similarity index 88%
rename from x-pack/legacy/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts
rename to x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts
index abe389165182f0..a8757e289dcf75 100644
--- a/x-pack/legacy/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts
+++ b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_audit_messages.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { callWithRequestType } from '../../../common/types/kibana';
-import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns';
-import { JobMessage } from '../../../common/types/audit_message';
+import { callWithRequestType } from '../../../../../legacy/plugins/ml/common/types/kibana';
+import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../../../legacy/plugins/ml/common/constants/index_patterns';
+import { JobMessage } from '../../../../../legacy/plugins/ml/common/types/audit_message';
const SIZE = 50;
diff --git a/x-pack/legacy/plugins/ml/server/models/data_frame_analytics/index.js b/x-pack/plugins/ml/server/models/data_frame_analytics/index.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_frame_analytics/index.js
rename to x-pack/plugins/ml/server/models/data_frame_analytics/index.js
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts
similarity index 97%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts
rename to x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts
index de23950e5cc1c8..c51f65714bc055 100644
--- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts
+++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.test.ts
@@ -5,7 +5,7 @@
*/
import { RequestHandlerContext } from 'kibana/server';
-import { Module } from '../../../common/types/modules';
+import { Module } from '../../../../../legacy/plugins/ml/common/types/modules';
import { DataRecognizer } from '../data_recognizer';
describe('ML - data recognizer', () => {
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts
similarity index 99%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts
rename to x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts
index 553de75e38e05d..8d2a6c9955da36 100644
--- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts
+++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts
@@ -10,7 +10,7 @@ import numeral from '@elastic/numeral';
import { CallAPIOptions, RequestHandlerContext, SavedObjectsClientContract } from 'kibana/server';
import { IndexPatternAttributes } from 'src/plugins/data/server';
import { merge } from 'lodash';
-import { MlJob } from '../../../common/types/jobs';
+import { MlJob } from '../../../../../legacy/plugins/ml/common/types/jobs';
import {
KibanaObjects,
ModuleDataFeed,
@@ -23,8 +23,11 @@ import {
JobResponse,
KibanaObjectResponse,
DataRecognizerConfigResponse,
-} from '../../../common/types/modules';
-import { getLatestDataOrBucketTimestamp, prefixDatafeedId } from '../../../common/util/job_utils';
+} from '../../../../../legacy/plugins/ml/common/types/modules';
+import {
+ getLatestDataOrBucketTimestamp,
+ prefixDatafeedId,
+} from '../../../../../legacy/plugins/ml/common/util/job_utils';
import { mlLog } from '../../client/log';
// @ts-ignore
import { jobServiceProvider } from '../job_service';
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/index.ts b/x-pack/plugins/ml/server/models/data_recognizer/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/index.ts
rename to x-pack/plugins/ml/server/models/data_recognizer/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/search/ml_http_access_filebeat_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_map_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/low_request_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_request_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/source_ip_url_count_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/status_code_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/visitor_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/high_mean_response_time.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_event_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/dashboard/ml_auditbeat_docker_process_explorer_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/search/ml_auditbeat_docker_process_events_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_by_process_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_event_rate_vis_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/kibana/visualization/ml_auditbeat_docker_process_occurrence_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_high_count_process_events_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/docker_rare_process_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_event_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/dashboard/ml_auditbeat_hosts_process_explorer_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/search/ml_auditbeat_hosts_process_events_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_by_process_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_event_rate_vis_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/kibana/visualization/ml_auditbeat_hosts_process_occurrence_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_high_count_process_events_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/hosts_rare_process_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/log_entry_rate.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/datafeed_log_entry_categories_count.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/logs_ui_categories/ml/log_entry_categories_count.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/high_mean_cpu_iowait_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/max_disk_utilization_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/metricbeat_outages_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/dashboard/ml_http_access_explorer_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/search/ml_http_access_filebeat_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_events_timechart_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_map_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_source_ip_timechart_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_status_code_timechart_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_source_ips_table_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_top_urls_table_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/kibana/visualization/ml_http_access_unique_count_url_timechart_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/low_request_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_request_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/source_ip_url_count_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/status_code_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/visitor_rate_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/high_sum_total_sales.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/low_request_rate.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/response_code_rates.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/url_scanning.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_port_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_service.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_network_url_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_process_all_hosts_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_linux_anomalous_user_name_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_port_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_service.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_network_url_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_process_all_hosts_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/linux_anomalous_user_name_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/rare_process_by_host_linux_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/suspicious_login_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_dns_tunneling.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_dns_question.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_server_domain.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_urls.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/datafeed_packetbeat_rare_user_agent.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_dns_tunneling.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_dns_question.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_server_domain.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_urls.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_packetbeat/ml/packetbeat_rare_user_agent.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_network_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_path_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_all_hosts_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_process_creation.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_script.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_service.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_anomalous_user_name_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_windows_rare_user_runas_event.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/rare_process_by_host_windows_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_network_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_path_activity_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_all_hosts_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_process_creation.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_script.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_service.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_anomalous_user_name_ecs.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/windows_rare_user_runas_event.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/logo.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/manifest.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/datafeed_windows_rare_user_type10_remote_login.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json
rename to x-pack/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json
diff --git a/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts
similarity index 99%
rename from x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.ts
rename to x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts
index b0a61b1232dc0b..9463f74e1e746e 100644
--- a/x-pack/legacy/plugins/ml/server/models/data_visualizer/data_visualizer.ts
+++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts
@@ -6,8 +6,8 @@
import { CallAPIOptions, IScopedClusterClient } from 'src/core/server';
import _ from 'lodash';
-import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types';
-import { getSafeAggregationName } from '../../../common/util/job_utils';
+import { ML_JOB_FIELD_TYPES } from '../../../../../legacy/plugins/ml/common/constants/field_types';
+import { getSafeAggregationName } from '../../../../../legacy/plugins/ml/common/util/job_utils';
import {
buildBaseFilterCriteria,
buildSamplerAggregation,
diff --git a/x-pack/legacy/plugins/ml/server/models/data_visualizer/index.ts b/x-pack/plugins/ml/server/models/data_visualizer/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/data_visualizer/index.ts
rename to x-pack/plugins/ml/server/models/data_visualizer/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/fields_service/fields_service.d.ts b/x-pack/plugins/ml/server/models/fields_service/fields_service.d.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/fields_service/fields_service.d.ts
rename to x-pack/plugins/ml/server/models/fields_service/fields_service.d.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/fields_service/fields_service.js b/x-pack/plugins/ml/server/models/fields_service/fields_service.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/fields_service/fields_service.js
rename to x-pack/plugins/ml/server/models/fields_service/fields_service.js
diff --git a/x-pack/legacy/plugins/ml/server/models/fields_service/index.ts b/x-pack/plugins/ml/server/models/fields_service/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/fields_service/index.ts
rename to x-pack/plugins/ml/server/models/fields_service/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts
similarity index 95%
rename from x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts
rename to x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts
index 9f30f609c60b63..1d0452f2337f95 100644
--- a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts
+++ b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts
@@ -6,7 +6,7 @@
import Boom from 'boom';
import { RequestHandlerContext } from 'kibana/server';
-import { FindFileStructureResponse } from '../../../common/types/file_datavisualizer';
+import { FindFileStructureResponse } from '../../../../../legacy/plugins/ml/common/types/file_datavisualizer';
export type InputData = any[];
diff --git a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/import_data.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts
similarity index 97%
rename from x-pack/legacy/plugins/ml/server/models/file_data_visualizer/import_data.ts
rename to x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts
index 008efb43a6c07e..e4de71ad0793d2 100644
--- a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/import_data.ts
+++ b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts
@@ -5,7 +5,7 @@
*/
import { RequestHandlerContext } from 'kibana/server';
-import { INDEX_META_DATA_CREATED_BY } from '../../../common/constants/file_datavisualizer';
+import { INDEX_META_DATA_CREATED_BY } from '../../../../../legacy/plugins/ml/common/constants/file_datavisualizer';
import { InputData } from './file_data_visualizer';
export interface Settings {
diff --git a/x-pack/legacy/plugins/ml/server/models/file_data_visualizer/index.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/file_data_visualizer/index.ts
rename to x-pack/plugins/ml/server/models/file_data_visualizer/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/filter/filter_manager.ts b/x-pack/plugins/ml/server/models/filter/filter_manager.ts
similarity index 98%
rename from x-pack/legacy/plugins/ml/server/models/filter/filter_manager.ts
rename to x-pack/plugins/ml/server/models/filter/filter_manager.ts
index f40663a5eb6b2d..baba495257acac 100644
--- a/x-pack/legacy/plugins/ml/server/models/filter/filter_manager.ts
+++ b/x-pack/plugins/ml/server/models/filter/filter_manager.ts
@@ -7,7 +7,10 @@
import Boom from 'boom';
import { IScopedClusterClient } from 'src/core/server';
-import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules';
+import {
+ DetectorRule,
+ DetectorRuleScope,
+} from '../../../../../legacy/plugins/ml/common/types/detector_rules';
export interface Filter {
filter_id: string;
diff --git a/x-pack/legacy/plugins/ml/server/models/filter/index.js b/x-pack/plugins/ml/server/models/filter/index.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/filter/index.js
rename to x-pack/plugins/ml/server/models/filter/index.js
diff --git a/x-pack/legacy/plugins/ml/server/models/filter/index.ts b/x-pack/plugins/ml/server/models/filter/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/filter/index.ts
rename to x-pack/plugins/ml/server/models/filter/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/index.ts b/x-pack/plugins/ml/server/models/job_audit_messages/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_audit_messages/index.ts
rename to x-pack/plugins/ml/server/models/job_audit_messages/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts
rename to x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.d.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
similarity index 98%
rename from x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
rename to x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
index 2cdfc0ef4f4c5f..b434846d6f0f4c 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
+++ b/x-pack/plugins/ml/server/models/job_audit_messages/job_audit_messages.js
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns';
+import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../../../legacy/plugins/ml/common/constants/index_patterns';
import moment from 'moment';
const SIZE = 1000;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/datafeeds.js b/x-pack/plugins/ml/server/models/job_service/datafeeds.js
similarity index 97%
rename from x-pack/legacy/plugins/ml/server/models/job_service/datafeeds.js
rename to x-pack/plugins/ml/server/models/job_service/datafeeds.js
index c3b54fff0682d8..961b712610512d 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/datafeeds.js
+++ b/x-pack/plugins/ml/server/models/job_service/datafeeds.js
@@ -5,7 +5,10 @@
*/
import { i18n } from '@kbn/i18n';
-import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states';
+import {
+ JOB_STATE,
+ DATAFEED_STATE,
+} from '../../../../../legacy/plugins/ml/common/constants/states';
import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils';
export function datafeedsProvider(callWithRequest) {
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/error_utils.js b/x-pack/plugins/ml/server/models/job_service/error_utils.js
similarity index 94%
rename from x-pack/legacy/plugins/ml/server/models/job_service/error_utils.js
rename to x-pack/plugins/ml/server/models/job_service/error_utils.js
index 6f25b5870f85ce..21e45110e70930 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/error_utils.js
+++ b/x-pack/plugins/ml/server/models/job_service/error_utils.js
@@ -5,7 +5,10 @@
*/
import { i18n } from '@kbn/i18n';
-import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states';
+import {
+ JOB_STATE,
+ DATAFEED_STATE,
+} from '../../../../../legacy/plugins/ml/common/constants/states';
const REQUEST_TIMEOUT = 'RequestTimeout';
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/groups.js b/x-pack/plugins/ml/server/models/job_service/groups.js
similarity index 95%
rename from x-pack/legacy/plugins/ml/server/models/job_service/groups.js
rename to x-pack/plugins/ml/server/models/job_service/groups.js
index 6fbc071ef9854f..b30e9cdc6048b7 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/groups.js
+++ b/x-pack/plugins/ml/server/models/job_service/groups.js
@@ -5,7 +5,7 @@
*/
import { CalendarManager } from '../calendar';
-import { GLOBAL_CALENDAR } from '../../../common/constants/calendars';
+import { GLOBAL_CALENDAR } from '../../../../../legacy/plugins/ml/common/constants/calendars';
export function groupsProvider(callWithRequest) {
const calMngr = new CalendarManager(callWithRequest);
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/index.js b/x-pack/plugins/ml/server/models/job_service/index.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/index.js
rename to x-pack/plugins/ml/server/models/job_service/index.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/jobs.js b/x-pack/plugins/ml/server/models/job_service/jobs.js
similarity index 98%
rename from x-pack/legacy/plugins/ml/server/models/job_service/jobs.js
rename to x-pack/plugins/ml/server/models/job_service/jobs.js
index b4b476c1f926ea..16d3c30bb0a280 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/jobs.js
+++ b/x-pack/plugins/ml/server/models/job_service/jobs.js
@@ -5,7 +5,10 @@
*/
import { i18n } from '@kbn/i18n';
-import { JOB_STATE, DATAFEED_STATE } from '../../../common/constants/states';
+import {
+ JOB_STATE,
+ DATAFEED_STATE,
+} from '../../../../../legacy/plugins/ml/common/constants/states';
import { datafeedsProvider } from './datafeeds';
import { jobAuditMessagesProvider } from '../job_audit_messages';
import { resultsServiceProvider } from '../results_service';
@@ -14,7 +17,7 @@ import { fillResultsWithTimeouts, isRequestTimeout } from './error_utils';
import {
getLatestDataOrBucketTimestamp,
isTimeSeriesViewJob,
-} from '../../../common/util/job_utils';
+} from '../../../../../legacy/plugins/ml/common/util/job_utils';
import { groupsProvider } from './groups';
import { uniq } from 'lodash';
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts
similarity index 95%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts
index ea2c71b04f56d2..1a098fdf16bb7c 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/examples.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/examples.ts
@@ -6,13 +6,13 @@
import { chunk } from 'lodash';
import { SearchResponse } from 'elasticsearch';
-import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../common/constants/new_job';
+import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../../../legacy/plugins/ml/common/constants/new_job';
import {
Token,
CategorizationAnalyzer,
CategoryFieldExample,
-} from '../../../../../common/types/categories';
-import { callWithRequestType } from '../../../../../common/types/kibana';
+} from '../../../../../../../legacy/plugins/ml/common/types/categories';
+import { callWithRequestType } from '../../../../../../../legacy/plugins/ml/common/types/kibana';
import { ValidationResults } from './validation_results';
const CHUNK_SIZE = 100;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/index.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job/categorization/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts
similarity index 92%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts
index 3361cc454e2b7b..c8eb0002a31c80 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/top_categories.ts
@@ -5,9 +5,12 @@
*/
import { SearchResponse } from 'elasticsearch';
-import { ML_RESULTS_INDEX_PATTERN } from '../../../../../common/constants/index_patterns';
-import { CategoryId, Category } from '../../../../../common/types/categories';
-import { callWithRequestType } from '../../../../../common/types/kibana';
+import { ML_RESULTS_INDEX_PATTERN } from '../../../../../../../legacy/plugins/ml/common/constants/index_patterns';
+import {
+ CategoryId,
+ Category,
+} from '../../../../../../../legacy/plugins/ml/common/types/categories';
+import { callWithRequestType } from '../../../../../../../legacy/plugins/ml/common/types/kibana';
export function topCategoriesProvider(callWithRequest: callWithRequestType) {
async function getTotalCategories(jobId: string): Promise<{ total: number }> {
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts
similarity index 96%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts
index 34e63eabb405ef..bb1106b4d6396f 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job/categorization/validation_results.ts
@@ -9,13 +9,13 @@ import {
CATEGORY_EXAMPLES_VALIDATION_STATUS,
CATEGORY_EXAMPLES_ERROR_LIMIT,
CATEGORY_EXAMPLES_WARNING_LIMIT,
-} from '../../../../../common/constants/new_job';
+} from '../../../../../../../legacy/plugins/ml/common/constants/new_job';
import {
FieldExampleCheck,
CategoryFieldExample,
VALIDATION_RESULT,
-} from '../../../../../common/types/categories';
-import { getMedianStringLength } from '../../../../../common/util/string_utils';
+} from '../../../../../../../legacy/plugins/ml/common/types/categories';
+import { getMedianStringLength } from '../../../../../../../legacy/plugins/ml/common/util/string_utils';
const VALID_TOKEN_COUNT = 3;
const MEDIAN_LINE_LENGTH_LIMIT = 400;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts b/x-pack/plugins/ml/server/models/job_service/new_job/charts.ts
similarity index 87%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job/charts.ts
index 88ae8caa91e4a1..e662e3ca03ded6 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job/charts.ts
@@ -6,7 +6,7 @@
import { newJobLineChartProvider } from './line_chart';
import { newJobPopulationChartProvider } from './population_chart';
-import { callWithRequestType } from '../../../../common/types/kibana';
+import { callWithRequestType } from '../../../../../../legacy/plugins/ml/common/types/kibana';
export function newJobChartsProvider(callWithRequest: callWithRequestType) {
const { newJobLineChart } = newJobLineChartProvider(callWithRequest);
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts b/x-pack/plugins/ml/server/models/job_service/new_job/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts
similarity index 92%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts
index c1a5ad5e38ecc9..3dfe935c655d5c 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts
@@ -5,9 +5,12 @@
*/
import { get } from 'lodash';
-import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields';
-import { callWithRequestType } from '../../../../common/types/kibana';
-import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils';
+import {
+ AggFieldNamePair,
+ EVENT_RATE_FIELD_ID,
+} from '../../../../../../legacy/plugins/ml/common/types/fields';
+import { callWithRequestType } from '../../../../../../legacy/plugins/ml/common/types/kibana';
+import { ML_MEDIAN_PERCENTS } from '../../../../../../legacy/plugins/ml/common/util/job_utils';
type DtrIndex = number;
type TimeStamp = number;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts
similarity index 95%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts
index ee35f13c44ee60..d1ef9773f8f179 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts
@@ -5,9 +5,12 @@
*/
import { get } from 'lodash';
-import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields';
-import { callWithRequestType } from '../../../../common/types/kibana';
-import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils';
+import {
+ AggFieldNamePair,
+ EVENT_RATE_FIELD_ID,
+} from '../../../../../../legacy/plugins/ml/common/types/fields';
+import { callWithRequestType } from '../../../../../../legacy/plugins/ml/common/types/kibana';
+import { ML_MEDIAN_PERCENTS } from '../../../../../../legacy/plugins/ml/common/util/job_utils';
const OVER_FIELD_EXAMPLES_COUNT = 40;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/cloudwatch_field_caps.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/farequote_field_caps.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/kibana_saved_objects.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/responses/rollup_caps.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/cloudwatch_rollup_job_caps.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json b/x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/__mocks__/results/farequote_job_caps_empty.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts
similarity index 97%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts
index efe06f8b5ad4ab..475612f276c724 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/aggregations.ts
@@ -4,12 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Aggregation, METRIC_AGG_TYPE } from '../../../../common/types/fields';
+import {
+ Aggregation,
+ METRIC_AGG_TYPE,
+} from '../../../../../../legacy/plugins/ml/common/types/fields';
import {
ML_JOB_AGGREGATION,
KIBANA_AGGREGATION,
ES_AGGREGATION,
-} from '../../../../common/constants/aggregation_types';
+} from '../../../../../../legacy/plugins/ml/common/constants/aggregation_types';
// aggregation object missing id, title and fields and has null for kibana and dsl aggregation names.
// this is used as the basis for the ML only aggregations
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
similarity index 96%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
index 5827201a636619..446c71dd40f684 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
@@ -12,9 +12,9 @@ import {
FieldId,
NewJobCaps,
METRIC_AGG_TYPE,
-} from '../../../../common/types/fields';
-import { ES_FIELD_TYPES } from '../../../../../../../../src/plugins/data/server';
-import { ML_JOB_AGGREGATION } from '../../../../common/constants/aggregation_types';
+} from '../../../../../../legacy/plugins/ml/common/types/fields';
+import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/server';
+import { ML_JOB_AGGREGATION } from '../../../../../../legacy/plugins/ml/common/constants/aggregation_types';
import { rollupServiceProvider, RollupJob, RollupFields } from './rollup';
import { aggregations, mlOnlyAggregations } from './aggregations';
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/index.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/index.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts
similarity index 93%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts
index 3a9d979ccb22ca..0a967c760a1932 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts
@@ -5,7 +5,11 @@
*/
import { SavedObjectsClientContract } from 'kibana/server';
-import { Aggregation, Field, NewJobCaps } from '../../../../common/types/fields';
+import {
+ Aggregation,
+ Field,
+ NewJobCaps,
+} from '../../../../../../legacy/plugins/ml/common/types/fields';
import { fieldServiceProvider } from './field_service';
interface NewJobCapsResponse {
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts
similarity index 92%
rename from x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts
rename to x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts
index 1e9ce3d8d50225..4cbdfe4f360e03 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts
+++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/rollup.ts
@@ -7,8 +7,8 @@
import { SavedObject } from 'src/core/server';
import { IndexPatternAttributes } from 'src/plugins/data/server';
import { SavedObjectsClientContract } from 'kibana/server';
-import { FieldId } from '../../../../common/types/fields';
-import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types';
+import { FieldId } from '../../../../../../legacy/plugins/ml/common/types/fields';
+import { ES_AGGREGATION } from '../../../../../../legacy/plugins/ml/common/constants/aggregation_types';
export type RollupFields = Record]>;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/job_validation.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/job_validation.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/job_validation.js
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/job_validation.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_farequote_cardinality.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_farequote_search_response.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_field_caps.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_it_search_response.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_field.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_field_nested.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json b/x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/mock_time_range.json
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js
similarity index 98%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js
index 3dc2bee1e8705f..023e0f5b614ed8 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js
+++ b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_bucket_span.js
@@ -6,7 +6,7 @@
import expect from '@kbn/expect';
import { validateBucketSpan } from '../validate_bucket_span';
-import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../../common/constants/validation';
+import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../../../../legacy/plugins/ml/common/constants/validation';
// farequote2017 snapshot snapshot mock search response
// it returns a mock for the response of PolledDataChecker's search request
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_cardinality.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_influencers.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_model_memory_limit.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js b/x-pack/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js
rename to x-pack/plugins/ml/server/models/job_validation/__tests__/validate_time_range.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/index.ts b/x-pack/plugins/ml/server/models/job_validation/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/index.ts
rename to x-pack/plugins/ml/server/models/job_validation/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.d.ts b/x-pack/plugins/ml/server/models/job_validation/job_validation.d.ts
similarity index 83%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.d.ts
rename to x-pack/plugins/ml/server/models/job_validation/job_validation.d.ts
index 4580602b0af238..bb8a372eaba30c 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.d.ts
+++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.d.ts
@@ -6,7 +6,7 @@
import { APICaller } from 'src/core/server';
import { TypeOf } from '@kbn/config-schema';
-import { validateJobSchema } from '../../new_platform/job_validation_schema';
+import { validateJobSchema } from '../../routes/schemas/job_validation_schema';
type ValidateJobPayload = TypeOf;
@@ -15,5 +15,5 @@ export function validateJob(
payload: ValidateJobPayload,
kbnVersion: string,
callAsInternalUser: APICaller,
- xpackMainPlugin: any
+ isSecurityDisabled: boolean
): string[];
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.js b/x-pack/plugins/ml/server/models/job_validation/job_validation.js
similarity index 94%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.js
rename to x-pack/plugins/ml/server/models/job_validation/job_validation.js
index ab1fbb39ee7062..d453c9add97d11 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_validation/job_validation.js
+++ b/x-pack/plugins/ml/server/models/job_validation/job_validation.js
@@ -8,11 +8,14 @@ import { i18n } from '@kbn/i18n';
import Boom from 'boom';
import { fieldsServiceProvider } from '../fields_service';
-import { renderTemplate } from '../../../common/util/string_utils';
+import { renderTemplate } from '../../../../../legacy/plugins/ml/common/util/string_utils';
import { getMessages } from './messages';
-import { VALIDATION_STATUS } from '../../../common/constants/validation';
+import { VALIDATION_STATUS } from '../../../../../legacy/plugins/ml/common/constants/validation';
-import { basicJobValidation, uniqWithIsEqual } from '../../../common/util/job_utils';
+import {
+ basicJobValidation,
+ uniqWithIsEqual,
+} from '../../../../../legacy/plugins/ml/common/util/job_utils';
import { validateBucketSpan } from './validate_bucket_span';
import { validateCardinality } from './validate_cardinality';
import { validateInfluencers } from './validate_influencers';
@@ -24,7 +27,7 @@ export async function validateJob(
payload,
kbnVersion = 'current',
callAsInternalUser,
- xpackMainPlugin
+ isSecurityDisabled
) {
const messages = getMessages();
@@ -112,7 +115,7 @@ export async function validateJob(
job,
duration,
callAsInternalUser,
- xpackMainPlugin
+ isSecurityDisabled
))
);
validationMessages.push(...(await validateTimeRange(callWithRequest, job, duration)));
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js b/x-pack/plugins/ml/server/models/job_validation/messages.js
similarity index 99%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/messages.js
rename to x-pack/plugins/ml/server/models/job_validation/messages.js
index 2c0c218bf86b51..33931f03facc3a 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js
+++ b/x-pack/plugins/ml/server/models/job_validation/messages.js
@@ -5,7 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
-import { JOB_ID_MAX_LENGTH } from '../../../common/constants/validation';
+import { JOB_ID_MAX_LENGTH } from '../../../../../legacy/plugins/ml/common/constants/validation';
let messages;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_bucket_span.js b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js
similarity index 93%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_bucket_span.js
rename to x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js
index 2914f086c1a83e..9e96e2219fb0f5 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_bucket_span.js
+++ b/x-pack/plugins/ml/server/models/job_validation/validate_bucket_span.js
@@ -5,9 +5,9 @@
*/
import { estimateBucketSpanFactory } from '../../models/bucket_span_estimator';
-import { mlFunctionToESAggregation } from '../../../common/util/job_utils';
-import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../common/constants/validation';
-import { parseInterval } from '../../../common/util/parse_interval';
+import { mlFunctionToESAggregation } from '../../../../../legacy/plugins/ml/common/util/job_utils';
+import { SKIP_BUCKET_SPAN_ESTIMATION } from '../../../../../legacy/plugins/ml/common/constants/validation';
+import { parseInterval } from '../../../../../legacy/plugins/ml/common/util/parse_interval';
import { validateJobObject } from './validate_job_object';
@@ -51,7 +51,7 @@ export async function validateBucketSpan(
job,
duration,
callAsInternalUser,
- xpackMainPlugin
+ isSecurityDisabled
) {
validateJobObject(job);
@@ -124,7 +124,7 @@ export async function validateBucketSpan(
estimateBucketSpanFactory(
callWithRequest,
callAsInternalUser,
- xpackMainPlugin
+ isSecurityDisabled
)(data)
.then(resolve)
// this catch gets triggered when the estimation code runs without error
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.d.ts b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts
similarity index 78%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.d.ts
rename to x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts
index dc109055337889..d3930ecf44c8d5 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.d.ts
+++ b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.d.ts
@@ -5,7 +5,10 @@
*/
import { APICaller } from 'src/core/server';
-import { Job, Datafeed } from '../../../public/application/jobs/new_job/common/job_creator/configs';
+import {
+ Job,
+ Datafeed,
+} from '../../../../../legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/configs';
interface ValidateCardinalityConfig extends Job {
datafeed_config?: Datafeed;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.js b/x-pack/plugins/ml/server/models/job_validation/validate_cardinality.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_cardinality.js
rename to x-pack/plugins/ml/server/models/job_validation/validate_cardinality.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_influencers.js b/x-pack/plugins/ml/server/models/job_validation/validate_influencers.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_influencers.js
rename to x-pack/plugins/ml/server/models/job_validation/validate_influencers.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_job_object.js b/x-pack/plugins/ml/server/models/job_validation/validate_job_object.js
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_job_object.js
rename to x-pack/plugins/ml/server/models/job_validation/validate_job_object.js
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_model_memory_limit.js b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.js
similarity index 98%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_model_memory_limit.js
rename to x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.js
index 733ed9c3c22c67..354a3124a534f7 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_model_memory_limit.js
+++ b/x-pack/plugins/ml/server/models/job_validation/validate_model_memory_limit.js
@@ -7,7 +7,7 @@
import numeral from '@elastic/numeral';
import { validateJobObject } from './validate_job_object';
import { calculateModelMemoryLimitProvider } from '../../models/calculate_model_memory_limit';
-import { ALLOWED_DATA_UNITS } from '../../../common/constants/validation';
+import { ALLOWED_DATA_UNITS } from '../../../../../legacy/plugins/ml/common/constants/validation';
// The minimum value the backend expects is 1MByte
const MODEL_MEMORY_LIMIT_MINIMUM_BYTES = 1048576;
diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_time_range.js b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.js
similarity index 93%
rename from x-pack/legacy/plugins/ml/server/models/job_validation/validate_time_range.js
rename to x-pack/plugins/ml/server/models/job_validation/validate_time_range.js
index df14d372664961..e6a92b45649b0d 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_validation/validate_time_range.js
+++ b/x-pack/plugins/ml/server/models/job_validation/validate_time_range.js
@@ -6,8 +6,8 @@
import _ from 'lodash';
-import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/server';
-import { parseInterval } from '../../../common/util/parse_interval';
+import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/server';
+import { parseInterval } from '../../../../../legacy/plugins/ml/common/util/parse_interval';
import { validateJobObject } from './validate_job_object';
const BUCKET_SPAN_COMPARE_FACTOR = 25;
diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts
similarity index 89%
rename from x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts
rename to x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts
index 2bd19985c85189..f2d74fb9152996 100644
--- a/x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts
+++ b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.d.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { AnomalyRecordDoc } from '../../../common/types/anomalies';
+import { AnomalyRecordDoc } from '../../../../../legacy/plugins/ml/common/types/anomalies';
export interface AnomaliesTableRecord {
time: number;
diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.js b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js
similarity index 99%
rename from x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.js
rename to x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js
index 4934a0ba07081e..fc4280c74994d9 100644
--- a/x-pack/legacy/plugins/ml/server/models/results_service/build_anomaly_table_items.js
+++ b/x-pack/plugins/ml/server/models/results_service/build_anomaly_table_items.js
@@ -12,7 +12,7 @@ import {
getEntityFieldValue,
showActualForFunction,
showTypicalForFunction,
-} from '../../../common/util/anomaly_utils';
+} from '../../../../../legacy/plugins/ml/common/util/anomaly_utils';
// Builds the items for display in the anomalies table from the supplied list of anomaly records.
// Provide the timezone to use for aggregating anomalies (by day or hour) as set in the
diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts
similarity index 95%
rename from x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts
rename to x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts
index 99eeaacc8de9cd..5d536059cb0a27 100644
--- a/x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts
+++ b/x-pack/plugins/ml/server/models/results_service/get_partition_fields_values.ts
@@ -5,8 +5,8 @@
*/
import Boom from 'boom';
-import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns';
-import { callWithRequestType } from '../../../common/types/kibana';
+import { ML_RESULTS_INDEX_PATTERN } from '../../../../../legacy/plugins/ml/common/constants/index_patterns';
+import { callWithRequestType } from '../../../../../legacy/plugins/ml/common/types/kibana';
import { CriteriaField } from './results_service';
const PARTITION_FIELDS = ['partition_field', 'over_field', 'by_field'] as const;
diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/index.ts b/x-pack/plugins/ml/server/models/results_service/index.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/models/results_service/index.ts
rename to x-pack/plugins/ml/server/models/results_service/index.ts
diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.ts b/x-pack/plugins/ml/server/models/results_service/results_service.ts
similarity index 97%
rename from x-pack/legacy/plugins/ml/server/models/results_service/results_service.ts
rename to x-pack/plugins/ml/server/models/results_service/results_service.ts
index 555a58fbb53335..324cbb91ca8c10 100644
--- a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.ts
+++ b/x-pack/plugins/ml/server/models/results_service/results_service.ts
@@ -9,10 +9,10 @@ import moment from 'moment';
import { SearchResponse } from 'elasticsearch';
import { RequestHandlerContext } from 'kibana/server';
import { buildAnomalyTableItems, AnomaliesTableRecord } from './build_anomaly_table_items';
-import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns';
-import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search';
+import { ML_RESULTS_INDEX_PATTERN } from '../../../../../legacy/plugins/ml/common/constants/index_patterns';
+import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../../../legacy/plugins/ml/common/constants/search';
import { getPartitionFieldsValuesFactory } from './get_partition_fields_values';
-import { AnomalyRecordDoc } from '../../../common/types/anomalies';
+import { AnomalyRecordDoc } from '../../../../../legacy/plugins/ml/common/types/anomalies';
// Service for carrying out Elasticsearch queries to obtain data for the
// ML Results dashboards.
diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts
new file mode 100644
index 00000000000000..b5adf1fedec791
--- /dev/null
+++ b/x-pack/plugins/ml/server/plugin.ts
@@ -0,0 +1,168 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { CoreSetup, IScopedClusterClient, Logger, PluginInitializerContext } from 'src/core/server';
+import { LicenseCheckResult, PluginsSetup, RouteInitialization } from './types';
+import { PLUGIN_ID } from '../../../legacy/plugins/ml/common/constants/app';
+import { VALID_FULL_LICENSE_MODES } from '../../../legacy/plugins/ml/common/constants/license';
+
+// @ts-ignore: could not find declaration file for module
+import { elasticsearchJsPlugin } from './client/elasticsearch_ml';
+import { makeMlUsageCollector } from './lib/ml_telemetry';
+import { initMlServerLog } from './client/log';
+import { addLinksToSampleDatasets } from './lib/sample_data_sets';
+
+import { annotationRoutes } from './routes/annotations';
+import { calendars } from './routes/calendars';
+import { dataFeedRoutes } from './routes/datafeeds';
+import { dataFrameAnalyticsRoutes } from './routes/data_frame_analytics';
+import { dataRecognizer } from './routes/modules';
+import { dataVisualizerRoutes } from './routes/data_visualizer';
+import { fieldsService } from './routes/fields_service';
+import { fileDataVisualizerRoutes } from './routes/file_data_visualizer';
+import { filtersRoutes } from './routes/filters';
+import { indicesRoutes } from './routes/indices';
+import { jobAuditMessagesRoutes } from './routes/job_audit_messages';
+import { jobRoutes } from './routes/anomaly_detectors';
+import { jobServiceRoutes } from './routes/job_service';
+import { jobValidationRoutes } from './routes/job_validation';
+import { notificationRoutes } from './routes/notification_settings';
+import { resultsServiceRoutes } from './routes/results_service';
+import { systemRoutes } from './routes/system';
+
+declare module 'kibana/server' {
+ interface RequestHandlerContext {
+ ml?: {
+ mlClient: IScopedClusterClient;
+ };
+ }
+}
+
+export class MlServerPlugin {
+ private readonly pluginId: string = PLUGIN_ID;
+ private log: Logger;
+ private version: string;
+
+ private licenseCheckResults: LicenseCheckResult = {
+ isAvailable: false,
+ isActive: false,
+ isEnabled: false,
+ isSecurityDisabled: false,
+ };
+
+ constructor(ctx: PluginInitializerContext) {
+ this.log = ctx.logger.get();
+ this.version = ctx.env.packageInfo.branch;
+ }
+
+ public setup(coreSetup: CoreSetup, plugins: PluginsSetup) {
+ let sampleLinksInitialized = false;
+
+ plugins.features.registerFeature({
+ id: PLUGIN_ID,
+ name: i18n.translate('xpack.ml.featureRegistry.mlFeatureName', {
+ defaultMessage: 'Machine Learning',
+ }),
+ icon: 'machineLearningApp',
+ navLinkId: PLUGIN_ID,
+ app: [PLUGIN_ID, 'kibana'],
+ catalogue: [PLUGIN_ID],
+ privileges: {},
+ reserved: {
+ privilege: {
+ savedObject: {
+ all: [],
+ read: [],
+ },
+ ui: [],
+ },
+ description: i18n.translate('xpack.ml.feature.reserved.description', {
+ defaultMessage:
+ 'To grant users access, you should also assign either the machine_learning_user or machine_learning_admin role.',
+ }),
+ },
+ });
+
+ // Can access via router's handler function 'context' parameter - context.ml.mlClient
+ const mlClient = coreSetup.elasticsearch.createClient(PLUGIN_ID, {
+ plugins: [elasticsearchJsPlugin],
+ });
+
+ coreSetup.http.registerRouteHandlerContext(PLUGIN_ID, (context, request) => {
+ return {
+ mlClient: mlClient.asScoped(request),
+ };
+ });
+
+ const routeInit: RouteInitialization = {
+ router: coreSetup.http.createRouter(),
+ getLicenseCheckResults: () => this.licenseCheckResults,
+ };
+
+ annotationRoutes(routeInit, plugins.security);
+ calendars(routeInit);
+ dataFeedRoutes(routeInit);
+ dataFrameAnalyticsRoutes(routeInit);
+ dataRecognizer(routeInit);
+ dataVisualizerRoutes(routeInit);
+ fieldsService(routeInit);
+ fileDataVisualizerRoutes(routeInit);
+ filtersRoutes(routeInit);
+ indicesRoutes(routeInit);
+ jobAuditMessagesRoutes(routeInit);
+ jobRoutes(routeInit);
+ jobServiceRoutes(routeInit);
+ notificationRoutes(routeInit);
+ resultsServiceRoutes(routeInit);
+ jobValidationRoutes(routeInit, this.version);
+ systemRoutes(routeInit, {
+ spacesPlugin: plugins.spaces,
+ cloud: plugins.cloud,
+ });
+ initMlServerLog({ log: this.log });
+ coreSetup.getStartServices().then(([core]) => {
+ makeMlUsageCollector(plugins.usageCollection, core.savedObjects);
+ });
+
+ plugins.licensing.license$.subscribe(async license => {
+ const { isEnabled: securityIsEnabled } = license.getFeature('security');
+ // @ts-ignore isAvailable is not read
+ const { isAvailable, isEnabled } = license.getFeature(this.pluginId);
+
+ this.licenseCheckResults = {
+ isActive: license.isActive,
+ // This `isAvailable` check for the ml plugin returns false for a basic license
+ // ML should be available on basic with reduced functionality (only file data visualizer)
+ // TODO: This will need to be updated in the second step of this cutover to NP.
+ isAvailable: isEnabled,
+ isEnabled,
+ isSecurityDisabled: securityIsEnabled === false,
+ type: license.type,
+ };
+
+ if (sampleLinksInitialized === false) {
+ sampleLinksInitialized = true;
+ // Add links to the Kibana sample data sets if ml is enabled
+ // and license is trial or platinum.
+ if (isEnabled === true && plugins.home) {
+ if (
+ this.licenseCheckResults.type &&
+ VALID_FULL_LICENSE_MODES.includes(this.licenseCheckResults.type)
+ ) {
+ addLinksToSampleDatasets({
+ addAppLinksToSampleDataset: plugins.home.sampleData.addAppLinksToSampleDataset,
+ });
+ }
+ }
+ }
+ });
+ }
+
+ public start() {}
+
+ public stop() {}
+}
diff --git a/x-pack/legacy/plugins/ml/server/routes/README.md b/x-pack/plugins/ml/server/routes/README.md
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/routes/README.md
rename to x-pack/plugins/ml/server/routes/README.md
diff --git a/x-pack/legacy/plugins/ml/server/routes/annotations.ts b/x-pack/plugins/ml/server/routes/annotations.ts
similarity index 83%
rename from x-pack/legacy/plugins/ml/server/routes/annotations.ts
rename to x-pack/plugins/ml/server/routes/annotations.ts
index 20f52b4b051c4c..bcc0238c366a3d 100644
--- a/x-pack/legacy/plugins/ml/server/routes/annotations.ts
+++ b/x-pack/plugins/ml/server/routes/annotations.ts
@@ -9,18 +9,19 @@ import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
+import { SecurityPluginSetup } from '../../../security/server';
import { isAnnotationsFeatureAvailable } from '../lib/check_annotations';
import { annotationServiceProvider } from '../models/annotation_service';
import { wrapError } from '../client/error_wrapper';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
-import { RouteInitialization } from '../new_platform/plugin';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
+import { RouteInitialization } from '../types';
import {
deleteAnnotationSchema,
getAnnotationsSchema,
indexAnnotationSchema,
-} from '../new_platform/annotations_schema';
+} from './schemas/annotations_schema';
-import { ANNOTATION_USER_UNKNOWN } from '../../common/constants/annotations';
+import { ANNOTATION_USER_UNKNOWN } from '../../../../legacy/plugins/ml/common/constants/annotations';
function getAnnotationsFeatureUnavailableErrorMessage() {
return Boom.badRequest(
@@ -34,7 +35,10 @@ function getAnnotationsFeatureUnavailableErrorMessage() {
/**
* Routes for annotations
*/
-export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: RouteInitialization) {
+export function annotationRoutes(
+ { router, getLicenseCheckResults }: RouteInitialization,
+ securityPlugin: SecurityPluginSetup
+) {
/**
* @apiGroup Annotations
*
@@ -57,7 +61,7 @@ export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: Ro
body: schema.object(getAnnotationsSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { getAnnotations } = annotationServiceProvider(context);
const resp = await getAnnotations(request.body);
@@ -88,7 +92,7 @@ export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: Ro
body: schema.object(indexAnnotationSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const annotationsFeatureAvailable = await isAnnotationsFeatureAvailable(
context.ml!.mlClient.callAsCurrentUser
@@ -99,6 +103,7 @@ export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: Ro
const { indexAnnotation } = annotationServiceProvider(context);
const user = securityPlugin.authc.getCurrentUser(request) || {};
+ // @ts-ignore username doesn't exist on {}
const resp = await indexAnnotation(request.body, user.username || ANNOTATION_USER_UNKNOWN);
return response.ok({
@@ -126,7 +131,7 @@ export function annotationRoutes({ xpackMainPlugin, router, securityPlugin }: Ro
params: schema.object(deleteAnnotationSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const annotationsFeatureAvailable = await isAnnotationsFeatureAvailable(
context.ml!.mlClient.callAsCurrentUser
diff --git a/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts
similarity index 89%
rename from x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts
rename to x-pack/plugins/ml/server/routes/anomaly_detectors.ts
index 99dbdec9e945bd..7bf2fb7bc6903d 100644
--- a/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.ts
+++ b/x-pack/plugins/ml/server/routes/anomaly_detectors.ts
@@ -6,17 +6,17 @@
import { schema } from '@kbn/config-schema';
import { wrapError } from '../client/error_wrapper';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
-import { RouteInitialization } from '../new_platform/plugin';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
+import { RouteInitialization } from '../types';
import {
anomalyDetectionJobSchema,
anomalyDetectionUpdateJobSchema,
-} from '../new_platform/anomaly_detectors_schema';
+} from './schemas/anomaly_detectors_schema';
/**
* Routes for the anomaly detectors
*/
-export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function jobRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup AnomalyDetectors
*
@@ -32,7 +32,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
path: '/api/ml/anomaly_detectors',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser('ml.jobs');
return response.ok({
@@ -62,7 +62,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { jobId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.jobs', { jobId });
@@ -90,7 +90,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
path: '/api/ml/anomaly_detectors/_stats',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser('ml.jobStats');
return response.ok({
@@ -120,7 +120,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { jobId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.jobStats', { jobId });
@@ -152,7 +152,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
body: schema.object({ ...anomalyDetectionJobSchema }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { jobId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.addJob', {
@@ -187,7 +187,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
body: schema.object({ ...anomalyDetectionUpdateJobSchema }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { jobId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.updateJob', {
@@ -221,7 +221,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { jobId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.openJob', {
@@ -254,7 +254,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const options: { jobId: string; force?: boolean } = {
jobId: request.params.jobId,
@@ -291,7 +291,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const options: { jobId: string; force?: boolean } = {
jobId: request.params.jobId,
@@ -326,7 +326,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
body: schema.any(),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser('ml.validateDetector', {
body: request.body,
@@ -359,7 +359,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
body: schema.object({ duration: schema.any() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const jobId = request.params.jobId;
const duration = request.body.duration;
@@ -407,7 +407,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser('ml.records', {
jobId: request.params.jobId,
@@ -456,7 +456,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser('ml.buckets', {
jobId: request.params.jobId,
@@ -499,7 +499,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser('ml.overallBuckets', {
jobId: request.params.jobId,
@@ -537,7 +537,7 @@ export function jobRoutes({ xpackMainPlugin, router }: RouteInitialization) {
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const options = {
jobId: request.params.jobId,
diff --git a/x-pack/legacy/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/routes/apidoc.json
rename to x-pack/plugins/ml/server/routes/apidoc.json
diff --git a/x-pack/legacy/plugins/ml/server/routes/calendars.ts b/x-pack/plugins/ml/server/routes/calendars.ts
similarity index 84%
rename from x-pack/legacy/plugins/ml/server/routes/calendars.ts
rename to x-pack/plugins/ml/server/routes/calendars.ts
index 8e4e1c4c14751d..ae494d3578890b 100644
--- a/x-pack/legacy/plugins/ml/server/routes/calendars.ts
+++ b/x-pack/plugins/ml/server/routes/calendars.ts
@@ -6,10 +6,10 @@
import { RequestHandlerContext } from 'src/core/server';
import { schema } from '@kbn/config-schema';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
-import { calendarSchema } from '../new_platform/calendars_schema';
+import { RouteInitialization } from '../types';
+import { calendarSchema } from './schemas/calendars_schema';
import { CalendarManager, Calendar, FormCalendar } from '../models/calendar';
function getAllCalendars(context: RequestHandlerContext) {
@@ -42,13 +42,13 @@ function getCalendarsByIds(context: RequestHandlerContext, calendarIds: string)
return cal.getCalendarsByIds(calendarIds);
}
-export function calendars({ xpackMainPlugin, router }: RouteInitialization) {
+export function calendars({ router, getLicenseCheckResults }: RouteInitialization) {
router.get(
{
path: '/api/ml/calendars',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getAllCalendars(context);
@@ -68,7 +68,7 @@ export function calendars({ xpackMainPlugin, router }: RouteInitialization) {
params: schema.object({ calendarIds: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
let returnValue;
try {
const calendarIds = request.params.calendarIds.split(',');
@@ -95,7 +95,7 @@ export function calendars({ xpackMainPlugin, router }: RouteInitialization) {
body: schema.object({ ...calendarSchema }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const body = request.body;
const resp = await newCalendar(context, body);
@@ -117,7 +117,7 @@ export function calendars({ xpackMainPlugin, router }: RouteInitialization) {
body: schema.object({ ...calendarSchema }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { calendarId } = request.params;
const body = request.body;
@@ -139,7 +139,7 @@ export function calendars({ xpackMainPlugin, router }: RouteInitialization) {
params: schema.object({ calendarId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { calendarId } = request.params;
const resp = await deleteCalendar(context, calendarId);
diff --git a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts
similarity index 89%
rename from x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts
rename to x-pack/plugins/ml/server/routes/data_frame_analytics.ts
index 6541fa541a59ff..0a93320c05eb5b 100644
--- a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts
+++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts
@@ -7,18 +7,18 @@
import { schema } from '@kbn/config-schema';
import { wrapError } from '../client/error_wrapper';
import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
-import { RouteInitialization } from '../new_platform/plugin';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
+import { RouteInitialization } from '../types';
import {
dataAnalyticsJobConfigSchema,
dataAnalyticsEvaluateSchema,
dataAnalyticsExplainSchema,
-} from '../new_platform/data_analytics_schema';
+} from './schemas/data_analytics_schema';
/**
* Routes for the data frame analytics
*/
-export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function dataFrameAnalyticsRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup DataFrameAnalytics
*
@@ -36,7 +36,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
params: schema.object({ analyticsId: schema.maybe(schema.string()) }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser('ml.getDataFrameAnalytics');
return response.ok({
@@ -64,7 +64,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
params: schema.object({ analyticsId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.getDataFrameAnalytics', {
@@ -91,7 +91,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
path: '/api/ml/data_frame/analytics/_stats',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.getDataFrameAnalyticsStats'
@@ -121,7 +121,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
params: schema.object({ analyticsId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser(
@@ -159,7 +159,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
body: schema.object(dataAnalyticsJobConfigSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser(
@@ -192,7 +192,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
body: schema.object({ ...dataAnalyticsEvaluateSchema }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.evaluateDataFrameAnalytics',
@@ -232,7 +232,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
body: schema.object({ ...dataAnalyticsExplainSchema }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const results = await context.ml!.mlClient.callAsCurrentUser(
'ml.explainDataFrameAnalytics',
@@ -267,7 +267,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser(
@@ -303,7 +303,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const results = await context.ml!.mlClient.callAsCurrentUser('ml.startDataFrameAnalytics', {
@@ -337,7 +337,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const options: { analyticsId: string; force?: boolean | undefined } = {
analyticsId: request.params.analyticsId,
@@ -377,7 +377,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti
params: schema.object({ analyticsId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { analyticsId } = request.params;
const { getAnalyticsAuditMessages } = analyticsAuditMessagesProvider(
diff --git a/x-pack/legacy/plugins/ml/server/routes/data_visualizer.ts b/x-pack/plugins/ml/server/routes/data_visualizer.ts
similarity index 89%
rename from x-pack/legacy/plugins/ml/server/routes/data_visualizer.ts
rename to x-pack/plugins/ml/server/routes/data_visualizer.ts
index df7e4b70108777..e4d068784def1b 100644
--- a/x-pack/legacy/plugins/ml/server/routes/data_visualizer.ts
+++ b/x-pack/plugins/ml/server/routes/data_visualizer.ts
@@ -11,9 +11,9 @@ import { Field } from '../models/data_visualizer/data_visualizer';
import {
dataVisualizerFieldStatsSchema,
dataVisualizerOverallStatsSchema,
-} from '../new_platform/data_visualizer_schema';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
-import { RouteInitialization } from '../new_platform/plugin';
+} from './schemas/data_visualizer_schema';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
+import { RouteInitialization } from '../types';
function getOverallStats(
context: RequestHandlerContext,
@@ -68,7 +68,7 @@ function getStatsForFields(
/**
* Routes for the index data visualizer.
*/
-export function dataVisualizerRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function dataVisualizerRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup DataVisualizer
*
@@ -83,7 +83,7 @@ export function dataVisualizerRoutes({ xpackMainPlugin, router }: RouteInitializ
path: '/api/ml/data_visualizer/get_field_stats/{indexPatternTitle}',
validate: dataVisualizerFieldStatsSchema,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const {
params: { indexPatternTitle },
@@ -135,7 +135,7 @@ export function dataVisualizerRoutes({ xpackMainPlugin, router }: RouteInitializ
path: '/api/ml/data_visualizer/get_overall_stats/{indexPatternTitle}',
validate: dataVisualizerOverallStatsSchema,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const {
params: { indexPatternTitle },
diff --git a/x-pack/legacy/plugins/ml/server/routes/datafeeds.ts b/x-pack/plugins/ml/server/routes/datafeeds.ts
similarity index 86%
rename from x-pack/legacy/plugins/ml/server/routes/datafeeds.ts
rename to x-pack/plugins/ml/server/routes/datafeeds.ts
index 9335403616cf7d..e3bce4c1328e4e 100644
--- a/x-pack/legacy/plugins/ml/server/routes/datafeeds.ts
+++ b/x-pack/plugins/ml/server/routes/datafeeds.ts
@@ -5,15 +5,15 @@
*/
import { schema } from '@kbn/config-schema';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
-import { startDatafeedSchema, datafeedConfigSchema } from '../new_platform/datafeeds_schema';
+import { RouteInitialization } from '../types';
+import { startDatafeedSchema, datafeedConfigSchema } from './schemas/datafeeds_schema';
/**
* Routes for datafeed service
*/
-export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function dataFeedRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup DatafeedService
*
@@ -26,7 +26,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
path: '/api/ml/datafeeds',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeeds');
@@ -53,7 +53,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
params: schema.object({ datafeedId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const datafeedId = request.params.datafeedId;
const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeeds', { datafeedId });
@@ -79,7 +79,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
path: '/api/ml/datafeeds/_stats',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedStats');
@@ -106,7 +106,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
params: schema.object({ datafeedId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const datafeedId = request.params.datafeedId;
const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedStats', {
@@ -137,7 +137,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
body: datafeedConfigSchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const datafeedId = request.params.datafeedId;
const resp = await context.ml!.mlClient.callAsCurrentUser('ml.addDatafeed', {
@@ -169,7 +169,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
body: datafeedConfigSchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const datafeedId = request.params.datafeedId;
const resp = await context.ml!.mlClient.callAsCurrentUser('ml.updateDatafeed', {
@@ -201,7 +201,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
query: schema.maybe(schema.object({ force: schema.maybe(schema.any()) })),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const options: { datafeedId: string; force?: boolean } = {
datafeedId: request.params.jobId,
@@ -237,7 +237,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
body: startDatafeedSchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const datafeedId = request.params.datafeedId;
const { start, end } = request.body;
@@ -271,7 +271,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
params: schema.object({ datafeedId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const datafeedId = request.params.datafeedId;
@@ -302,7 +302,7 @@ export function dataFeedRoutes({ xpackMainPlugin, router }: RouteInitialization)
params: schema.object({ datafeedId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const datafeedId = request.params.datafeedId;
const resp = await context.ml!.mlClient.callAsCurrentUser('ml.datafeedPreview', {
diff --git a/x-pack/legacy/plugins/ml/server/routes/fields_service.ts b/x-pack/plugins/ml/server/routes/fields_service.ts
similarity index 84%
rename from x-pack/legacy/plugins/ml/server/routes/fields_service.ts
rename to x-pack/plugins/ml/server/routes/fields_service.ts
index 4827adf23d7b46..bc092190c2c620 100644
--- a/x-pack/legacy/plugins/ml/server/routes/fields_service.ts
+++ b/x-pack/plugins/ml/server/routes/fields_service.ts
@@ -5,13 +5,13 @@
*/
import { RequestHandlerContext } from 'src/core/server';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
+import { RouteInitialization } from '../types';
import {
getCardinalityOfFieldsSchema,
getTimeFieldRangeSchema,
-} from '../new_platform/fields_service_schema';
+} from './schemas/fields_service_schema';
import { fieldsServiceProvider } from '../models/fields_service';
function getCardinalityOfFields(context: RequestHandlerContext, payload: any) {
@@ -29,7 +29,7 @@ function getTimeFieldRange(context: RequestHandlerContext, payload: any) {
/**
* Routes for fields service
*/
-export function fieldsService({ xpackMainPlugin, router }: RouteInitialization) {
+export function fieldsService({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup FieldsService
*
@@ -44,7 +44,7 @@ export function fieldsService({ xpackMainPlugin, router }: RouteInitialization)
body: getCardinalityOfFieldsSchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getCardinalityOfFields(context, request.body);
@@ -71,7 +71,7 @@ export function fieldsService({ xpackMainPlugin, router }: RouteInitialization)
body: getTimeFieldRangeSchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getTimeFieldRange(context, request.body);
diff --git a/x-pack/legacy/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts
similarity index 87%
rename from x-pack/legacy/plugins/ml/server/routes/file_data_visualizer.ts
rename to x-pack/plugins/ml/server/routes/file_data_visualizer.ts
index d5a992c9332930..1d724a8843350d 100644
--- a/x-pack/legacy/plugins/ml/server/routes/file_data_visualizer.ts
+++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts
@@ -6,7 +6,7 @@
import { schema } from '@kbn/config-schema';
import { RequestHandlerContext } from 'kibana/server';
-import { MAX_BYTES } from '../../common/constants/file_datavisualizer';
+import { MAX_BYTES } from '../../../../legacy/plugins/ml/common/constants/file_datavisualizer';
import { wrapError } from '../client/error_wrapper';
import {
InputOverrides,
@@ -18,8 +18,8 @@ import {
Mappings,
} from '../models/file_data_visualizer';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
-import { RouteInitialization } from '../new_platform/plugin';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
+import { RouteInitialization } from '../types';
import { incrementFileDataVisualizerIndexCreationCount } from '../lib/ml_telemetry';
function analyzeFiles(context: RequestHandlerContext, data: InputData, overrides: InputOverrides) {
@@ -43,12 +43,7 @@ function importData(
/**
* Routes for the file data visualizer.
*/
-export function fileDataVisualizerRoutes({
- router,
- xpackMainPlugin,
- savedObjects,
- elasticsearchPlugin,
-}: RouteInitialization) {
+export function fileDataVisualizerRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup FileDataVisualizer
*
@@ -87,7 +82,7 @@ export function fileDataVisualizerRoutes({
},
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const result = await analyzeFiles(context, request.body, request.query);
return response.ok({ body: result });
@@ -129,7 +124,7 @@ export function fileDataVisualizerRoutes({
},
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { id } = request.query;
const { index, data, settings, mappings, ingestPipeline } = request.body;
@@ -138,7 +133,8 @@ export function fileDataVisualizerRoutes({
// follow-up import calls to just add additional data will include the `id` of the created
// index, we'll ignore those and don't increment the counter.
if (id === undefined) {
- await incrementFileDataVisualizerIndexCreationCount(savedObjects!);
+ // @ts-ignore
+ await incrementFileDataVisualizerIndexCreationCount(context.core.savedObjects.client);
}
const result = await importData(
diff --git a/x-pack/legacy/plugins/ml/server/routes/filters.ts b/x-pack/plugins/ml/server/routes/filters.ts
similarity index 87%
rename from x-pack/legacy/plugins/ml/server/routes/filters.ts
rename to x-pack/plugins/ml/server/routes/filters.ts
index a06f8d4f8b727b..d5530668b26062 100644
--- a/x-pack/legacy/plugins/ml/server/routes/filters.ts
+++ b/x-pack/plugins/ml/server/routes/filters.ts
@@ -6,10 +6,10 @@
import { RequestHandlerContext } from 'src/core/server';
import { schema } from '@kbn/config-schema';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
-import { createFilterSchema, updateFilterSchema } from '../new_platform/filters_schema';
+import { RouteInitialization } from '../types';
+import { createFilterSchema, updateFilterSchema } from './schemas/filters_schema';
import { FilterManager, FormFilter } from '../models/filter';
// TODO - add function for returning a list of just the filter IDs.
@@ -44,7 +44,7 @@ function deleteFilter(context: RequestHandlerContext, filterId: string) {
return mgr.deleteFilter(filterId);
}
-export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function filtersRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup Filters
*
@@ -60,7 +60,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization)
path: '/api/ml/filters',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getAllFilters(context);
@@ -90,7 +90,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization)
params: schema.object({ filterId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getFilter(context, request.params.filterId);
return response.ok({
@@ -119,7 +119,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization)
body: schema.object(createFilterSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const body = request.body;
const resp = await newFilter(context, body);
@@ -151,7 +151,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization)
body: schema.object(updateFilterSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { filterId } = request.params;
const body = request.body;
@@ -182,7 +182,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization)
params: schema.object({ filterId: schema.string() }),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { filterId } = request.params;
const resp = await deleteFilter(context, filterId);
@@ -212,7 +212,7 @@ export function filtersRoutes({ xpackMainPlugin, router }: RouteInitialization)
path: '/api/ml/filters/_stats',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getAllFilterStats(context);
diff --git a/x-pack/legacy/plugins/ml/server/routes/indices.ts b/x-pack/plugins/ml/server/routes/indices.ts
similarity index 80%
rename from x-pack/legacy/plugins/ml/server/routes/indices.ts
rename to x-pack/plugins/ml/server/routes/indices.ts
index 0ee15f1321e9c2..e01a7a0cbad287 100644
--- a/x-pack/legacy/plugins/ml/server/routes/indices.ts
+++ b/x-pack/plugins/ml/server/routes/indices.ts
@@ -6,13 +6,13 @@
import { schema } from '@kbn/config-schema';
import { wrapError } from '../client/error_wrapper';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
-import { RouteInitialization } from '../new_platform/plugin';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
+import { RouteInitialization } from '../types';
/**
* Indices routes.
*/
-export function indicesRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function indicesRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup Indices
*
@@ -30,7 +30,7 @@ export function indicesRoutes({ xpackMainPlugin, router }: RouteInitialization)
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const {
body: { index, fields: requestFields },
diff --git a/x-pack/legacy/plugins/ml/server/routes/job_audit_messages.ts b/x-pack/plugins/ml/server/routes/job_audit_messages.ts
similarity index 84%
rename from x-pack/legacy/plugins/ml/server/routes/job_audit_messages.ts
rename to x-pack/plugins/ml/server/routes/job_audit_messages.ts
index 76986b935b993a..38df28e17ec0d6 100644
--- a/x-pack/legacy/plugins/ml/server/routes/job_audit_messages.ts
+++ b/x-pack/plugins/ml/server/routes/job_audit_messages.ts
@@ -5,15 +5,15 @@
*/
import { schema } from '@kbn/config-schema';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
+import { RouteInitialization } from '../types';
import { jobAuditMessagesProvider } from '../models/job_audit_messages';
/**
* Routes for job audit message routes
*/
-export function jobAuditMessagesRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function jobAuditMessagesRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup JobAuditMessages
*
@@ -29,7 +29,7 @@ export function jobAuditMessagesRoutes({ xpackMainPlugin, router }: RouteInitial
query: schema.maybe(schema.object({ from: schema.maybe(schema.any()) })),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { getJobAuditMessages } = jobAuditMessagesProvider(
context.ml!.mlClient.callAsCurrentUser
@@ -62,7 +62,7 @@ export function jobAuditMessagesRoutes({ xpackMainPlugin, router }: RouteInitial
query: schema.maybe(schema.object({ from: schema.maybe(schema.any()) })),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { getJobAuditMessages } = jobAuditMessagesProvider(
context.ml!.mlClient.callAsCurrentUser
diff --git a/x-pack/legacy/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts
similarity index 88%
rename from x-pack/legacy/plugins/ml/server/routes/job_service.ts
rename to x-pack/plugins/ml/server/routes/job_service.ts
index 5ddbd4cdfd5a5d..e15888088d3a13 100644
--- a/x-pack/legacy/plugins/ml/server/routes/job_service.ts
+++ b/x-pack/plugins/ml/server/routes/job_service.ts
@@ -7,10 +7,9 @@
import Boom from 'boom';
import { schema } from '@kbn/config-schema';
import { IScopedClusterClient } from 'src/core/server';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
-import { isSecurityDisabled } from '../lib/security_utils';
+import { RouteInitialization } from '../types';
import {
categorizationFieldExamplesSchema,
chartSchema,
@@ -21,7 +20,7 @@ import {
lookBackProgressSchema,
topCategoriesSchema,
updateGroupsSchema,
-} from '../new_platform/job_service_schema';
+} from './schemas/job_service_schema';
// @ts-ignore no declaration module
import { jobServiceProvider } from '../models/job_service';
import { categorizationExamplesProvider } from '../models/job_service/new_job';
@@ -29,11 +28,12 @@ import { categorizationExamplesProvider } from '../models/job_service/new_job';
/**
* Routes for job service
*/
-export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function jobServiceRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
async function hasPermissionToCreateJobs(
callAsCurrentUser: IScopedClusterClient['callAsCurrentUser']
) {
- if (isSecurityDisabled(xpackMainPlugin) === true) {
+ const { isSecurityDisabled } = getLicenseCheckResults();
+ if (isSecurityDisabled === true) {
return true;
}
@@ -63,7 +63,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(forceStartDatafeedSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { forceStartDatafeeds } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { datafeedIds, start, end } = request.body;
@@ -92,7 +92,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(datafeedIdsSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { stopDatafeeds } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { datafeedIds } = request.body;
@@ -121,7 +121,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(jobIdsSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { deleteJobs } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { jobIds } = request.body;
@@ -150,7 +150,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(jobIdsSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { closeJobs } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { jobIds } = request.body;
@@ -179,7 +179,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(jobIdsSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { jobsSummary } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { jobIds } = request.body;
@@ -208,7 +208,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(jobsWithTimerangeSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { jobsWithTimerange } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { dateFormatTz } = request.body;
@@ -237,7 +237,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(jobIdsSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { createFullJobsList } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { jobIds } = request.body;
@@ -264,7 +264,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
path: '/api/ml/jobs/groups',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { getAllGroups } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const resp = await getAllGroups();
@@ -292,7 +292,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(updateGroupsSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { updateGroups } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { jobs } = request.body;
@@ -319,7 +319,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
path: '/api/ml/jobs/deleting_jobs_tasks',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { deletingJobTasks } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const resp = await deletingJobTasks();
@@ -347,7 +347,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(jobIdsSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { jobsExist } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { jobIds } = request.body;
@@ -377,7 +377,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
query: schema.maybe(schema.object({ rollup: schema.maybe(schema.string()) })),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { indexPattern } = request.params;
const isRollup = request.query.rollup === 'true';
@@ -408,7 +408,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(chartSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const {
indexPatternTitle,
@@ -461,7 +461,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(chartSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const {
indexPatternTitle,
@@ -509,7 +509,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
path: '/api/ml/jobs/all_jobs_and_group_ids',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { getAllJobAndGroupIds } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const resp = await getAllJobAndGroupIds();
@@ -537,7 +537,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(lookBackProgressSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { getLookBackProgress } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { jobId, start, end } = request.body;
@@ -566,7 +566,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(categorizationFieldExamplesSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
// due to the use of the _analyze endpoint which is called by the kibana user,
// basic job creation privileges are required to use this endpoint
@@ -625,7 +625,7 @@ export function jobServiceRoutes({ xpackMainPlugin, router }: RouteInitializatio
body: schema.object(topCategoriesSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { topCategories } = jobServiceProvider(context.ml!.mlClient.callAsCurrentUser);
const { jobId, count } = request.body;
diff --git a/x-pack/legacy/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts
similarity index 85%
rename from x-pack/legacy/plugins/ml/server/routes/job_validation.ts
rename to x-pack/plugins/ml/server/routes/job_validation.ts
index 64c9ccd27720a8..ae2e6885ba0f3b 100644
--- a/x-pack/legacy/plugins/ml/server/routes/job_validation.ts
+++ b/x-pack/plugins/ml/server/routes/job_validation.ts
@@ -7,15 +7,15 @@
import Boom from 'boom';
import { RequestHandlerContext } from 'src/core/server';
import { schema, TypeOf } from '@kbn/config-schema';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
+import { RouteInitialization } from '../types';
import {
estimateBucketSpanSchema,
modelMemoryLimitSchema,
validateCardinalitySchema,
validateJobSchema,
-} from '../new_platform/job_validation_schema';
+} from './schemas/job_validation_schema';
import { estimateBucketSpanFactory } from '../models/bucket_span_estimator';
import { calculateModelMemoryLimitProvider } from '../models/calculate_model_memory_limit';
import { validateJob, validateCardinality } from '../models/job_validation';
@@ -25,7 +25,10 @@ type CalculateModelMemoryLimitPayload = TypeOf;
/**
* Routes for job validation
*/
-export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteInitialization) {
+export function jobValidationRoutes(
+ { getLicenseCheckResults, router }: RouteInitialization,
+ version: string
+) {
function calculateModelMemoryLimit(
context: RequestHandlerContext,
payload: CalculateModelMemoryLimitPayload
@@ -67,13 +70,13 @@ export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteIn
body: estimateBucketSpanSchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
let errorResp;
const resp = await estimateBucketSpanFactory(
context.ml!.mlClient.callAsCurrentUser,
context.core.elasticsearch.adminClient.callAsInternalUser,
- xpackMainPlugin
+ getLicenseCheckResults().isSecurityDisabled
)(request.body)
// this catch gets triggered when the estimation code runs without error
// but isn't able to come up with a bucket span estimation.
@@ -114,7 +117,7 @@ export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteIn
body: modelMemoryLimitSchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await calculateModelMemoryLimit(context, request.body);
@@ -141,7 +144,7 @@ export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteIn
body: schema.object(validateCardinalitySchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await validateCardinality(
context.ml!.mlClient.callAsCurrentUser,
@@ -171,16 +174,15 @@ export function jobValidationRoutes({ config, xpackMainPlugin, router }: RouteIn
body: validateJobSchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
- // pkg.branch corresponds to the version used in documentation links.
- const version = config.get('pkg.branch');
+ // version corresponds to the version used in documentation links.
const resp = await validateJob(
context.ml!.mlClient.callAsCurrentUser,
request.body,
version,
context.core.elasticsearch.adminClient.callAsInternalUser,
- xpackMainPlugin
+ getLicenseCheckResults().isSecurityDisabled
);
return response.ok({
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/licence_check_pre_routing_factory.ts b/x-pack/plugins/ml/server/routes/license_check_pre_routing_factory.ts
similarity index 71%
rename from x-pack/legacy/plugins/ml/server/new_platform/licence_check_pre_routing_factory.ts
rename to x-pack/plugins/ml/server/routes/license_check_pre_routing_factory.ts
index cc77d2872fb909..a371af1abf2d13 100644
--- a/x-pack/legacy/plugins/ml/server/new_platform/licence_check_pre_routing_factory.ts
+++ b/x-pack/plugins/ml/server/routes/license_check_pre_routing_factory.ts
@@ -10,10 +10,10 @@ import {
RequestHandler,
RequestHandlerContext,
} from 'src/core/server';
-import { PLUGIN_ID, MlXpackMainPlugin } from './plugin';
+import { LicenseCheckResult } from '../types';
export const licensePreRoutingFactory = (
- xpackMainPlugin: MlXpackMainPlugin,
+ getLicenseCheckResults: () => LicenseCheckResult,
handler: RequestHandler
): RequestHandler => {
// License checking and enable/disable logic
@@ -22,14 +22,10 @@ export const licensePreRoutingFactory = (
request: KibanaRequest,
response: KibanaResponseFactory
) {
- const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN_ID).getLicenseCheckResults();
+ const licenseCheckResults = getLicenseCheckResults();
if (!licenseCheckResults.isAvailable) {
- return response.forbidden({
- body: {
- message: licenseCheckResults.message,
- },
- });
+ return response.forbidden();
}
return handler(ctx, request, response);
diff --git a/x-pack/legacy/plugins/ml/server/routes/modules.ts b/x-pack/plugins/ml/server/routes/modules.ts
similarity index 88%
rename from x-pack/legacy/plugins/ml/server/routes/modules.ts
rename to x-pack/plugins/ml/server/routes/modules.ts
index a40fb1c9149ca1..c9b005d4e43f92 100644
--- a/x-pack/legacy/plugins/ml/server/routes/modules.ts
+++ b/x-pack/plugins/ml/server/routes/modules.ts
@@ -6,12 +6,12 @@
import { schema } from '@kbn/config-schema';
import { RequestHandlerContext } from 'kibana/server';
-import { DatafeedOverride, JobOverride } from '../../common/types/modules';
+import { DatafeedOverride, JobOverride } from '../../../../legacy/plugins/ml/common/types/modules';
import { wrapError } from '../client/error_wrapper';
import { DataRecognizer } from '../models/data_recognizer';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
-import { getModuleIdParamSchema, setupModuleBodySchema } from '../new_platform/modules';
-import { RouteInitialization } from '../new_platform/plugin';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
+import { getModuleIdParamSchema, setupModuleBodySchema } from './schemas/modules';
+import { RouteInitialization } from '../types';
function recognize(context: RequestHandlerContext, indexPatternTitle: string) {
const dr = new DataRecognizer(context);
@@ -65,7 +65,7 @@ function dataRecognizerJobsExist(context: RequestHandlerContext, moduleId: strin
/**
* Recognizer routes.
*/
-export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization) {
+export function dataRecognizer({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup DataRecognizer
*
@@ -84,7 +84,7 @@ export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization)
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { indexPatternTitle } = request.params;
const results = await recognize(context, indexPatternTitle);
@@ -114,7 +114,7 @@ export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization)
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
let { moduleId } = request.params;
if (moduleId === '') {
@@ -150,7 +150,7 @@ export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization)
body: setupModuleBodySchema,
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { moduleId } = request.params;
@@ -207,7 +207,7 @@ export function dataRecognizer({ xpackMainPlugin, router }: RouteInitialization)
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const { moduleId } = request.params;
const result = await dataRecognizerJobsExist(context, moduleId);
diff --git a/x-pack/legacy/plugins/ml/server/routes/notification_settings.ts b/x-pack/plugins/ml/server/routes/notification_settings.ts
similarity index 75%
rename from x-pack/legacy/plugins/ml/server/routes/notification_settings.ts
rename to x-pack/plugins/ml/server/routes/notification_settings.ts
index c65627543b21d8..b68d2441333f93 100644
--- a/x-pack/legacy/plugins/ml/server/routes/notification_settings.ts
+++ b/x-pack/plugins/ml/server/routes/notification_settings.ts
@@ -4,14 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
+import { RouteInitialization } from '../types';
/**
* Routes for notification settings
*/
-export function notificationRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function notificationRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup NotificationSettings
*
@@ -24,7 +24,7 @@ export function notificationRoutes({ xpackMainPlugin, router }: RouteInitializat
path: '/api/ml/notification_settings',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const params = {
includeDefaults: true,
diff --git a/x-pack/legacy/plugins/ml/server/routes/results_service.ts b/x-pack/plugins/ml/server/routes/results_service.ts
similarity index 88%
rename from x-pack/legacy/plugins/ml/server/routes/results_service.ts
rename to x-pack/plugins/ml/server/routes/results_service.ts
index 5d107b2d978090..77c998acc9f27a 100644
--- a/x-pack/legacy/plugins/ml/server/routes/results_service.ts
+++ b/x-pack/plugins/ml/server/routes/results_service.ts
@@ -6,16 +6,16 @@
import { RequestHandlerContext } from 'src/core/server';
import { schema } from '@kbn/config-schema';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
import { wrapError } from '../client/error_wrapper';
-import { RouteInitialization } from '../new_platform/plugin';
+import { RouteInitialization } from '../types';
import {
anomaliesTableDataSchema,
categoryDefinitionSchema,
categoryExamplesSchema,
maxAnomalyScoreSchema,
partitionFieldValuesSchema,
-} from '../new_platform/results_service_schema';
+} from './schemas/results_service_schema';
import { resultsServiceProvider } from '../models/results_service';
function getAnomaliesTableData(context: RequestHandlerContext, payload: any) {
@@ -74,7 +74,7 @@ function getPartitionFieldsValues(context: RequestHandlerContext, payload: any)
/**
* Routes for results service
*/
-export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitialization) {
+export function resultsServiceRoutes({ router, getLicenseCheckResults }: RouteInitialization) {
/**
* @apiGroup ResultsService
*
@@ -89,7 +89,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ
body: schema.object(anomaliesTableDataSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getAnomaliesTableData(context, request.body);
@@ -116,7 +116,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ
body: schema.object(categoryDefinitionSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getCategoryDefinition(context, request.body);
@@ -143,7 +143,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ
body: schema.object(maxAnomalyScoreSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getMaxAnomalyScore(context, request.body);
@@ -170,7 +170,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ
body: schema.object(categoryExamplesSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getCategoryExamples(context, request.body);
@@ -197,7 +197,7 @@ export function resultsServiceRoutes({ xpackMainPlugin, router }: RouteInitializ
body: schema.object(partitionFieldValuesSchema),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const resp = await getPartitionFieldsValues(context, request.body);
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/annotations_schema.ts b/x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/annotations_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/annotations_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/anomaly_detectors_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/calendars_schema.ts b/x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/calendars_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/calendars_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/data_analytics_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/data_analytics_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/data_visualizer_schema.ts b/x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/data_visualizer_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/data_visualizer_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/datafeeds_schema.ts b/x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/datafeeds_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/datafeeds_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/fields_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/fields_service_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/fields_service_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/filters_schema.ts b/x-pack/plugins/ml/server/routes/schemas/filters_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/filters_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/filters_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/job_service_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/job_validation_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/job_validation_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/job_validation_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/modules.ts b/x-pack/plugins/ml/server/routes/schemas/modules.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/modules.ts
rename to x-pack/plugins/ml/server/routes/schemas/modules.ts
diff --git a/x-pack/legacy/plugins/ml/server/new_platform/results_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts
similarity index 100%
rename from x-pack/legacy/plugins/ml/server/new_platform/results_service_schema.ts
rename to x-pack/plugins/ml/server/routes/schemas/results_service_schema.ts
diff --git a/x-pack/legacy/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts
similarity index 88%
rename from x-pack/legacy/plugins/ml/server/routes/system.ts
rename to x-pack/plugins/ml/server/routes/system.ts
index 5861b53d74875c..36a9ea1447f583 100644
--- a/x-pack/legacy/plugins/ml/server/routes/system.ts
+++ b/x-pack/plugins/ml/server/routes/system.ts
@@ -11,20 +11,17 @@ import { RequestHandlerContext } from 'kibana/server';
import { wrapError } from '../client/error_wrapper';
import { mlLog } from '../client/log';
import { privilegesProvider } from '../lib/check_privileges';
-import { isSecurityDisabled } from '../lib/security_utils';
import { spacesUtilsProvider } from '../lib/spaces_utils';
-import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory';
-import { RouteInitialization } from '../new_platform/plugin';
+import { licensePreRoutingFactory } from './license_check_pre_routing_factory';
+import { RouteInitialization, SystemRouteDeps } from '../types';
/**
* System routes
*/
-export function systemRoutes({
- router,
- xpackMainPlugin,
- spacesPlugin,
- cloud,
-}: RouteInitialization) {
+export function systemRoutes(
+ { getLicenseCheckResults, router }: RouteInitialization,
+ { spacesPlugin, cloud }: SystemRouteDeps
+) {
async function getNodeCount(context: RequestHandlerContext) {
const filterPath = 'nodes.*.attributes';
const resp = await context.ml!.mlClient.callAsInternalUser('nodes.info', {
@@ -59,7 +56,7 @@ export function systemRoutes({
body: schema.maybe(schema.any()),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
let upgradeInProgress = false;
try {
@@ -80,7 +77,7 @@ export function systemRoutes({
}
}
- if (isSecurityDisabled(xpackMainPlugin)) {
+ if (getLicenseCheckResults().isSecurityDisabled) {
// if xpack.security.enabled has been explicitly set to false
// return that security is disabled and don't call the privilegeCheck endpoint
return response.ok({
@@ -119,7 +116,7 @@ export function systemRoutes({
}),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const ignoreSpaces = request.query && request.query.ignoreSpaces === 'true';
// if spaces is disabled force isMlEnabledInSpace to be true
@@ -130,7 +127,7 @@ export function systemRoutes({
const { getPrivileges } = privilegesProvider(
context.ml!.mlClient.callAsCurrentUser,
- xpackMainPlugin,
+ getLicenseCheckResults(),
isMlEnabledInSpace,
ignoreSpaces
);
@@ -155,11 +152,11 @@ export function systemRoutes({
path: '/api/ml/ml_node_count',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
// check for basic license first for consistency with other
// security disabled checks
- if (isSecurityDisabled(xpackMainPlugin)) {
+ if (getLicenseCheckResults().isSecurityDisabled) {
return response.ok({
body: await getNodeCount(context),
});
@@ -206,7 +203,7 @@ export function systemRoutes({
path: '/api/ml/info',
validate: false,
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
const info = await context.ml!.mlClient.callAsCurrentUser('ml.info');
const cloudId = cloud && cloud.cloudId;
@@ -234,7 +231,7 @@ export function systemRoutes({
body: schema.maybe(schema.any()),
},
},
- licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => {
+ licensePreRoutingFactory(getLicenseCheckResults, async (context, request, response) => {
try {
return response.ok({
body: await context.ml!.mlClient.callAsCurrentUser('search', request.body),
diff --git a/x-pack/plugins/ml/server/types.ts b/x-pack/plugins/ml/server/types.ts
new file mode 100644
index 00000000000000..550abadb3c06f3
--- /dev/null
+++ b/x-pack/plugins/ml/server/types.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
+import { HomeServerPluginSetup } from 'src/plugins/home/server';
+import { IRouter } from 'src/core/server';
+import { CloudSetup } from '../../cloud/server';
+import { SecurityPluginSetup } from '../../security/server';
+import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
+import { LicensingPluginSetup } from '../../licensing/server';
+import { SpacesPluginSetup } from '../../spaces/server';
+
+export interface LicenseCheckResult {
+ isAvailable: boolean;
+ isActive: boolean;
+ isEnabled: boolean;
+ isSecurityDisabled: boolean;
+ status?: string;
+ type?: string;
+}
+
+export interface SystemRouteDeps {
+ cloud: CloudSetup;
+ spacesPlugin: SpacesPluginSetup;
+}
+
+export interface PluginsSetup {
+ cloud: CloudSetup;
+ features: FeaturesPluginSetup;
+ home: HomeServerPluginSetup;
+ licensing: LicensingPluginSetup;
+ security: SecurityPluginSetup;
+ spaces: SpacesPluginSetup;
+ usageCollection: UsageCollectionSetup;
+}
+
+export interface RouteInitialization {
+ router: IRouter;
+ getLicenseCheckResults: () => LicenseCheckResult;
+}
diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts
index 65874ba3a461e6..af019ff10dedc3 100644
--- a/x-pack/plugins/security/server/authentication/authenticator.test.ts
+++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts
@@ -5,6 +5,8 @@
*/
jest.mock('./providers/basic');
+jest.mock('./providers/saml');
+jest.mock('./providers/http');
import Boom from 'boom';
import { duration, Duration } from 'moment';
@@ -23,15 +25,27 @@ import { Authenticator, AuthenticatorOptions, ProviderSession } from './authenti
import { DeauthenticationResult } from './deauthentication_result';
import { BasicAuthenticationProvider } from './providers';
-function getMockOptions(config: Partial = {}) {
+function getMockOptions({
+ session,
+ providers,
+ http = {},
+}: {
+ session?: AuthenticatorOptions['config']['session'];
+ providers?: string[];
+ http?: Partial;
+} = {}) {
return {
clusterClient: elasticsearchServiceMock.createClusterClient(),
basePath: httpServiceMock.createSetupContract().basePath,
loggers: loggingServiceMock.create(),
config: {
- session: { idleTimeout: null, lifespan: null },
- authc: { providers: [], oidc: {}, saml: {} },
- ...config,
+ session: { idleTimeout: null, lifespan: null, ...(session || {}) },
+ authc: {
+ providers: providers || [],
+ oidc: {},
+ saml: {},
+ http: { enabled: true, autoSchemesEnabled: true, schemes: ['apikey'], ...http },
+ },
},
sessionStorageFactory: sessionStorageMock.createFactory(),
};
@@ -44,8 +58,13 @@ describe('Authenticator', () => {
login: jest.fn(),
authenticate: jest.fn(),
logout: jest.fn(),
+ getHTTPAuthenticationScheme: jest.fn(),
};
+ jest.requireMock('./providers/http').HTTPAuthenticationProvider.mockImplementation(() => ({
+ authenticate: jest.fn().mockResolvedValue(AuthenticationResult.notHandled()),
+ }));
+
jest
.requireMock('./providers/basic')
.BasicAuthenticationProvider.mockImplementation(() => mockBasicAuthenticationProvider);
@@ -55,23 +74,80 @@ describe('Authenticator', () => {
describe('initialization', () => {
it('fails if authentication providers are not configured.', () => {
- const mockOptions = getMockOptions({
- authc: { providers: [], oidc: {}, saml: {} },
- });
- expect(() => new Authenticator(mockOptions)).toThrowError(
+ expect(() => new Authenticator(getMockOptions())).toThrowError(
'No authentication provider is configured. Verify `xpack.security.authc.providers` config value.'
);
});
it('fails if configured authentication provider is not known.', () => {
- const mockOptions = getMockOptions({
- authc: { providers: ['super-basic'], oidc: {}, saml: {} },
- });
-
- expect(() => new Authenticator(mockOptions)).toThrowError(
+ expect(() => new Authenticator(getMockOptions({ providers: ['super-basic'] }))).toThrowError(
'Unsupported authentication provider name: super-basic.'
);
});
+
+ describe('HTTP authentication provider', () => {
+ beforeEach(() => {
+ jest
+ .requireMock('./providers/basic')
+ .BasicAuthenticationProvider.mockImplementation(() => ({
+ getHTTPAuthenticationScheme: jest.fn().mockReturnValue('basic'),
+ }));
+ });
+
+ afterEach(() => jest.resetAllMocks());
+
+ it('enabled by default', () => {
+ const authenticator = new Authenticator(getMockOptions({ providers: ['basic'] }));
+ expect(authenticator.isProviderEnabled('basic')).toBe(true);
+ expect(authenticator.isProviderEnabled('http')).toBe(true);
+
+ expect(
+ jest.requireMock('./providers/http').HTTPAuthenticationProvider
+ ).toHaveBeenCalledWith(expect.anything(), {
+ supportedSchemes: new Set(['apikey', 'basic']),
+ });
+ });
+
+ it('includes all required schemes if `autoSchemesEnabled` is enabled', () => {
+ const authenticator = new Authenticator(
+ getMockOptions({ providers: ['basic', 'kerberos'] })
+ );
+ expect(authenticator.isProviderEnabled('basic')).toBe(true);
+ expect(authenticator.isProviderEnabled('kerberos')).toBe(true);
+ expect(authenticator.isProviderEnabled('http')).toBe(true);
+
+ expect(
+ jest.requireMock('./providers/http').HTTPAuthenticationProvider
+ ).toHaveBeenCalledWith(expect.anything(), {
+ supportedSchemes: new Set(['apikey', 'basic', 'bearer']),
+ });
+ });
+
+ it('does not include additional schemes if `autoSchemesEnabled` is disabled', () => {
+ const authenticator = new Authenticator(
+ getMockOptions({ providers: ['basic', 'kerberos'], http: { autoSchemesEnabled: false } })
+ );
+ expect(authenticator.isProviderEnabled('basic')).toBe(true);
+ expect(authenticator.isProviderEnabled('kerberos')).toBe(true);
+ expect(authenticator.isProviderEnabled('http')).toBe(true);
+
+ expect(
+ jest.requireMock('./providers/http').HTTPAuthenticationProvider
+ ).toHaveBeenCalledWith(expect.anything(), { supportedSchemes: new Set(['apikey']) });
+ });
+
+ it('disabled if explicitly disabled', () => {
+ const authenticator = new Authenticator(
+ getMockOptions({ providers: ['basic'], http: { enabled: false } })
+ );
+ expect(authenticator.isProviderEnabled('basic')).toBe(true);
+ expect(authenticator.isProviderEnabled('http')).toBe(false);
+
+ expect(
+ jest.requireMock('./providers/http').HTTPAuthenticationProvider
+ ).not.toHaveBeenCalled();
+ });
+ });
});
describe('`login` method', () => {
@@ -80,9 +156,7 @@ describe('Authenticator', () => {
let mockSessionStorage: jest.Mocked>;
let mockSessVal: any;
beforeEach(() => {
- mockOptions = getMockOptions({
- authc: { providers: ['basic'], oidc: {}, saml: {} },
- });
+ mockOptions = getMockOptions({ providers: ['basic'] });
mockSessionStorage = sessionStorageMock.create();
mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage);
mockSessVal = {
@@ -124,12 +198,9 @@ describe('Authenticator', () => {
AuthenticationResult.failed(failureReason)
);
- const authenticationResult = await authenticator.login(request, {
- provider: 'basic',
- value: {},
- });
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
+ await expect(authenticator.login(request, { provider: 'basic', value: {} })).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
});
it('returns user that authentication provider returns.', async () => {
@@ -140,13 +211,9 @@ describe('Authenticator', () => {
AuthenticationResult.succeeded(user, { authHeaders: { authorization: 'Basic .....' } })
);
- const authenticationResult = await authenticator.login(request, {
- provider: 'basic',
- value: {},
- });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Basic .....' });
+ await expect(authenticator.login(request, { provider: 'basic', value: {} })).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { authHeaders: { authorization: 'Basic .....' } })
+ );
});
it('creates session whenever authentication provider returns state', async () => {
@@ -158,12 +225,9 @@ describe('Authenticator', () => {
AuthenticationResult.succeeded(user, { state: { authorization } })
);
- const authenticationResult = await authenticator.login(request, {
- provider: 'basic',
- value: {},
- });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.login(request, { provider: 'basic', value: {} })).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { state: { authorization } })
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
@@ -174,11 +238,9 @@ describe('Authenticator', () => {
it('returns `notHandled` if login attempt is targeted to not configured provider.', async () => {
const request = httpServerMock.createKibanaRequest();
- const authenticationResult = await authenticator.login(request, {
- provider: 'token',
- value: {},
- });
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(authenticator.login(request, { provider: 'token', value: {} })).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
});
it('clears session if it belongs to a different provider.', async () => {
@@ -189,12 +251,9 @@ describe('Authenticator', () => {
mockBasicAuthenticationProvider.login.mockResolvedValue(AuthenticationResult.succeeded(user));
mockSessionStorage.get.mockResolvedValue({ ...mockSessVal, provider: 'token' });
- const authenticationResult = await authenticator.login(request, {
- provider: 'basic',
- value: credentials,
- });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toBe(user);
+ await expect(
+ authenticator.login(request, { provider: 'basic', value: credentials })
+ ).resolves.toEqual(AuthenticationResult.succeeded(user));
expect(mockBasicAuthenticationProvider.login).toHaveBeenCalledWith(
request,
@@ -214,12 +273,9 @@ describe('Authenticator', () => {
AuthenticationResult.succeeded(user, { state: null })
);
- const authenticationResult = await authenticator.login(request, {
- provider: 'basic',
- value: {},
- });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.login(request, { provider: 'basic', value: {} })).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { state: null })
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).toHaveBeenCalled();
@@ -232,9 +288,7 @@ describe('Authenticator', () => {
let mockSessionStorage: jest.Mocked>;
let mockSessVal: any;
beforeEach(() => {
- mockOptions = getMockOptions({
- authc: { providers: ['basic'], oidc: {}, saml: {} },
- });
+ mockOptions = getMockOptions({ providers: ['basic'] });
mockSessionStorage = sessionStorageMock.create();
mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage);
mockSessVal = {
@@ -277,10 +331,9 @@ describe('Authenticator', () => {
AuthenticationResult.succeeded(user, { authHeaders: { authorization: 'Basic .....' } })
);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Basic .....' });
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { authHeaders: { authorization: 'Basic .....' } })
+ );
});
it('creates session whenever authentication provider returns state for system API requests', async () => {
@@ -294,9 +347,9 @@ describe('Authenticator', () => {
AuthenticationResult.succeeded(user, { state: { authorization } })
);
- const systemAPIAuthenticationResult = await authenticator.authenticate(request);
- expect(systemAPIAuthenticationResult.succeeded()).toBe(true);
- expect(systemAPIAuthenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { state: { authorization } })
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
@@ -316,9 +369,9 @@ describe('Authenticator', () => {
AuthenticationResult.succeeded(user, { state: { authorization } })
);
- const systemAPIAuthenticationResult = await authenticator.authenticate(request);
- expect(systemAPIAuthenticationResult.succeeded()).toBe(true);
- expect(systemAPIAuthenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { state: { authorization } })
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
@@ -338,9 +391,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user)
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
@@ -357,9 +410,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user)
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith(mockSessVal);
@@ -377,7 +430,7 @@ describe('Authenticator', () => {
idleTimeout: duration(3600 * 24),
lifespan: null,
},
- authc: { providers: ['basic'], oidc: {}, saml: {} },
+ providers: ['basic'],
});
mockSessionStorage = sessionStorageMock.create();
@@ -392,9 +445,9 @@ describe('Authenticator', () => {
jest.spyOn(Date, 'now').mockImplementation(() => currentDate);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user)
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
@@ -416,7 +469,7 @@ describe('Authenticator', () => {
idleTimeout: duration(hr * 2),
lifespan: duration(hr * 8),
},
- authc: { providers: ['basic'], oidc: {}, saml: {} },
+ providers: ['basic'],
});
mockSessionStorage = sessionStorageMock.create();
@@ -437,9 +490,9 @@ describe('Authenticator', () => {
jest.spyOn(Date, 'now').mockImplementation(() => currentDate);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user)
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
@@ -468,7 +521,7 @@ describe('Authenticator', () => {
idleTimeout: null,
lifespan,
},
- authc: { providers: ['basic'], oidc: {}, saml: {} },
+ providers: ['basic'],
});
mockSessionStorage = sessionStorageMock.create();
@@ -485,9 +538,9 @@ describe('Authenticator', () => {
AuthenticationResult.succeeded(user)
);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user)
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
@@ -517,13 +570,15 @@ describe('Authenticator', () => {
headers: { 'kbn-system-request': 'true' },
});
+ const failureReason = new Error('some error');
mockBasicAuthenticationProvider.authenticate.mockResolvedValue(
- AuthenticationResult.failed(new Error('some error'))
+ AuthenticationResult.failed(failureReason)
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.failed()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
@@ -534,13 +589,15 @@ describe('Authenticator', () => {
headers: { 'kbn-system-request': 'false' },
});
+ const failureReason = new Error('some error');
mockBasicAuthenticationProvider.authenticate.mockResolvedValue(
- AuthenticationResult.failed(new Error('some error'))
+ AuthenticationResult.failed(failureReason)
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.failed()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
@@ -558,9 +615,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { state: newState })
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
@@ -582,9 +639,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { state: newState })
+ );
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
@@ -604,8 +661,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.failed()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.unauthorized())
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).toHaveBeenCalled();
@@ -621,8 +679,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.failed()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.unauthorized())
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).toHaveBeenCalled();
@@ -636,8 +695,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.redirected()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.redirectTo('some-url', { state: null })
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).toHaveBeenCalled();
@@ -653,8 +713,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
@@ -670,8 +731,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
@@ -687,8 +749,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue({ ...mockSessVal, provider: 'token' });
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).toHaveBeenCalled();
@@ -704,8 +767,9 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue({ ...mockSessVal, provider: 'token' });
- const authenticationResult = await authenticator.authenticate(request);
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(authenticator.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
expect(mockSessionStorage.set).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).toHaveBeenCalled();
@@ -718,9 +782,7 @@ describe('Authenticator', () => {
let mockSessionStorage: jest.Mocked>;
let mockSessVal: any;
beforeEach(() => {
- mockOptions = getMockOptions({
- authc: { providers: ['basic'], oidc: {}, saml: {} },
- });
+ mockOptions = getMockOptions({ providers: ['basic'] });
mockSessionStorage = sessionStorageMock.create();
mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage);
mockSessVal = {
@@ -744,9 +806,10 @@ describe('Authenticator', () => {
const request = httpServerMock.createKibanaRequest();
mockSessionStorage.get.mockResolvedValue(null);
- const deauthenticationResult = await authenticator.logout(request);
+ await expect(authenticator.logout(request)).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
- expect(deauthenticationResult.notHandled()).toBe(true);
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
});
@@ -757,12 +820,12 @@ describe('Authenticator', () => {
);
mockSessionStorage.get.mockResolvedValue(mockSessVal);
- const deauthenticationResult = await authenticator.logout(request);
+ await expect(authenticator.logout(request)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('some-url')
+ );
expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.clear).toHaveBeenCalled();
- expect(deauthenticationResult.redirected()).toBe(true);
- expect(deauthenticationResult.redirectURL).toBe('some-url');
});
it('if session does not exist but provider name is valid, returns whatever authentication provider returns.', async () => {
@@ -773,21 +836,22 @@ describe('Authenticator', () => {
DeauthenticationResult.redirectTo('some-url')
);
- const deauthenticationResult = await authenticator.logout(request);
+ await expect(authenticator.logout(request)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('some-url')
+ );
expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
- expect(deauthenticationResult.redirected()).toBe(true);
- expect(deauthenticationResult.redirectURL).toBe('some-url');
});
it('returns `notHandled` if session does not exist and provider name is invalid', async () => {
const request = httpServerMock.createKibanaRequest({ query: { provider: 'foo' } });
mockSessionStorage.get.mockResolvedValue(null);
- const deauthenticationResult = await authenticator.logout(request);
+ await expect(authenticator.logout(request)).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
- expect(deauthenticationResult.notHandled()).toBe(true);
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
});
@@ -796,11 +860,12 @@ describe('Authenticator', () => {
const state = { authorization: 'Bearer xxx' };
mockSessionStorage.get.mockResolvedValue({ ...mockSessVal, state, provider: 'token' });
- const deauthenticationResult = await authenticator.logout(request);
+ await expect(authenticator.logout(request)).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled();
expect(mockSessionStorage.clear).toHaveBeenCalled();
- expect(deauthenticationResult.notHandled()).toBe(true);
});
});
@@ -809,9 +874,7 @@ describe('Authenticator', () => {
let mockOptions: ReturnType;
let mockSessionStorage: jest.Mocked>;
beforeEach(() => {
- mockOptions = getMockOptions({
- authc: { providers: ['basic'], oidc: {}, saml: {} },
- });
+ mockOptions = getMockOptions({ providers: ['basic'] });
mockSessionStorage = sessionStorageMock.create();
mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage);
@@ -851,4 +914,16 @@ describe('Authenticator', () => {
expect(sessionInfo).toBe(null);
});
});
+
+ describe('`isProviderEnabled` method', () => {
+ it('returns `true` only if specified provider is enabled', () => {
+ let authenticator = new Authenticator(getMockOptions({ providers: ['basic'] }));
+ expect(authenticator.isProviderEnabled('basic')).toBe(true);
+ expect(authenticator.isProviderEnabled('saml')).toBe(false);
+
+ authenticator = new Authenticator(getMockOptions({ providers: ['basic', 'saml'] }));
+ expect(authenticator.isProviderEnabled('basic')).toBe(true);
+ expect(authenticator.isProviderEnabled('saml')).toBe(true);
+ });
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts
index 3ab49d3c5b124b..4954e1b24216cb 100644
--- a/x-pack/plugins/security/server/authentication/authenticator.ts
+++ b/x-pack/plugins/security/server/authentication/authenticator.ts
@@ -27,6 +27,7 @@ import {
TokenAuthenticationProvider,
OIDCAuthenticationProvider,
PKIAuthenticationProvider,
+ HTTPAuthenticationProvider,
isSAMLRequestQuery,
} from './providers';
import { AuthenticationResult } from './authentication_result';
@@ -191,6 +192,7 @@ export class Authenticator {
client: this.options.clusterClient,
logger: this.options.loggers.get('tokens'),
}),
+ isProviderEnabled: this.isProviderEnabled.bind(this),
};
const authProviders = this.options.config.authc.providers;
@@ -206,6 +208,8 @@ export class Authenticator {
? (this.options.config.authc as Record)[providerType]
: undefined;
+ this.logger.debug(`Enabling "${providerType}" authentication provider.`);
+
return [
providerType,
instantiateProvider(
@@ -216,6 +220,17 @@ export class Authenticator {
] as [string, BaseAuthenticationProvider];
})
);
+
+ // For the BWC reasons we always include HTTP authentication provider unless it's explicitly disabled.
+ if (this.options.config.authc.http.enabled) {
+ this.setupHTTPAuthenticationProvider(
+ Object.freeze({
+ ...providerCommonOptions,
+ logger: options.loggers.get(HTTPAuthenticationProvider.type),
+ })
+ );
+ }
+
this.serverBasePath = this.options.basePath.serverBasePath || '/';
this.idleTimeout = this.options.config.session.idleTimeout;
@@ -385,6 +400,41 @@ export class Authenticator {
return null;
}
+ /**
+ * Checks whether specified provider type is currently enabled.
+ * @param providerType Type of the provider (`basic`, `saml`, `pki` etc.).
+ */
+ isProviderEnabled(providerType: string) {
+ return this.providers.has(providerType);
+ }
+
+ /**
+ * Initializes HTTP Authentication provider and appends it to the end of the list of enabled
+ * authentication providers.
+ * @param options Common provider options.
+ */
+ private setupHTTPAuthenticationProvider(options: AuthenticationProviderOptions) {
+ const supportedSchemes = new Set(
+ this.options.config.authc.http.schemes.map(scheme => scheme.toLowerCase())
+ );
+
+ // If `autoSchemesEnabled` is set we should allow schemes that other providers use to
+ // authenticate requests with Elasticsearch.
+ if (this.options.config.authc.http.autoSchemesEnabled) {
+ for (const provider of this.providers.values()) {
+ const supportedScheme = provider.getHTTPAuthenticationScheme();
+ if (supportedScheme) {
+ supportedSchemes.add(supportedScheme.toLowerCase());
+ }
+ }
+ }
+
+ this.providers.set(
+ HTTPAuthenticationProvider.type,
+ new HTTPAuthenticationProvider(options, { supportedSchemes })
+ );
+ }
+
/**
* Returns provider iterator where providers are sorted in the order of priority (based on the session ownership).
* @param sessionValue Current session value.
diff --git a/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.test.ts b/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.test.ts
new file mode 100644
index 00000000000000..6a63634394ec0c
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.test.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { httpServerMock } from '../../../../../src/core/server/http/http_server.mocks';
+
+import { getHTTPAuthenticationScheme } from './get_http_authentication_scheme';
+
+describe('getHTTPAuthenticationScheme', () => {
+ it('returns `null` if request does not have authorization header', () => {
+ expect(getHTTPAuthenticationScheme(httpServerMock.createKibanaRequest())).toBeNull();
+ });
+
+ it('returns `null` if authorization header value isn not a string', () => {
+ expect(
+ getHTTPAuthenticationScheme(
+ httpServerMock.createKibanaRequest({
+ headers: { authorization: ['Basic xxx', 'Bearer xxx'] as any },
+ })
+ )
+ ).toBeNull();
+ });
+
+ it('returns `null` if authorization header value is an empty string', () => {
+ expect(
+ getHTTPAuthenticationScheme(
+ httpServerMock.createKibanaRequest({ headers: { authorization: '' } })
+ )
+ ).toBeNull();
+ });
+
+ it('returns only scheme portion of the authorization header value in lower case', () => {
+ const headerValueAndSchemeMap = [
+ ['Basic xxx', 'basic'],
+ ['Basic xxx yyy', 'basic'],
+ ['basic xxx', 'basic'],
+ ['basic', 'basic'],
+ // We don't trim leading whitespaces in scheme.
+ [' Basic xxx', ''],
+ ['Negotiate xxx', 'negotiate'],
+ ['negotiate xxx', 'negotiate'],
+ ['negotiate', 'negotiate'],
+ ['ApiKey xxx', 'apikey'],
+ ['apikey xxx', 'apikey'],
+ ['Api Key xxx', 'api'],
+ ];
+
+ for (const [authorization, scheme] of headerValueAndSchemeMap) {
+ expect(
+ getHTTPAuthenticationScheme(
+ httpServerMock.createKibanaRequest({ headers: { authorization } })
+ )
+ ).toBe(scheme);
+ }
+ });
+});
diff --git a/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.ts b/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.ts
new file mode 100644
index 00000000000000..b9c53f34dbcab7
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/get_http_authentication_scheme.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { KibanaRequest } from '../../../../../src/core/server';
+
+/**
+ * Parses request's `Authorization` HTTP header if present and extracts authentication scheme.
+ * https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes
+ * @param request Request instance to extract authentication scheme for.
+ */
+export function getHTTPAuthenticationScheme(request: KibanaRequest) {
+ const authorizationHeaderValue = request.headers.authorization;
+ if (!authorizationHeaderValue || typeof authorizationHeaderValue !== 'string') {
+ return null;
+ }
+
+ return authorizationHeaderValue.split(/\s+/)[0].toLowerCase();
+}
diff --git a/x-pack/plugins/security/server/authentication/index.mock.ts b/x-pack/plugins/security/server/authentication/index.mock.ts
index 77f1f9e45aea72..c634e2c80c2994 100644
--- a/x-pack/plugins/security/server/authentication/index.mock.ts
+++ b/x-pack/plugins/security/server/authentication/index.mock.ts
@@ -9,11 +9,12 @@ import { Authentication } from '.';
export const authenticationMock = {
create: (): jest.Mocked => ({
login: jest.fn(),
+ logout: jest.fn(),
+ isProviderEnabled: jest.fn(),
createAPIKey: jest.fn(),
getCurrentUser: jest.fn(),
invalidateAPIKey: jest.fn(),
isAuthenticated: jest.fn(),
- logout: jest.fn(),
getSessionInfo: jest.fn(),
}),
};
diff --git a/x-pack/plugins/security/server/authentication/index.test.ts b/x-pack/plugins/security/server/authentication/index.test.ts
index 3727b1fc13dac9..aaf3fc357352ee 100644
--- a/x-pack/plugins/security/server/authentication/index.test.ts
+++ b/x-pack/plugins/security/server/authentication/index.test.ts
@@ -61,7 +61,7 @@ describe('setupAuthentication()', () => {
lifespan: null,
},
cookieName: 'my-sid-cookie',
- authc: { providers: ['basic'] },
+ authc: { providers: ['basic'], http: { enabled: true } },
}),
true
);
diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts
index 467afe00340259..189babbc6bfe67 100644
--- a/x-pack/plugins/security/server/authentication/index.ts
+++ b/x-pack/plugins/security/server/authentication/index.ts
@@ -169,6 +169,7 @@ export async function setupAuthentication({
login: authenticator.login.bind(authenticator),
logout: authenticator.logout.bind(authenticator),
getSessionInfo: authenticator.getSessionInfo.bind(authenticator),
+ isProviderEnabled: authenticator.isProviderEnabled.bind(authenticator),
getCurrentUser,
createAPIKey: (request: KibanaRequest, params: CreateAPIKeyParams) =>
apiKeys.create(request, params),
diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts
index a659786f4aeffe..0781608f8bc4c0 100644
--- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts
+++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts
@@ -4,9 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import sinon from 'sinon';
-import { ScopedClusterClient } from '../../../../../../src/core/server';
-import { Tokens } from '../tokens';
import {
loggingServiceMock,
httpServiceMock,
@@ -17,34 +14,7 @@ export type MockAuthenticationProviderOptions = ReturnType<
typeof mockAuthenticationProviderOptions
>;
-export type MockAuthenticationProviderOptionsWithJest = ReturnType<
- typeof mockAuthenticationProviderOptionsWithJest
->;
-
-export function mockScopedClusterClient(
- client: MockAuthenticationProviderOptions['client'],
- requestMatcher: sinon.SinonMatcher = sinon.match.any
-) {
- const scopedClusterClient = sinon.createStubInstance(ScopedClusterClient);
- client.asScoped.withArgs(requestMatcher).returns(scopedClusterClient);
- return scopedClusterClient;
-}
-
export function mockAuthenticationProviderOptions() {
- const logger = loggingServiceMock.create().get();
- const basePath = httpServiceMock.createSetupContract().basePath;
- basePath.get.mockReturnValue('/base-path');
-
- return {
- client: { callAsInternalUser: sinon.stub(), asScoped: sinon.stub(), close: sinon.stub() },
- logger,
- basePath,
- tokens: sinon.createStubInstance(Tokens),
- };
-}
-
-// Will be renamed to mockAuthenticationProviderOptions as soon as we migrate all providers tests to Jest.
-export function mockAuthenticationProviderOptionsWithJest() {
const basePath = httpServiceMock.createSetupContract().basePath;
basePath.get.mockReturnValue('/base-path');
diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts
index a40732768810d9..300e59d9ea3dac 100644
--- a/x-pack/plugins/security/server/authentication/providers/base.ts
+++ b/x-pack/plugins/security/server/authentication/providers/base.ts
@@ -84,6 +84,13 @@ export abstract class BaseAuthenticationProvider {
*/
abstract logout(request: KibanaRequest, state?: unknown): Promise;
+ /**
+ * Returns HTTP authentication scheme that provider uses within `Authorization` HTTP header that
+ * it attaches to all successfully authenticated requests to Elasticsearch or `null` in case
+ * provider doesn't attach any additional `Authorization` HTTP headers.
+ */
+ abstract getHTTPAuthenticationScheme(): string | null;
+
/**
* Queries Elasticsearch `_authenticate` endpoint to authenticate request and retrieve the user
* information of authenticated user.
diff --git a/x-pack/plugins/security/server/authentication/providers/basic.test.ts b/x-pack/plugins/security/server/authentication/providers/basic.test.ts
index f9c665d6cea48c..b7bdff0531fc29 100644
--- a/x-pack/plugins/security/server/authentication/providers/basic.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/basic.test.ts
@@ -4,18 +4,31 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import sinon from 'sinon';
-
-import { httpServerMock } from '../../../../../../src/core/server/mocks';
+import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
-import { mockAuthenticationProviderOptions, mockScopedClusterClient } from './base.mock';
+import { mockAuthenticationProviderOptions } from './base.mock';
+import { IClusterClient, ScopeableRequest } from '../../../../../../src/core/server';
+import { AuthenticationResult } from '../authentication_result';
+import { DeauthenticationResult } from '../deauthentication_result';
import { BasicAuthenticationProvider } from './basic';
function generateAuthorizationHeader(username: string, password: string) {
return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
}
+function expectAuthenticateCall(
+ mockClusterClient: jest.Mocked,
+ scopeableRequest: ScopeableRequest
+) {
+ expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1);
+ expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest);
+
+ const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value;
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
+}
+
describe('BasicAuthenticationProvider', () => {
let provider: BasicAuthenticationProvider;
let mockOptions: ReturnType;
@@ -30,38 +43,39 @@ describe('BasicAuthenticationProvider', () => {
const credentials = { username: 'user', password: 'password' };
const authorization = generateAuthorizationHeader(credentials.username, credentials.password);
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.login(
- httpServerMock.createKibanaRequest(),
- credentials
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(
+ provider.login(httpServerMock.createKibanaRequest({ headers: {} }), credentials)
+ ).resolves.toEqual(
+ AuthenticationResult.succeeded(user, {
+ authHeaders: { authorization },
+ state: { authorization },
+ })
);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
- expect(authenticationResult.state).toEqual({ authorization });
- expect(authenticationResult.authHeaders).toEqual({ authorization });
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
});
it('fails if user cannot be retrieved during login attempt', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const credentials = { username: 'user', password: 'password' };
const authorization = generateAuthorizationHeader(credentials.username, credentials.password);
const authenticationError = new Error('Some error');
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(authenticationError);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- const authenticationResult = await provider.login(request, credentials);
+ await expect(provider.login(request, credentials)).resolves.toEqual(
+ AuthenticationResult.failed(authenticationError)
+ );
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.error).toEqual(authenticationError);
});
});
@@ -69,142 +83,113 @@ describe('BasicAuthenticationProvider', () => {
it('does not redirect AJAX requests that can not be authenticated to the login page.', async () => {
// Add `kbn-xsrf` header to make `can_redirect_request` think that it's AJAX request and
// avoid triggering of redirect logic.
- const authenticationResult = await provider.authenticate(
- httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }),
- null
- );
-
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(
+ provider.authenticate(
+ httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }),
+ null
+ )
+ ).resolves.toEqual(AuthenticationResult.notHandled());
});
it('redirects non-AJAX requests that can not be authenticated to the login page.', async () => {
- const authenticationResult = await provider.authenticate(
- httpServerMock.createKibanaRequest({ path: '/s/foo/some-path # that needs to be encoded' }),
- null
- );
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- '/base-path/login?next=%2Fbase-path%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded'
+ await expect(
+ provider.authenticate(
+ httpServerMock.createKibanaRequest({
+ path: '/s/foo/some-path # that needs to be encoded',
+ }),
+ null
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ '/base-path/login?next=%2Fbase-path%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded'
+ )
);
});
it('does not handle authentication if state exists, but authorization property is missing.', async () => {
- const authenticationResult = await provider.authenticate(
- httpServerMock.createKibanaRequest(),
- {}
- );
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(
+ provider.authenticate(httpServerMock.createKibanaRequest(), {})
+ ).resolves.toEqual(AuthenticationResult.notHandled());
});
- it('succeeds if only `authorization` header is available.', async () => {
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: generateAuthorizationHeader('user', 'password') },
- });
- const user = mockAuthenticatedUser();
-
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: request.headers }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request);
+ it('does not handle authentication via `authorization` header.', async () => {
+ const authorization = generateAuthorizationHeader('user', 'password');
+ const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
- // Session state and authHeaders aren't returned for header-based auth.
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.authHeaders).toBeUndefined();
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe(authorization);
});
- it('succeeds if only state is available.', async () => {
- const request = httpServerMock.createKibanaRequest();
- const user = mockAuthenticatedUser();
+ it('does not handle authentication via `authorization` header even if state contains valid credentials.', async () => {
const authorization = generateAuthorizationHeader('user', 'password');
+ const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request, { authorization });
+ await expect(provider.authenticate(request, { authorization })).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.authHeaders).toEqual({ authorization });
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe(authorization);
});
- it('does not handle `authorization` header with unsupported schema even if state contains valid credentials.', async () => {
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Bearer ***' },
- });
+ it('succeeds if only state is available.', async () => {
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
+ const user = mockAuthenticatedUser();
const authorization = generateAuthorizationHeader('user', 'password');
- const authenticationResult = await provider.authenticate(request, { authorization });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- sinon.assert.notCalled(mockOptions.client.asScoped);
- expect(request.headers.authorization).toBe('Bearer ***');
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(provider.authenticate(request, { authorization })).resolves.toEqual(
+ AuthenticationResult.succeeded(user, { authHeaders: { authorization } })
+ );
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
});
it('fails if state contains invalid credentials.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const authorization = generateAuthorizationHeader('user', 'password');
const authenticationError = new Error('Forbidden');
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(authenticationError);
-
- const authenticationResult = await provider.authenticate(request, { authorization });
-
- expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.authHeaders).toBeUndefined();
- expect(authenticationResult.error).toBe(authenticationError);
- });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- it('authenticates only via `authorization` header even if state is available.', async () => {
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: generateAuthorizationHeader('user', 'password') },
- });
- const user = mockAuthenticatedUser();
-
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: request.headers }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ await expect(provider.authenticate(request, { authorization })).resolves.toEqual(
+ AuthenticationResult.failed(authenticationError)
+ );
- const authorizationInState = generateAuthorizationHeader('user1', 'password2');
- const authenticationResult = await provider.authenticate(request, {
- authorization: authorizationInState,
- });
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual(user);
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.authHeaders).toBeUndefined();
+ expect(request.headers).not.toHaveProperty('authorization');
});
});
describe('`logout` method', () => {
it('always redirects to the login page.', async () => {
- const request = httpServerMock.createKibanaRequest();
- const deauthenticateResult = await provider.logout(request);
- expect(deauthenticateResult.redirected()).toBe(true);
- expect(deauthenticateResult.redirectURL).toBe('/base-path/login?msg=LOGGED_OUT');
+ await expect(provider.logout(httpServerMock.createKibanaRequest())).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/base-path/login?msg=LOGGED_OUT')
+ );
});
it('passes query string parameters to the login page.', async () => {
- const request = httpServerMock.createKibanaRequest({
- query: { next: '/app/ml', msg: 'SESSION_EXPIRED' },
- });
- const deauthenticateResult = await provider.logout(request);
- expect(deauthenticateResult.redirected()).toBe(true);
- expect(deauthenticateResult.redirectURL).toBe(
- '/base-path/login?next=%2Fapp%2Fml&msg=SESSION_EXPIRED'
+ await expect(
+ provider.logout(
+ httpServerMock.createKibanaRequest({ query: { next: '/app/ml', msg: 'SESSION_EXPIRED' } })
+ )
+ ).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/base-path/login?next=%2Fapp%2Fml&msg=SESSION_EXPIRED')
);
});
});
+
+ it('`getHTTPAuthenticationScheme` method', () => {
+ expect(provider.getHTTPAuthenticationScheme()).toBe('basic');
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/providers/basic.ts b/x-pack/plugins/security/server/authentication/providers/basic.ts
index a8e4e8705a7a8d..ad46aff8afa518 100644
--- a/x-pack/plugins/security/server/authentication/providers/basic.ts
+++ b/x-pack/plugins/security/server/authentication/providers/basic.ts
@@ -8,6 +8,7 @@ import { KibanaRequest } from '../../../../../../src/core/server';
import { canRedirectRequest } from '../can_redirect_request';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
+import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
import { BaseAuthenticationProvider } from './base';
/**
@@ -75,29 +76,25 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- // try header-based auth
- const {
- authenticationResult: headerAuthResult,
- headerNotRecognized,
- } = await this.authenticateViaHeader(request);
- if (headerNotRecognized) {
- return headerAuthResult;
+ if (getHTTPAuthenticationScheme(request) != null) {
+ this.logger.debug('Cannot authenticate requests with `Authorization` header.');
+ return AuthenticationResult.notHandled();
}
- let authenticationResult = headerAuthResult;
- if (authenticationResult.notHandled() && state) {
- authenticationResult = await this.authenticateViaState(request, state);
- } else if (authenticationResult.notHandled() && canRedirectRequest(request)) {
- // If we couldn't handle authentication let's redirect user to the login page.
- const nextURL = encodeURIComponent(
- `${this.options.basePath.get(request)}${request.url.path}`
- );
- authenticationResult = AuthenticationResult.redirectTo(
- `${this.options.basePath.get(request)}/login?next=${nextURL}`
+ if (state) {
+ return await this.authenticateViaState(request, state);
+ }
+
+ // If state isn't present let's redirect user to the login page.
+ if (canRedirectRequest(request)) {
+ this.logger.debug('Redirecting request to Login page.');
+ const basePath = this.options.basePath.get(request);
+ return AuthenticationResult.redirectTo(
+ `${basePath}/login?next=${encodeURIComponent(`${basePath}${request.url.path}`)}`
);
}
- return authenticationResult;
+ return AuthenticationResult.notHandled();
}
/**
@@ -114,37 +111,11 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider {
}
/**
- * Validates whether request contains `Basic ***` Authorization header and just passes it
- * forward to Elasticsearch backend.
- * @param request Request instance.
+ * Returns HTTP authentication scheme (`Bearer`) that's used within `Authorization` HTTP header
+ * that provider attaches to all successfully authenticated requests to Elasticsearch.
*/
- private async authenticateViaHeader(request: KibanaRequest) {
- this.logger.debug('Trying to authenticate via header.');
-
- const authorization = request.headers.authorization;
- if (!authorization || typeof authorization !== 'string') {
- this.logger.debug('Authorization header is not presented.');
- return { authenticationResult: AuthenticationResult.notHandled() };
- }
-
- const authenticationSchema = authorization.split(/\s+/)[0];
- if (authenticationSchema.toLowerCase() !== 'basic') {
- this.logger.debug(`Unsupported authentication schema: ${authenticationSchema}`);
- return {
- authenticationResult: AuthenticationResult.notHandled(),
- headerNotRecognized: true,
- };
- }
-
- try {
- const user = await this.getUser(request);
-
- this.logger.debug('Request has been authenticated via header.');
- return { authenticationResult: AuthenticationResult.succeeded(user) };
- } catch (err) {
- this.logger.debug(`Failed to authenticate request via header: ${err.message}`);
- return { authenticationResult: AuthenticationResult.failed(err) };
- }
+ public getHTTPAuthenticationScheme() {
+ return 'basic';
}
/**
diff --git a/x-pack/plugins/security/server/authentication/providers/http.test.ts b/x-pack/plugins/security/server/authentication/providers/http.test.ts
new file mode 100644
index 00000000000000..65fbd7cd9f4adf
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/providers/http.test.ts
@@ -0,0 +1,198 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
+import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
+import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock';
+
+import {
+ ElasticsearchErrorHelpers,
+ IClusterClient,
+ ScopeableRequest,
+} from '../../../../../../src/core/server';
+import { AuthenticationResult } from '../authentication_result';
+import { DeauthenticationResult } from '../deauthentication_result';
+import { HTTPAuthenticationProvider } from './http';
+
+function expectAuthenticateCall(
+ mockClusterClient: jest.Mocked,
+ scopeableRequest: ScopeableRequest
+) {
+ expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1);
+ expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest);
+
+ const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value;
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
+}
+
+describe('HTTPAuthenticationProvider', () => {
+ let mockOptions: MockAuthenticationProviderOptions;
+ beforeEach(() => {
+ mockOptions = mockAuthenticationProviderOptions();
+ });
+
+ it('throws if `schemes` are not specified', () => {
+ const providerOptions = mockAuthenticationProviderOptions();
+
+ expect(() => new HTTPAuthenticationProvider(providerOptions, undefined as any)).toThrowError(
+ 'Supported schemes should be specified'
+ );
+ expect(() => new HTTPAuthenticationProvider(providerOptions, {} as any)).toThrowError(
+ 'Supported schemes should be specified'
+ );
+ expect(
+ () => new HTTPAuthenticationProvider(providerOptions, { supportedSchemes: new Set() })
+ ).toThrowError('Supported schemes should be specified');
+ });
+
+ describe('`login` method', () => {
+ it('does not handle login', async () => {
+ const provider = new HTTPAuthenticationProvider(mockOptions, {
+ supportedSchemes: new Set(['apikey']),
+ });
+
+ await expect(provider.login()).resolves.toEqual(AuthenticationResult.notHandled());
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('`authenticate` method', () => {
+ it('does not handle authentication for requests without `authorization` header.', async () => {
+ const provider = new HTTPAuthenticationProvider(mockOptions, {
+ supportedSchemes: new Set(['apikey']),
+ });
+
+ await expect(provider.authenticate(httpServerMock.createKibanaRequest())).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
+ });
+
+ it('does not handle authentication for requests with empty scheme in `authorization` header.', async () => {
+ const provider = new HTTPAuthenticationProvider(mockOptions, {
+ supportedSchemes: new Set(['apikey']),
+ });
+
+ await expect(
+ provider.authenticate(
+ httpServerMock.createKibanaRequest({ headers: { authorization: '' } })
+ )
+ ).resolves.toEqual(AuthenticationResult.notHandled());
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
+ });
+
+ it('does not handle authentication via `authorization` header if scheme is not supported.', async () => {
+ for (const { schemes, header } of [
+ { schemes: ['basic'], header: 'Bearer xxx' },
+ { schemes: ['bearer'], header: 'Basic xxx' },
+ { schemes: ['basic', 'apikey'], header: 'Bearer xxx' },
+ { schemes: ['basic', 'bearer'], header: 'ApiKey xxx' },
+ ]) {
+ const request = httpServerMock.createKibanaRequest({ headers: { authorization: header } });
+
+ const provider = new HTTPAuthenticationProvider(mockOptions, {
+ supportedSchemes: new Set(schemes),
+ });
+
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
+
+ expect(request.headers.authorization).toBe(header);
+ }
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
+ });
+
+ it('succeeds if authentication via `authorization` header with supported scheme succeeds.', async () => {
+ const user = mockAuthenticatedUser();
+ for (const { schemes, header } of [
+ { schemes: ['basic'], header: 'Basic xxx' },
+ { schemes: ['bearer'], header: 'Bearer xxx' },
+ { schemes: ['basic', 'apikey'], header: 'ApiKey xxx' },
+ { schemes: ['some-weird-scheme'], header: 'some-weird-scheme xxx' },
+ { schemes: ['apikey', 'bearer'], header: 'Bearer xxx' },
+ ]) {
+ const request = httpServerMock.createKibanaRequest({ headers: { authorization: header } });
+
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+ mockOptions.client.asScoped.mockClear();
+
+ const provider = new HTTPAuthenticationProvider(mockOptions, {
+ supportedSchemes: new Set(schemes),
+ });
+
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded({ ...user, authentication_provider: 'http' })
+ );
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization: header } });
+
+ expect(request.headers.authorization).toBe(header);
+ }
+ });
+
+ it('fails if authentication via `authorization` header with supported scheme fails.', async () => {
+ const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
+ for (const { schemes, header } of [
+ { schemes: ['basic'], header: 'Basic xxx' },
+ { schemes: ['bearer'], header: 'Bearer xxx' },
+ { schemes: ['basic', 'apikey'], header: 'ApiKey xxx' },
+ { schemes: ['some-weird-scheme'], header: 'some-weird-scheme xxx' },
+ { schemes: ['apikey', 'bearer'], header: 'Bearer xxx' },
+ ]) {
+ const request = httpServerMock.createKibanaRequest({ headers: { authorization: header } });
+
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+ mockOptions.client.asScoped.mockClear();
+
+ const provider = new HTTPAuthenticationProvider(mockOptions, {
+ supportedSchemes: new Set(schemes),
+ });
+
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization: header } });
+
+ expect(request.headers.authorization).toBe(header);
+ }
+ });
+ });
+
+ describe('`logout` method', () => {
+ it('does not handle logout', async () => {
+ const provider = new HTTPAuthenticationProvider(mockOptions, {
+ supportedSchemes: new Set(['apikey']),
+ });
+
+ await expect(provider.logout()).resolves.toEqual(DeauthenticationResult.notHandled());
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
+ });
+ });
+
+ it('`getHTTPAuthenticationScheme` method', () => {
+ const provider = new HTTPAuthenticationProvider(mockOptions, {
+ supportedSchemes: new Set(['apikey']),
+ });
+ expect(provider.getHTTPAuthenticationScheme()).toBeNull();
+ });
+});
diff --git a/x-pack/plugins/security/server/authentication/providers/http.ts b/x-pack/plugins/security/server/authentication/providers/http.ts
new file mode 100644
index 00000000000000..57163bf8145b8d
--- /dev/null
+++ b/x-pack/plugins/security/server/authentication/providers/http.ts
@@ -0,0 +1,99 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { KibanaRequest } from '../../../../../../src/core/server';
+import { AuthenticationResult } from '../authentication_result';
+import { DeauthenticationResult } from '../deauthentication_result';
+import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
+import { AuthenticationProviderOptions, BaseAuthenticationProvider } from './base';
+
+interface HTTPAuthenticationProviderOptions {
+ supportedSchemes: Set;
+}
+
+/**
+ * Provider that supports request authentication via forwarding `Authorization` HTTP header to Elasticsearch.
+ */
+export class HTTPAuthenticationProvider extends BaseAuthenticationProvider {
+ /**
+ * Type of the provider.
+ */
+ static readonly type = 'http';
+
+ /**
+ * Set of the schemes (`Basic`, `Bearer` etc.) that provider expects to see within `Authorization`
+ * HTTP header while authenticating request.
+ */
+ private readonly supportedSchemes: Set;
+
+ constructor(
+ protected readonly options: Readonly,
+ httpOptions: Readonly
+ ) {
+ super(options);
+
+ if ((httpOptions?.supportedSchemes?.size ?? 0) === 0) {
+ throw new Error('Supported schemes should be specified');
+ }
+ this.supportedSchemes = httpOptions.supportedSchemes;
+ }
+
+ /**
+ * NOT SUPPORTED.
+ */
+ public async login() {
+ this.logger.debug('Login is not supported.');
+ return AuthenticationResult.notHandled();
+ }
+
+ /**
+ * Performs request authentication using provided `Authorization` HTTP headers.
+ * @param request Request instance.
+ */
+ public async authenticate(request: KibanaRequest) {
+ this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
+
+ const authenticationScheme = getHTTPAuthenticationScheme(request);
+ if (authenticationScheme == null) {
+ this.logger.debug('Authorization header is not presented.');
+ return AuthenticationResult.notHandled();
+ }
+
+ if (!this.supportedSchemes.has(authenticationScheme)) {
+ this.logger.debug(`Unsupported authentication scheme: ${authenticationScheme}`);
+ return AuthenticationResult.notHandled();
+ }
+
+ try {
+ const user = await this.getUser(request);
+ this.logger.debug(
+ `Request to ${request.url.path} has been authenticated via authorization header with "${authenticationScheme}" scheme.`
+ );
+ return AuthenticationResult.succeeded(user);
+ } catch (err) {
+ this.logger.debug(
+ `Failed to authenticate request to ${request.url.path} via authorization header with "${authenticationScheme}" scheme: ${err.message}`
+ );
+ return AuthenticationResult.failed(err);
+ }
+ }
+
+ /**
+ * NOT SUPPORTED.
+ */
+ public async logout() {
+ this.logger.debug('Logout is not supported.');
+ return DeauthenticationResult.notHandled();
+ }
+
+ /**
+ * Returns `null` since provider doesn't attach any additional `Authorization` HTTP headers to
+ * successfully authenticated requests to Elasticsearch.
+ */
+ public getHTTPAuthenticationScheme() {
+ return null;
+ }
+}
diff --git a/x-pack/plugins/security/server/authentication/providers/index.ts b/x-pack/plugins/security/server/authentication/providers/index.ts
index 1ec6dfb67a81d5..cd8f5a70c64e3d 100644
--- a/x-pack/plugins/security/server/authentication/providers/index.ts
+++ b/x-pack/plugins/security/server/authentication/providers/index.ts
@@ -15,3 +15,4 @@ export { SAMLAuthenticationProvider, isSAMLRequestQuery, SAMLLoginStep } from '.
export { TokenAuthenticationProvider } from './token';
export { OIDCAuthenticationProvider, OIDCAuthenticationFlow } from './oidc';
export { PKIAuthenticationProvider } from './pki';
+export { HTTPAuthenticationProvider } from './http';
diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts
index e4b4df3feeae21..51fb961482e83d 100644
--- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts
@@ -6,18 +6,31 @@
import Boom from 'boom';
import { errors } from 'elasticsearch';
-import sinon from 'sinon';
-import { httpServerMock } from '../../../../../../src/core/server/mocks';
+import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
-import {
- MockAuthenticationProviderOptions,
- mockAuthenticationProviderOptions,
- mockScopedClusterClient,
-} from './base.mock';
+import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock';
+import {
+ ElasticsearchErrorHelpers,
+ IClusterClient,
+ ScopeableRequest,
+} from '../../../../../../src/core/server';
+import { AuthenticationResult } from '../authentication_result';
+import { DeauthenticationResult } from '../deauthentication_result';
import { KerberosAuthenticationProvider } from './kerberos';
-import { ElasticsearchErrorHelpers } from '../../../../../../src/core/server/elasticsearch';
+
+function expectAuthenticateCall(
+ mockClusterClient: jest.Mocked,
+ scopeableRequest: ScopeableRequest
+) {
+ expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1);
+ expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest);
+
+ const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value;
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
+}
describe('KerberosAuthenticationProvider', () => {
let provider: KerberosAuthenticationProvider;
@@ -28,104 +41,128 @@ describe('KerberosAuthenticationProvider', () => {
});
describe('`authenticate` method', () => {
- it('does not handle `authorization` header with unsupported schema even if state contains a valid token.', async () => {
+ it('does not handle authentication via `authorization` header with non-negotiate scheme.', async () => {
+ const request = httpServerMock.createKibanaRequest({
+ headers: { authorization: 'Bearer some-token' },
+ });
+
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
+ });
+
+ it('does not handle authentication via `authorization` header with non-negotiate scheme even if state contains a valid token.', async () => {
const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Basic some:credentials' },
+ headers: { authorization: 'Bearer some-token' },
});
const tokenPair = {
accessToken: 'some-valid-token',
refreshToken: 'some-valid-refresh-token',
};
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
- sinon.assert.notCalled(mockOptions.client.asScoped);
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
- expect(request.headers.authorization).toBe('Basic some:credentials');
- expect(authenticationResult.notHandled()).toBe(true);
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
});
it('does not handle requests that can be authenticated without `Negotiate` header.', async () => {
- const request = httpServerMock.createKibanaRequest();
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({
- headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
- })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves({});
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
- const authenticationResult = await provider.authenticate(request, null);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({});
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
- expect(authenticationResult.notHandled()).toBe(true);
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
+ });
});
it('does not handle requests if backend does not support Kerberos.', async () => {
- const request = httpServerMock.createKibanaRequest();
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({
- headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
- })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()));
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
+
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
- const authenticationResult = await provider.authenticate(request, null);
- expect(authenticationResult.notHandled()).toBe(true);
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
+ });
});
it('fails if state is present, but backend does not support Kerberos.', async () => {
const request = httpServerMock.createKibanaRequest();
const tokenPair = { accessToken: 'token', refreshToken: 'refresh-token' };
- mockScopedClusterClient(mockOptions.client)
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()));
- mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null);
+ const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+ mockOptions.tokens.refresh.mockResolvedValue(null);
+
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
- const authenticationResult = await provider.authenticate(request, tokenPair);
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toHaveProperty('output.statusCode', 401);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
});
it('fails with `Negotiate` challenge if backend supports Kerberos.', async () => {
- const request = httpServerMock.createKibanaRequest();
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({
- headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
+
+ const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(
+ new (errors.AuthenticationException as any)('Unauthorized', {
+ body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } },
})
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(
- ElasticsearchErrorHelpers.decorateNotAuthorizedError(
- new (errors.AuthenticationException as any)('Unauthorized', {
- body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } },
- })
- )
- );
-
- const authenticationResult = await provider.authenticate(request, null);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toHaveProperty('output.statusCode', 401);
- expect(authenticationResult.authResponseHeaders).toEqual({ 'WWW-Authenticate': 'Negotiate' });
+ );
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason, {
+ authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' },
+ })
+ );
+
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
+ });
});
it('fails if request authentication is failed with non-401 error.', async () => {
- const request = httpServerMock.createKibanaRequest();
- mockScopedClusterClient(mockOptions.client)
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(new errors.ServiceUnavailable());
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
+
+ const failureReason = new errors.ServiceUnavailable();
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- const authenticationResult = await provider.authenticate(request, null);
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toHaveProperty('status', 503);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` },
+ });
});
it('gets a token pair in exchange to SPNEGO one and stores it in the state.', async () => {
@@ -134,34 +171,33 @@ describe('KerberosAuthenticationProvider', () => {
headers: { authorization: 'negotiate spnego' },
});
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer some-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
+ access_token: 'some-token',
+ refresh_token: 'some-refresh-token',
+ });
- mockOptions.client.callAsInternalUser
- .withArgs('shield.getAccessToken')
- .resolves({ access_token: 'some-token', refresh_token: 'some-refresh-token' });
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'kerberos' },
+ {
+ authHeaders: { authorization: 'Bearer some-token' },
+ state: { accessToken: 'some-token', refreshToken: 'some-refresh-token' },
+ }
+ )
+ );
- const authenticationResult = await provider.authenticate(request);
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: 'Bearer some-token' },
+ });
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.getAccessToken',
- { body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' } }
- );
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' },
+ });
expect(request.headers.authorization).toBe('negotiate spnego');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'kerberos' });
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer some-token' });
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
- expect(authenticationResult.state).toEqual({
- accessToken: 'some-token',
- refreshToken: 'some-refresh-token',
- });
});
it('requests auth response header if token pair is complemented with Kerberos response token.', async () => {
@@ -170,38 +206,35 @@ describe('KerberosAuthenticationProvider', () => {
headers: { authorization: 'negotiate spnego' },
});
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer some-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- mockOptions.client.callAsInternalUser.withArgs('shield.getAccessToken').resolves({
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
access_token: 'some-token',
refresh_token: 'some-refresh-token',
kerberos_authentication_response_token: 'response-token',
});
- const authenticationResult = await provider.authenticate(request);
-
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.getAccessToken',
- { body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' } }
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'kerberos' },
+ {
+ authHeaders: { authorization: 'Bearer some-token' },
+ authResponseHeaders: { 'WWW-Authenticate': 'Negotiate response-token' },
+ state: { accessToken: 'some-token', refreshToken: 'some-refresh-token' },
+ }
+ )
);
- expect(request.headers.authorization).toBe('negotiate spnego');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'kerberos' });
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer some-token' });
- expect(authenticationResult.authResponseHeaders).toEqual({
- 'WWW-Authenticate': 'Negotiate response-token',
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: 'Bearer some-token' },
});
- expect(authenticationResult.state).toEqual({
- accessToken: 'some-token',
- refreshToken: 'some-refresh-token',
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' },
});
+
+ expect(request.headers.authorization).toBe('negotiate spnego');
});
it('fails with `Negotiate response-token` if cannot complete context with a response token.', async () => {
@@ -214,24 +247,19 @@ describe('KerberosAuthenticationProvider', () => {
body: { error: { header: { 'WWW-Authenticate': 'Negotiate response-token' } } },
})
);
- mockOptions.client.callAsInternalUser
- .withArgs('shield.getAccessToken')
- .rejects(failureReason);
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- const authenticationResult = await provider.authenticate(request);
-
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.getAccessToken',
- { body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' } }
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.unauthorized(), {
+ authResponseHeaders: { 'WWW-Authenticate': 'Negotiate response-token' },
+ })
);
- expect(request.headers.authorization).toBe('negotiate spnego');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(Boom.unauthorized());
- expect(authenticationResult.authResponseHeaders).toEqual({
- 'WWW-Authenticate': 'Negotiate response-token',
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' },
});
+
+ expect(request.headers.authorization).toBe('negotiate spnego');
});
it('fails with `Negotiate` if cannot create context using provided SPNEGO token.', async () => {
@@ -244,24 +272,19 @@ describe('KerberosAuthenticationProvider', () => {
body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } },
})
);
- mockOptions.client.callAsInternalUser
- .withArgs('shield.getAccessToken')
- .rejects(failureReason);
-
- const authenticationResult = await provider.authenticate(request);
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.getAccessToken',
- { body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' } }
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.unauthorized(), {
+ authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' },
+ })
);
- expect(request.headers.authorization).toBe('negotiate spnego');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(Boom.unauthorized());
- expect(authenticationResult.authResponseHeaders).toEqual({
- 'WWW-Authenticate': 'Negotiate',
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' },
});
+
+ expect(request.headers.authorization).toBe('negotiate spnego');
});
it('fails if could not retrieve an access token in exchange to SPNEGO one.', async () => {
@@ -270,22 +293,17 @@ describe('KerberosAuthenticationProvider', () => {
});
const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
- mockOptions.client.callAsInternalUser
- .withArgs('shield.getAccessToken')
- .rejects(failureReason);
-
- const authenticationResult = await provider.authenticate(request);
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.getAccessToken',
- { body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' } }
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' },
+ });
+
expect(request.headers.authorization).toBe('negotiate spnego');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
});
it('fails if could not retrieve user using the new access token.', async () => {
@@ -294,51 +312,52 @@ describe('KerberosAuthenticationProvider', () => {
});
const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer some-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
-
- mockOptions.client.callAsInternalUser
- .withArgs('shield.getAccessToken')
- .resolves({ access_token: 'some-token', refresh_token: 'some-refresh-token' });
-
- const authenticationResult = await provider.authenticate(request);
-
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.getAccessToken',
- { body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' } }
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
+ access_token: 'some-token',
+ refresh_token: 'some-refresh-token',
+ });
+
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
);
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: 'Bearer some-token' },
+ });
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: '_kerberos', kerberos_ticket: 'spnego' },
+ });
+
expect(request.headers.authorization).toBe('negotiate spnego');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
});
it('succeeds if state contains a valid token.', async () => {
const user = mockAuthenticatedUser();
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const tokenPair = {
accessToken: 'some-valid-token',
refreshToken: 'some-valid-refresh-token',
};
const authorization = `Bearer ${tokenPair.accessToken}`;
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'kerberos' },
+ { authHeaders: { authorization } }
+ )
+ );
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toEqual({ authorization });
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'kerberos' });
- expect(authenticationResult.state).toBeUndefined();
});
it('succeeds with valid session even if requiring a token refresh', async () => {
@@ -346,149 +365,94 @@ describe('KerberosAuthenticationProvider', () => {
const request = httpServerMock.createKibanaRequest();
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()));
-
- mockOptions.tokens.refresh
- .withArgs(tokenPair.refreshToken)
- .resolves({ accessToken: 'newfoo', refreshToken: 'newbar' });
+ mockOptions.client.asScoped.mockImplementation(scopeableRequest => {
+ if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ return mockScopedClusterClient;
+ }
+
+ if (scopeableRequest?.headers.authorization === 'Bearer newfoo') {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ return mockScopedClusterClient;
+ }
+
+ throw new Error('Unexpected call');
+ });
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer newfoo' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ mockOptions.tokens.refresh.mockResolvedValue({
+ accessToken: 'newfoo',
+ refreshToken: 'newbar',
+ });
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'kerberos' },
+ {
+ authHeaders: { authorization: 'Bearer newfoo' },
+ state: { accessToken: 'newfoo', refreshToken: 'newbar' },
+ }
+ )
+ );
- sinon.assert.calledOnce(mockOptions.tokens.refresh);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer newfoo' });
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'kerberos' });
- expect(authenticationResult.state).toEqual({ accessToken: 'newfoo', refreshToken: 'newbar' });
expect(request.headers).not.toHaveProperty('authorization');
});
it('fails if token from the state is rejected because of unknown reason.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const tokenPair = {
accessToken: 'some-valid-token',
refreshToken: 'some-valid-refresh-token',
};
const failureReason = new errors.InternalServerError('Token is not valid!');
- const scopedClusterClient = mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
);
- scopedClusterClient.callAsCurrentUser.withArgs('shield.authenticate').rejects(failureReason);
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: `Bearer ${tokenPair.accessToken}` },
+ });
+
+ expect(mockScopedClusterClient.callAsInternalUser).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- sinon.assert.neverCalledWith(scopedClusterClient.callAsCurrentUser, 'shield.getAccessToken');
});
it('fails with `Negotiate` challenge if both access and refresh tokens from the state are expired and backend supports Kerberos.', async () => {
const request = httpServerMock.createKibanaRequest();
const tokenPair = { accessToken: 'expired-token', refreshToken: 'some-valid-refresh-token' };
- mockScopedClusterClient(mockOptions.client)
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(
- ElasticsearchErrorHelpers.decorateNotAuthorizedError(
- new (errors.AuthenticationException as any)('Unauthorized', {
- body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } },
- })
- )
- );
- mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null);
-
- const authenticationResult = await provider.authenticate(request, tokenPair);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toHaveProperty('output.statusCode', 401);
- expect(authenticationResult.authResponseHeaders).toEqual({ 'WWW-Authenticate': 'Negotiate' });
- });
-
- it('succeeds if `authorization` contains a valid token.', async () => {
- const user = mockAuthenticatedUser();
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Bearer some-valid-token' },
- });
-
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer some-valid-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request);
-
- expect(request.headers.authorization).toBe('Bearer some-valid-token');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toBeUndefined();
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'kerberos' });
- expect(authenticationResult.state).toBeUndefined();
- });
-
- it('fails if token from `authorization` header is rejected.', async () => {
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Bearer some-invalid-token' },
- });
-
- const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer some-invalid-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
-
- const authenticationResult = await provider.authenticate(request);
+ const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(
+ new (errors.AuthenticationException as any)('Unauthorized', {
+ body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } },
+ })
+ );
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- });
+ mockOptions.tokens.refresh.mockResolvedValue(null);
- it('fails if token from `authorization` header is rejected even if state contains a valid one.', async () => {
- const user = mockAuthenticatedUser();
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Bearer some-invalid-token' },
- });
- const tokenPair = {
- accessToken: 'some-valid-token',
- refreshToken: 'some-valid-refresh-token',
- };
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason, {
+ authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' },
+ })
+ );
- const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer some-invalid-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
-
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request, tokenPair);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
});
});
@@ -496,13 +460,13 @@ describe('KerberosAuthenticationProvider', () => {
it('returns `notHandled` if state is not presented.', async () => {
const request = httpServerMock.createKibanaRequest();
- let deauthenticateResult = await provider.logout(request);
- expect(deauthenticateResult.notHandled()).toBe(true);
+ await expect(provider.logout(request)).resolves.toEqual(DeauthenticationResult.notHandled());
- deauthenticateResult = await provider.logout(request, null);
- expect(deauthenticateResult.notHandled()).toBe(true);
+ await expect(provider.logout(request, null)).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
- sinon.assert.notCalled(mockOptions.tokens.invalidate);
+ expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled();
});
it('fails if `tokens.invalidate` fails', async () => {
@@ -510,15 +474,14 @@ describe('KerberosAuthenticationProvider', () => {
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
const failureReason = new Error('failed to delete token');
- mockOptions.tokens.invalidate.withArgs(tokenPair).rejects(failureReason);
-
- const authenticationResult = await provider.logout(request, tokenPair);
+ mockOptions.tokens.invalidate.mockRejectedValue(failureReason);
- sinon.assert.calledOnce(mockOptions.tokens.invalidate);
- sinon.assert.calledWithExactly(mockOptions.tokens.invalidate, tokenPair);
+ await expect(provider.logout(request, tokenPair)).resolves.toEqual(
+ DeauthenticationResult.failed(failureReason)
+ );
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair);
});
it('redirects to `/logged_out` page if tokens are invalidated successfully.', async () => {
@@ -528,15 +491,18 @@ describe('KerberosAuthenticationProvider', () => {
refreshToken: 'some-valid-refresh-token',
};
- mockOptions.tokens.invalidate.withArgs(tokenPair).resolves();
-
- const authenticationResult = await provider.logout(request, tokenPair);
+ mockOptions.tokens.invalidate.mockResolvedValue(undefined);
- sinon.assert.calledOnce(mockOptions.tokens.invalidate);
- sinon.assert.calledWithExactly(mockOptions.tokens.invalidate, tokenPair);
+ await expect(provider.logout(request, tokenPair)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out')
+ );
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair);
});
});
+
+ it('`getHTTPAuthenticationScheme` method', () => {
+ expect(provider.getHTTPAuthenticationScheme()).toBe('bearer');
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts
index b8e3b7bc237901..b6474a5e1d471d 100644
--- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts
+++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts
@@ -12,27 +12,15 @@ import {
} from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
-import { BaseAuthenticationProvider } from './base';
+import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
import { Tokens, TokenPair } from '../tokens';
+import { BaseAuthenticationProvider } from './base';
/**
* The state supported by the provider.
*/
type ProviderState = TokenPair;
-/**
- * Parses request's `Authorization` HTTP header if present and extracts authentication scheme.
- * @param request Request instance to extract authentication scheme for.
- */
-function getRequestAuthenticationScheme(request: KibanaRequest) {
- const authorization = request.headers.authorization;
- if (!authorization || typeof authorization !== 'string') {
- return '';
- }
-
- return authorization.split(/\s+/)[0].toLowerCase();
-}
-
/**
* Name of the `WWW-Authenticate` we parse out of Elasticsearch responses or/and return to the
* client to initiate or continue negotiation.
@@ -56,24 +44,15 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- const authenticationScheme = getRequestAuthenticationScheme(request);
- if (
- authenticationScheme &&
- authenticationScheme !== 'negotiate' &&
- authenticationScheme !== 'bearer'
- ) {
+ const authenticationScheme = getHTTPAuthenticationScheme(request);
+ if (authenticationScheme && authenticationScheme !== 'negotiate') {
this.logger.debug(`Unsupported authentication scheme: ${authenticationScheme}`);
return AuthenticationResult.notHandled();
}
- let authenticationResult = AuthenticationResult.notHandled();
- if (authenticationScheme) {
- // We should get rid of `Bearer` scheme support as soon as Reporting doesn't need it anymore.
- authenticationResult =
- authenticationScheme === 'bearer'
- ? await this.authenticateWithBearerScheme(request)
- : await this.authenticateWithNegotiateScheme(request);
- }
+ let authenticationResult = authenticationScheme
+ ? await this.authenticateWithNegotiateScheme(request)
+ : AuthenticationResult.notHandled();
if (state && authenticationResult.notHandled()) {
authenticationResult = await this.authenticateViaState(request, state);
@@ -115,6 +94,14 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
return DeauthenticationResult.redirectTo(`${this.options.basePath.serverBasePath}/logged_out`);
}
+ /**
+ * Returns HTTP authentication scheme (`Bearer`) that's used within `Authorization` HTTP header
+ * that provider attaches to all successfully authenticated requests to Elasticsearch.
+ */
+ public getHTTPAuthenticationScheme() {
+ return 'bearer';
+ }
+
/**
* Tries to authenticate request with `Negotiate ***` Authorization header by passing it to the Elasticsearch backend to
* get an access token in exchange.
@@ -201,26 +188,6 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider {
}
}
- /**
- * Tries to authenticate request with `Bearer ***` Authorization header by passing it to the Elasticsearch backend.
- * @param request Request instance.
- */
- private async authenticateWithBearerScheme(request: KibanaRequest) {
- this.logger.debug('Trying to authenticate request using "Bearer" authentication scheme.');
-
- try {
- const user = await this.getUser(request);
-
- this.logger.debug('Request has been authenticated using "Bearer" authentication scheme.');
- return AuthenticationResult.succeeded(user);
- } catch (err) {
- this.logger.debug(
- `Failed to authenticate request using "Bearer" authentication scheme: ${err.message}`
- );
- return AuthenticationResult.failed(err);
- }
- }
-
/**
* Tries to extract access token from state and adds it to the request before it's
* forwarded to Elasticsearch backend.
diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts
index dae37749558591..51a25825bf985a 100644
--- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts
@@ -4,20 +4,34 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import sinon from 'sinon';
import Boom from 'boom';
-import { httpServerMock } from '../../../../../../src/core/server/mocks';
+import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
-import {
- MockAuthenticationProviderOptions,
- mockAuthenticationProviderOptions,
- mockScopedClusterClient,
-} from './base.mock';
+import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock';
-import { KibanaRequest } from '../../../../../../src/core/server';
+import {
+ ElasticsearchErrorHelpers,
+ IClusterClient,
+ KibanaRequest,
+ ScopeableRequest,
+} from '../../../../../../src/core/server';
+import { AuthenticationResult } from '../authentication_result';
+import { DeauthenticationResult } from '../deauthentication_result';
import { OIDCAuthenticationProvider, OIDCAuthenticationFlow, ProviderLoginAttempt } from './oidc';
+function expectAuthenticateCall(
+ mockClusterClient: jest.Mocked,
+ scopeableRequest: ScopeableRequest
+) {
+ expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1);
+ expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest);
+
+ const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value;
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
+}
+
describe('OIDCAuthenticationProvider', () => {
let provider: OIDCAuthenticationProvider;
let mockOptions: MockAuthenticationProviderOptions;
@@ -44,7 +58,7 @@ describe('OIDCAuthenticationProvider', () => {
it('redirects third party initiated login attempts to the OpenId Connect Provider.', async () => {
const request = httpServerMock.createKibanaRequest({ path: '/api/security/oidc/callback' });
- mockOptions.client.callAsInternalUser.withArgs('shield.oidcPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
state: 'statevalue',
nonce: 'noncevalue',
redirect:
@@ -56,30 +70,27 @@ describe('OIDCAuthenticationProvider', () => {
'&login_hint=loginhint',
});
- const authenticationResult = await provider.login(request, {
- flow: OIDCAuthenticationFlow.InitiatedBy3rdParty,
- iss: 'theissuer',
- loginHint: 'loginhint',
- });
+ await expect(
+ provider.login(request, {
+ flow: OIDCAuthenticationFlow.InitiatedBy3rdParty,
+ iss: 'theissuer',
+ loginHint: 'loginhint',
+ })
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ 'https://op-host/path/login?response_type=code' +
+ '&scope=openid%20profile%20email' +
+ '&client_id=s6BhdRkqt3' +
+ '&state=statevalue' +
+ '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc' +
+ '&login_hint=loginhint',
+ { state: { state: 'statevalue', nonce: 'noncevalue', nextURL: '/base-path/' } }
+ )
+ );
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.oidcPrepare', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcPrepare', {
body: { iss: 'theissuer', login_hint: 'loginhint' },
});
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- 'https://op-host/path/login?response_type=code' +
- '&scope=openid%20profile%20email' +
- '&client_id=s6BhdRkqt3' +
- '&state=statevalue' +
- '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc' +
- '&login_hint=loginhint'
- );
- expect(authenticationResult.state).toEqual({
- state: 'statevalue',
- nonce: 'noncevalue',
- nextURL: '/base-path/',
- });
});
function defineAuthenticationFlowTests(
@@ -92,18 +103,24 @@ describe('OIDCAuthenticationProvider', () => {
it('gets token and redirects user to requested URL if OIDC authentication response is valid.', async () => {
const { request, attempt, expectedRedirectURI } = getMocks();
- mockOptions.client.callAsInternalUser
- .withArgs('shield.oidcAuthenticate')
- .resolves({ access_token: 'some-token', refresh_token: 'some-refresh-token' });
-
- const authenticationResult = await provider.login(request, attempt, {
- state: 'statevalue',
- nonce: 'noncevalue',
- nextURL: '/base-path/some-path',
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
+ access_token: 'some-token',
+ refresh_token: 'some-refresh-token',
});
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ await expect(
+ provider.login(request, attempt, {
+ state: 'statevalue',
+ nonce: 'noncevalue',
+ nextURL: '/base-path/some-path',
+ })
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo('/base-path/some-path', {
+ state: { accessToken: 'some-token', refreshToken: 'some-refresh-token' },
+ })
+ );
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.oidcAuthenticate',
{
body: {
@@ -114,58 +131,52 @@ describe('OIDCAuthenticationProvider', () => {
},
}
);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/base-path/some-path');
- expect(authenticationResult.state).toEqual({
- accessToken: 'some-token',
- refreshToken: 'some-refresh-token',
- });
});
it('fails if authentication response is presented but session state does not contain the state parameter.', async () => {
const { request, attempt } = getMocks();
- const authenticationResult = await provider.login(request, attempt, {
- nextURL: '/base-path/some-path',
- });
-
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(
- Boom.badRequest(
- 'Response session state does not have corresponding state or nonce parameters or redirect URL.'
+ await expect(
+ provider.login(request, attempt, { nextURL: '/base-path/some-path' })
+ ).resolves.toEqual(
+ AuthenticationResult.failed(
+ Boom.badRequest(
+ 'Response session state does not have corresponding state or nonce parameters or redirect URL.'
+ )
)
);
+
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('fails if authentication response is presented but session state does not contain redirect URL.', async () => {
const { request, attempt } = getMocks();
- const authenticationResult = await provider.login(request, attempt, {
- state: 'statevalue',
- nonce: 'noncevalue',
- });
-
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(
- Boom.badRequest(
- 'Response session state does not have corresponding state or nonce parameters or redirect URL.'
+ await expect(
+ provider.login(request, attempt, { state: 'statevalue', nonce: 'noncevalue' })
+ ).resolves.toEqual(
+ AuthenticationResult.failed(
+ Boom.badRequest(
+ 'Response session state does not have corresponding state or nonce parameters or redirect URL.'
+ )
)
);
+
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('fails if session state is not presented.', async () => {
const { request, attempt } = getMocks();
- const authenticationResult = await provider.login(request, attempt, {});
-
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
+ await expect(provider.login(request, attempt, {})).resolves.toEqual(
+ AuthenticationResult.failed(
+ Boom.badRequest(
+ 'Response session state does not have corresponding state or nonce parameters or redirect URL.'
+ )
+ )
+ );
- expect(authenticationResult.failed()).toBe(true);
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('fails if authentication response is not valid.', async () => {
@@ -174,18 +185,17 @@ describe('OIDCAuthenticationProvider', () => {
const failureReason = new Error(
'Failed to exchange code for Id Token using the Token Endpoint.'
);
- mockOptions.client.callAsInternalUser
- .withArgs('shield.oidcAuthenticate')
- .returns(Promise.reject(failureReason));
-
- const authenticationResult = await provider.login(request, attempt, {
- state: 'statevalue',
- nonce: 'noncevalue',
- nextURL: '/base-path/some-path',
- });
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ await expect(
+ provider.login(request, attempt, {
+ state: 'statevalue',
+ nonce: 'noncevalue',
+ nextURL: '/base-path/some-path',
+ })
+ ).resolves.toEqual(AuthenticationResult.failed(failureReason));
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.oidcAuthenticate',
{
body: {
@@ -196,9 +206,6 @@ describe('OIDCAuthenticationProvider', () => {
},
}
);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
}
@@ -234,16 +241,15 @@ describe('OIDCAuthenticationProvider', () => {
describe('`authenticate` method', () => {
it('does not handle AJAX request that can not be authenticated.', async () => {
const request = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } });
-
- const authenticationResult = await provider.authenticate(request, null);
-
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
});
it('redirects non-AJAX request that can not be authenticated to the OpenId Connect Provider.', async () => {
const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' });
- mockOptions.client.callAsInternalUser.withArgs('shield.oidcPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
state: 'statevalue',
nonce: 'noncevalue',
redirect:
@@ -254,84 +260,99 @@ describe('OIDCAuthenticationProvider', () => {
'&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc',
});
- const authenticationResult = await provider.authenticate(request, null);
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ 'https://op-host/path/login?response_type=code' +
+ '&scope=openid%20profile%20email' +
+ '&client_id=s6BhdRkqt3' +
+ '&state=statevalue' +
+ '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc',
+ {
+ state: {
+ state: 'statevalue',
+ nonce: 'noncevalue',
+ nextURL: '/base-path/s/foo/some-path',
+ },
+ }
+ )
+ );
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.oidcPrepare', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcPrepare', {
body: { realm: `oidc1` },
});
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- 'https://op-host/path/login?response_type=code' +
- '&scope=openid%20profile%20email' +
- '&client_id=s6BhdRkqt3' +
- '&state=statevalue' +
- '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc'
- );
- expect(authenticationResult.state).toEqual({
- state: 'statevalue',
- nonce: 'noncevalue',
- nextURL: '/base-path/s/foo/some-path',
- });
});
it('fails if OpenID Connect authentication request preparation fails.', async () => {
const request = httpServerMock.createKibanaRequest({ path: '/some-path' });
const failureReason = new Error('Realm is misconfigured!');
- mockOptions.client.callAsInternalUser
- .withArgs('shield.oidcPrepare')
- .returns(Promise.reject(failureReason));
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- const authenticationResult = await provider.authenticate(request, null);
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.oidcPrepare', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcPrepare', {
body: { realm: `oidc1` },
});
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('succeeds if state contains a valid token.', async () => {
const user = mockAuthenticatedUser();
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const tokenPair = {
accessToken: 'some-valid-token',
refreshToken: 'some-valid-refresh-token',
};
const authorization = `Bearer ${tokenPair.accessToken}`;
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'oidc' },
+ { authHeaders: { authorization } }
+ )
+ );
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toEqual({ authorization });
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'oidc' });
- expect(authenticationResult.state).toBeUndefined();
});
- it('does not handle `authorization` header with unsupported schema even if state contains a valid token.', async () => {
+ it('does not handle authentication via `authorization` header.', async () => {
const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Basic some:credentials' },
+ headers: { authorization: 'Bearer some-token' },
});
- const authenticationResult = await provider.authenticate(request, {
- accessToken: 'some-valid-token',
- refreshToken: 'some-valid-refresh-token',
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
+ });
+
+ it('does not handle authentication via `authorization` header even if state contains a valid token.', async () => {
+ const request = httpServerMock.createKibanaRequest({
+ headers: { authorization: 'Bearer some-token' },
});
- sinon.assert.notCalled(mockOptions.client.asScoped);
- expect(request.headers.authorization).toBe('Basic some:credentials');
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(
+ provider.authenticate(request, {
+ accessToken: 'some-valid-token',
+ refreshToken: 'some-valid-refresh-token',
+ })
+ ).resolves.toEqual(AuthenticationResult.notHandled());
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
});
it('fails if token from the state is rejected because of unknown reason.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const tokenPair = {
accessToken: 'some-invalid-token',
refreshToken: 'some-invalid-refresh-token',
@@ -339,15 +360,17 @@ describe('OIDCAuthenticationProvider', () => {
const authorization = `Bearer ${tokenPair.accessToken}`;
const failureReason = new Error('Token is not valid!');
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('succeeds if token from the state is expired, but has been successfully refreshed.', async () => {
@@ -355,67 +378,80 @@ describe('OIDCAuthenticationProvider', () => {
const request = httpServerMock.createKibanaRequest();
const tokenPair = { accessToken: 'expired-token', refreshToken: 'valid-refresh-token' };
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ mockOptions.client.asScoped.mockImplementation(scopeableRequest => {
+ if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ return mockScopedClusterClient;
+ }
+
+ if (scopeableRequest?.headers.authorization === 'Bearer new-access-token') {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ return mockScopedClusterClient;
+ }
+
+ throw new Error('Unexpected call');
+ });
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer new-access-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ mockOptions.tokens.refresh.mockResolvedValue({
+ accessToken: 'new-access-token',
+ refreshToken: 'new-refresh-token',
+ });
- mockOptions.tokens.refresh
- .withArgs(tokenPair.refreshToken)
- .resolves({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token' });
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'oidc' },
+ {
+ authHeaders: { authorization: 'Bearer new-access-token' },
+ state: { accessToken: 'new-access-token', refreshToken: 'new-refresh-token' },
+ }
+ )
+ );
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toEqual({
- authorization: 'Bearer new-access-token',
- });
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'oidc' });
- expect(authenticationResult.state).toEqual({
- accessToken: 'new-access-token',
- refreshToken: 'new-refresh-token',
- });
});
it('fails if token from the state is expired and refresh attempt failed too.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const tokenPair = { accessToken: 'expired-token', refreshToken: 'invalid-refresh-token' };
+ const authorization = `Bearer ${tokenPair.accessToken}`;
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
const refreshFailureReason = {
statusCode: 500,
message: 'Something is wrong with refresh token.',
};
- mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).rejects(refreshFailureReason);
+ mockOptions.tokens.refresh.mockRejectedValue(refreshFailureReason);
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(refreshFailureReason as any)
+ );
+
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(refreshFailureReason);
});
it('redirects to OpenID Connect Provider for non-AJAX requests if refresh token is expired or already refreshed.', async () => {
- const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' });
+ const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path', headers: {} });
const tokenPair = { accessToken: 'expired-token', refreshToken: 'expired-refresh-token' };
+ const authorization = `Bearer ${tokenPair.accessToken}`;
- mockOptions.client.callAsInternalUser.withArgs('shield.oidcPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
state: 'statevalue',
nonce: 'noncevalue',
redirect:
@@ -426,115 +462,71 @@ describe('OIDCAuthenticationProvider', () => {
'&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc',
});
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null);
+ mockOptions.tokens.refresh.mockResolvedValue(null);
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ 'https://op-host/path/login?response_type=code' +
+ '&scope=openid%20profile%20email' +
+ '&client_id=s6BhdRkqt3' +
+ '&state=statevalue' +
+ '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc',
+ {
+ state: {
+ state: 'statevalue',
+ nonce: 'noncevalue',
+ nextURL: '/base-path/s/foo/some-path',
+ },
+ }
+ )
+ );
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.oidcPrepare', {
- body: { realm: `oidc1` },
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
+
+ expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.asScoped).toHaveBeenCalledWith({
+ headers: { authorization },
});
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- 'https://op-host/path/login?response_type=code' +
- '&scope=openid%20profile%20email' +
- '&client_id=s6BhdRkqt3' +
- '&state=statevalue' +
- '&redirect_uri=https%3A%2F%2Ftest-hostname:1234%2Ftest-base-path%2Fapi%2Fsecurity%2Fv1%2F/oidc'
- );
- expect(authenticationResult.state).toEqual({
- state: 'statevalue',
- nonce: 'noncevalue',
- nextURL: '/base-path/s/foo/some-path',
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcPrepare', {
+ body: { realm: `oidc1` },
});
});
it('fails for AJAX requests with user friendly message if refresh token is expired.', async () => {
const request = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } });
const tokenPair = { accessToken: 'expired-token', refreshToken: 'expired-refresh-token' };
+ const authorization = `Bearer ${tokenPair.accessToken}`;
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
-
- mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null);
-
- const authenticationResult = await provider.authenticate(request, tokenPair);
-
- expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(
- Boom.badRequest('Both access and refresh tokens are expired.')
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
);
- });
-
- it('succeeds if `authorization` contains a valid token.', async () => {
- const user = mockAuthenticatedUser();
- const authorization = 'Bearer some-valid-token';
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
-
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- const authenticationResult = await provider.authenticate(request);
+ mockOptions.tokens.refresh.mockResolvedValue(null);
- expect(request.headers.authorization).toBe('Bearer some-valid-token');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toBeUndefined();
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'oidc' });
- expect(authenticationResult.state).toBeUndefined();
- });
-
- it('fails if token from `authorization` header is rejected.', async () => {
- const authorization = 'Bearer some-invalid-token';
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
-
- const failureReason = { statusCode: 401 };
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.badRequest('Both access and refresh tokens are expired.'))
+ );
- const authenticationResult = await provider.authenticate(request);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- });
-
- it('fails if token from `authorization` header is rejected even if state contains a valid one.', async () => {
- const user = mockAuthenticatedUser();
- const authorization = 'Bearer some-invalid-token';
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
-
- const failureReason = { statusCode: 401 };
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
-
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer some-valid-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request, {
- accessToken: 'some-valid-token',
- refreshToken: 'some-valid-refresh-token',
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { 'kbn-xsrf': 'xsrf', authorization },
});
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
+ expect(request.headers).not.toHaveProperty('authorization');
});
});
@@ -542,16 +534,19 @@ describe('OIDCAuthenticationProvider', () => {
it('returns `notHandled` if state is not presented or does not include access token.', async () => {
const request = httpServerMock.createKibanaRequest();
- let deauthenticateResult = await provider.logout(request, {});
- expect(deauthenticateResult.notHandled()).toBe(true);
+ await expect(provider.logout(request, undefined as any)).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
- deauthenticateResult = await provider.logout(request, {});
- expect(deauthenticateResult.notHandled()).toBe(true);
+ await expect(provider.logout(request, {})).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
- deauthenticateResult = await provider.logout(request, { nonce: 'x' });
- expect(deauthenticateResult.notHandled()).toBe(true);
+ await expect(provider.logout(request, { nonce: 'x' })).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('fails if OpenID Connect logout call fails.', async () => {
@@ -560,22 +555,16 @@ describe('OIDCAuthenticationProvider', () => {
const refreshToken = 'x-oidc-refresh-token';
const failureReason = new Error('Realm is misconfigured!');
- mockOptions.client.callAsInternalUser
- .withArgs('shield.oidcLogout')
- .returns(Promise.reject(failureReason));
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- const authenticationResult = await provider.logout(request, {
- accessToken,
- refreshToken,
- });
+ await expect(provider.logout(request, { accessToken, refreshToken })).resolves.toEqual(
+ DeauthenticationResult.failed(failureReason)
+ );
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.oidcLogout', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', {
body: { token: accessToken, refresh_token: refreshToken },
});
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('redirects to /logged_out if `redirect` field in OpenID Connect logout response is null.', async () => {
@@ -583,22 +572,16 @@ describe('OIDCAuthenticationProvider', () => {
const accessToken = 'x-oidc-token';
const refreshToken = 'x-oidc-refresh-token';
- mockOptions.client.callAsInternalUser
- .withArgs('shield.oidcLogout')
- .resolves({ redirect: null });
+ mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null });
- const authenticationResult = await provider.logout(request, {
- accessToken,
- refreshToken,
- });
+ await expect(provider.logout(request, { accessToken, refreshToken })).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out')
+ );
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.oidcLogout', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', {
body: { token: accessToken, refresh_token: refreshToken },
});
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
});
it('redirects user to the OpenID Connect Provider if RP initiated SLO is supported.', async () => {
@@ -606,18 +589,22 @@ describe('OIDCAuthenticationProvider', () => {
const accessToken = 'x-oidc-token';
const refreshToken = 'x-oidc-refresh-token';
- mockOptions.client.callAsInternalUser
- .withArgs('shield.oidcLogout')
- .resolves({ redirect: 'http://fake-idp/logout&id_token_hint=thehint' });
-
- const authenticationResult = await provider.logout(request, {
- accessToken,
- refreshToken,
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
+ redirect: 'http://fake-idp/logout&id_token_hint=thehint',
});
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('http://fake-idp/logout&id_token_hint=thehint');
+ await expect(provider.logout(request, { accessToken, refreshToken })).resolves.toEqual(
+ DeauthenticationResult.redirectTo('http://fake-idp/logout&id_token_hint=thehint')
+ );
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', {
+ body: { token: accessToken, refresh_token: refreshToken },
+ });
});
});
+
+ it('`getHTTPAuthenticationScheme` method', () => {
+ expect(provider.getHTTPAuthenticationScheme()).toBe('bearer');
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts
index f13a2ec05231a2..c6b504e722adf4 100644
--- a/x-pack/plugins/security/server/authentication/providers/oidc.ts
+++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts
@@ -10,6 +10,7 @@ import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { canRedirectRequest } from '../can_redirect_request';
import { DeauthenticationResult } from '../deauthentication_result';
+import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
import { Tokens, TokenPair } from '../tokens';
import {
AuthenticationProviderOptions,
@@ -130,16 +131,13 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- // We should get rid of `Bearer` scheme support as soon as Reporting doesn't need it anymore.
- let {
- authenticationResult,
- headerNotRecognized, // eslint-disable-line prefer-const
- } = await this.authenticateViaHeader(request);
- if (headerNotRecognized) {
- return authenticationResult;
+ if (getHTTPAuthenticationScheme(request) != null) {
+ this.logger.debug('Cannot authenticate requests with `Authorization` header.');
+ return AuthenticationResult.notHandled();
}
- if (state && authenticationResult.notHandled()) {
+ let authenticationResult = AuthenticationResult.notHandled();
+ if (state) {
authenticationResult = await this.authenticateViaState(request, state);
if (
authenticationResult.failed() &&
@@ -276,46 +274,6 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider {
}
}
- /**
- * Validates whether request contains `Bearer ***` Authorization header and just passes it
- * forward to Elasticsearch backend.
- * @param request Request instance.
- */
- private async authenticateViaHeader(request: KibanaRequest) {
- this.logger.debug('Trying to authenticate via header.');
-
- const authorization = request.headers.authorization;
- if (!authorization || typeof authorization !== 'string') {
- this.logger.debug('Authorization header is not presented.');
- return {
- authenticationResult: AuthenticationResult.notHandled(),
- };
- }
-
- const authenticationSchema = authorization.split(/\s+/)[0];
- if (authenticationSchema.toLowerCase() !== 'bearer') {
- this.logger.debug(`Unsupported authentication schema: ${authenticationSchema}`);
- return {
- authenticationResult: AuthenticationResult.notHandled(),
- headerNotRecognized: true,
- };
- }
-
- try {
- const user = await this.getUser(request);
-
- this.logger.debug('Request has been authenticated via header.');
- return {
- authenticationResult: AuthenticationResult.succeeded(user),
- };
- } catch (err) {
- this.logger.debug(`Failed to authenticate request via header: ${err.message}`);
- return {
- authenticationResult: AuthenticationResult.failed(err),
- };
- }
- }
-
/**
* Tries to extract an elasticsearch access token from state and adds it to the request before it's
* forwarded to Elasticsearch backend.
@@ -444,4 +402,12 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider {
return DeauthenticationResult.failed(err);
}
}
+
+ /**
+ * Returns HTTP authentication scheme (`Bearer`) that's used within `Authorization` HTTP header
+ * that provider attaches to all successfully authenticated requests to Elasticsearch.
+ */
+ public getHTTPAuthenticationScheme() {
+ return 'bearer';
+ }
}
diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts
index a2dda88c4680c3..efc286c6c895f7 100644
--- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts
@@ -7,23 +7,23 @@
jest.mock('net');
jest.mock('tls');
+import { Socket } from 'net';
import { PeerCertificate, TLSSocket } from 'tls';
+import Boom from 'boom';
import { errors } from 'elasticsearch';
import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
-import {
- MockAuthenticationProviderOptionsWithJest,
- mockAuthenticationProviderOptionsWithJest,
-} from './base.mock';
+import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock';
-import { PKIAuthenticationProvider } from './pki';
import {
ElasticsearchErrorHelpers,
- ScopedClusterClient,
-} from '../../../../../../src/core/server/elasticsearch';
-import { Socket } from 'net';
-import { getErrorStatusCode } from '../../errors';
+ IClusterClient,
+ ScopeableRequest,
+} from '../../../../../../src/core/server';
+import { AuthenticationResult } from '../authentication_result';
+import { DeauthenticationResult } from '../deauthentication_result';
+import { PKIAuthenticationProvider } from './pki';
interface MockPeerCertificate extends Partial {
issuerCertificate: MockPeerCertificate;
@@ -62,32 +62,59 @@ function getMockSocket({
return socket;
}
+function expectAuthenticateCall(
+ mockClusterClient: jest.Mocked,
+ scopeableRequest: ScopeableRequest
+) {
+ expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1);
+ expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest);
+
+ const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value;
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
+}
+
describe('PKIAuthenticationProvider', () => {
let provider: PKIAuthenticationProvider;
- let mockOptions: MockAuthenticationProviderOptionsWithJest;
+ let mockOptions: MockAuthenticationProviderOptions;
beforeEach(() => {
- mockOptions = mockAuthenticationProviderOptionsWithJest();
+ mockOptions = mockAuthenticationProviderOptions();
provider = new PKIAuthenticationProvider(mockOptions);
});
afterEach(() => jest.clearAllMocks());
describe('`authenticate` method', () => {
- it('does not handle `authorization` header with unsupported schema even if state contains a valid token.', async () => {
+ it('does not handle authentication via `authorization` header.', async () => {
+ const request = httpServerMock.createKibanaRequest({
+ headers: { authorization: 'Bearer some-token' },
+ });
+
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
+ });
+
+ it('does not handle authentication via `authorization` header even if state contains a valid token.', async () => {
const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Basic some:credentials' },
+ headers: { authorization: 'Bearer some-token' },
});
const state = {
accessToken: 'some-valid-token',
peerCertificateFingerprint256: '2A:7A:C2:DD',
};
- const authenticationResult = await provider.authenticate(request, state);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
- expect(request.headers.authorization).toBe('Basic some:credentials');
- expect(authenticationResult.notHandled()).toBe(true);
+ expect(request.headers.authorization).toBe('Bearer some-token');
});
it('does not handle requests without certificate.', async () => {
@@ -95,9 +122,10 @@ describe('PKIAuthenticationProvider', () => {
socket: getMockSocket({ authorized: true }),
});
- const authenticationResult = await provider.authenticate(request, null);
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
- expect(authenticationResult.notHandled()).toBe(true);
expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
@@ -107,9 +135,10 @@ describe('PKIAuthenticationProvider', () => {
socket: getMockSocket({ peerCertificate: getMockPeerCertificate('2A:7A:C2:DD') }),
});
- const authenticationResult = await provider.authenticate(request, null);
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
- expect(authenticationResult.notHandled()).toBe(true);
expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
@@ -121,12 +150,10 @@ describe('PKIAuthenticationProvider', () => {
const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' };
- const authenticationResult = await provider.authenticate(request, state);
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toMatchInlineSnapshot(
- `[Error: Peer certificate is not available]`
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.failed(new Error('Peer certificate is not available'))
);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
+
expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled();
});
@@ -134,10 +161,9 @@ describe('PKIAuthenticationProvider', () => {
const request = httpServerMock.createKibanaRequest({ socket: getMockSocket() });
const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' };
- const authenticationResult = await provider.authenticate(request, state);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(getErrorStatusCode(authenticationResult.error)).toBe(401);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.unauthorized())
+ );
expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({
@@ -151,10 +177,9 @@ describe('PKIAuthenticationProvider', () => {
});
const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' };
- const authenticationResult = await provider.authenticate(request, state);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(getErrorStatusCode(authenticationResult.error)).toBe(401);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.unauthorized())
+ );
expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({
@@ -174,12 +199,18 @@ describe('PKIAuthenticationProvider', () => {
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' });
- const authenticationResult = await provider.authenticate(request);
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'pki' },
+ {
+ authHeaders: { authorization: 'Bearer access-token' },
+ state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
+ }
+ )
+ );
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', {
@@ -191,22 +222,11 @@ describe('PKIAuthenticationProvider', () => {
},
});
- expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1);
- expect(mockOptions.client.asScoped).toHaveBeenCalledWith({
- headers: { authorization: `Bearer access-token` },
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: 'Bearer access-token' },
});
- expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
- expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'pki' });
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer access-token' });
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
- expect(authenticationResult.state).toEqual({
- accessToken: 'access-token',
- peerCertificateFingerprint256: '2A:7A:C2:DD',
- });
});
it('gets an access token in exchange to a self-signed certificate and stores it in the state.', async () => {
@@ -221,34 +241,29 @@ describe('PKIAuthenticationProvider', () => {
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' });
- const authenticationResult = await provider.authenticate(request);
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'pki' },
+ {
+ authHeaders: { authorization: 'Bearer access-token' },
+ state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
+ }
+ )
+ );
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', {
body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] },
});
- expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1);
- expect(mockOptions.client.asScoped).toHaveBeenCalledWith({
- headers: { authorization: `Bearer access-token` },
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: 'Bearer access-token' },
});
- expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
- expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'pki' });
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer access-token' });
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
- expect(authenticationResult.state).toEqual({
- accessToken: 'access-token',
- peerCertificateFingerprint256: '2A:7A:C2:DD',
- });
});
it('invalidates existing token and gets a new one if fingerprints do not match.', async () => {
@@ -263,12 +278,18 @@ describe('PKIAuthenticationProvider', () => {
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' });
- const authenticationResult = await provider.authenticate(request, state);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'pki' },
+ {
+ authHeaders: { authorization: 'Bearer access-token' },
+ state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
+ }
+ )
+ );
expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({
@@ -286,14 +307,6 @@ describe('PKIAuthenticationProvider', () => {
});
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'pki' });
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer access-token' });
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
- expect(authenticationResult.state).toEqual({
- accessToken: 'access-token',
- peerCertificateFingerprint256: '2A:7A:C2:DD',
- });
});
it('gets a new access token even if existing token is expired.', async () => {
@@ -312,12 +325,18 @@ describe('PKIAuthenticationProvider', () => {
.mockRejectedValueOnce(ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()))
// In response to a call with a new token.
.mockResolvedValueOnce(user);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' });
- const authenticationResult = await provider.authenticate(request, state);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'pki' },
+ {
+ authHeaders: { authorization: 'Bearer access-token' },
+ state: { accessToken: 'access-token', peerCertificateFingerprint256: '2A:7A:C2:DD' },
+ }
+ )
+ );
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', {
@@ -330,14 +349,6 @@ describe('PKIAuthenticationProvider', () => {
});
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'pki' });
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer access-token' });
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
- expect(authenticationResult.state).toEqual({
- accessToken: 'access-token',
- peerCertificateFingerprint256: '2A:7A:C2:DD',
- });
});
it('fails with 401 if existing token is expired, but certificate is not present.', async () => {
@@ -348,18 +359,15 @@ describe('PKIAuthenticationProvider', () => {
mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- const authenticationResult = await provider.authenticate(request, state);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.unauthorized())
+ );
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(getErrorStatusCode(authenticationResult.error)).toBe(401);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
});
it('fails if could not retrieve an access token in exchange to peer certificate chain.', async () => {
@@ -373,7 +381,9 @@ describe('PKIAuthenticationProvider', () => {
const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- const authenticationResult = await provider.authenticate(request);
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', {
@@ -381,9 +391,6 @@ describe('PKIAuthenticationProvider', () => {
});
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
});
it('fails if could not retrieve user using the new access token.', async () => {
@@ -398,35 +405,30 @@ describe('PKIAuthenticationProvider', () => {
const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
mockOptions.client.callAsInternalUser.mockResolvedValue({ access_token: 'access-token' });
- const authenticationResult = await provider.authenticate(request);
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.delegatePKI', {
body: { x509_certificate_chain: ['fingerprint:2A:7A:C2:DD:base64'] },
});
- expect(mockOptions.client.asScoped).toHaveBeenCalledTimes(1);
- expect(mockOptions.client.asScoped).toHaveBeenCalledWith({
- headers: { authorization: `Bearer access-token` },
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { authorization: 'Bearer access-token' },
});
- expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
- expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
});
it('succeeds if state contains a valid token.', async () => {
const user = mockAuthenticatedUser();
const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' };
const request = httpServerMock.createKibanaRequest({
+ headers: {},
socket: getMockSocket({
authorized: true,
peerCertificate: getMockPeerCertificate(state.peerCertificateFingerprint256),
@@ -435,110 +437,42 @@ describe('PKIAuthenticationProvider', () => {
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'pki' },
+ { authHeaders: { authorization: `Bearer ${state.accessToken}` } }
+ )
);
- const authenticationResult = await provider.authenticate(request, state);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization: 'Bearer token' } });
expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toEqual({
- authorization: `Bearer ${state.accessToken}`,
- });
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'pki' });
- expect(authenticationResult.state).toBeUndefined();
});
it('fails if token from the state is rejected because of unknown reason.', async () => {
const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' };
const request = httpServerMock.createKibanaRequest({
+ headers: {},
socket: getMockSocket({
authorized: true,
peerCertificate: getMockPeerCertificate(state.peerCertificateFingerprint256),
}),
});
- const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
- mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(new errors.ServiceUnavailable());
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
-
- const authenticationResult = await provider.authenticate(request, state);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toHaveProperty('status', 503);
- expect(authenticationResult.authResponseHeaders).toBeUndefined();
- });
-
- it('succeeds if `authorization` contains a valid token.', async () => {
- const user = mockAuthenticatedUser();
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Bearer some-valid-token' },
- });
-
- const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
- mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
-
- const authenticationResult = await provider.authenticate(request);
-
- expect(request.headers.authorization).toBe('Bearer some-valid-token');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toBeUndefined();
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'pki' });
- expect(authenticationResult.state).toBeUndefined();
- });
-
- it('fails if token from `authorization` header is rejected.', async () => {
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Bearer some-invalid-token' },
- });
-
- const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
+ const failureReason = new errors.ServiceUnavailable();
const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
- );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- const authenticationResult = await provider.authenticate(request);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- });
-
- it('fails if token from `authorization` header is rejected even if state contains a valid one.', async () => {
- const user = mockAuthenticatedUser();
- const state = { accessToken: 'token', peerCertificateFingerprint256: '2A:7A:C2:DD' };
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Bearer some-invalid-token' },
- socket: getMockSocket({
- authorized: true,
- peerCertificate: getMockPeerCertificate(state.peerCertificateFingerprint256),
- }),
- });
-
- const failureReason = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error());
- const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
- mockScopedClusterClient.callAsCurrentUser
- // In response to call with a token from header.
- .mockRejectedValueOnce(failureReason)
- // In response to a call with a token from session (not expected to be called).
- .mockResolvedValueOnce(user);
- mockOptions.client.asScoped.mockReturnValue(
- (mockScopedClusterClient as unknown) as jest.Mocked
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
);
- const authenticationResult = await provider.authenticate(request, state);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization: 'Bearer token' } });
});
});
@@ -546,11 +480,11 @@ describe('PKIAuthenticationProvider', () => {
it('returns `notHandled` if state is not presented.', async () => {
const request = httpServerMock.createKibanaRequest();
- let deauthenticateResult = await provider.logout(request);
- expect(deauthenticateResult.notHandled()).toBe(true);
+ await expect(provider.logout(request)).resolves.toEqual(DeauthenticationResult.notHandled());
- deauthenticateResult = await provider.logout(request, null);
- expect(deauthenticateResult.notHandled()).toBe(true);
+ await expect(provider.logout(request, null)).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled();
});
@@ -562,13 +496,12 @@ describe('PKIAuthenticationProvider', () => {
const failureReason = new Error('failed to delete token');
mockOptions.tokens.invalidate.mockRejectedValue(failureReason);
- const authenticationResult = await provider.logout(request, state);
+ await expect(provider.logout(request, state)).resolves.toEqual(
+ DeauthenticationResult.failed(failureReason)
+ );
expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ accessToken: 'foo' });
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('redirects to `/logged_out` page if access token is invalidated successfully.', async () => {
@@ -577,13 +510,16 @@ describe('PKIAuthenticationProvider', () => {
mockOptions.tokens.invalidate.mockResolvedValue(undefined);
- const authenticationResult = await provider.logout(request, state);
+ await expect(provider.logout(request, state)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out')
+ );
expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({ accessToken: 'foo' });
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
});
});
+
+ it('`getHTTPAuthenticationScheme` method', () => {
+ expect(provider.getHTTPAuthenticationScheme()).toBe('bearer');
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts
index 6d5aa9f01f2ea3..854f92a50fa9d5 100644
--- a/x-pack/plugins/security/server/authentication/providers/pki.ts
+++ b/x-pack/plugins/security/server/authentication/providers/pki.ts
@@ -9,8 +9,9 @@ import { DetailedPeerCertificate } from 'tls';
import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
-import { BaseAuthenticationProvider } from './base';
+import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
import { Tokens } from '../tokens';
+import { BaseAuthenticationProvider } from './base';
/**
* The state supported by the provider.
@@ -27,19 +28,6 @@ interface ProviderState {
peerCertificateFingerprint256: string;
}
-/**
- * Parses request's `Authorization` HTTP header if present and extracts authentication scheme.
- * @param request Request instance to extract authentication scheme for.
- */
-function getRequestAuthenticationScheme(request: KibanaRequest) {
- const authorization = request.headers.authorization;
- if (!authorization || typeof authorization !== 'string') {
- return '';
- }
-
- return authorization.split(/\s+/)[0].toLowerCase();
-}
-
/**
* Provider that supports PKI request authentication.
*/
@@ -57,19 +45,13 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- const authenticationScheme = getRequestAuthenticationScheme(request);
- if (authenticationScheme && authenticationScheme !== 'bearer') {
- this.logger.debug(`Unsupported authentication scheme: ${authenticationScheme}`);
+ if (getHTTPAuthenticationScheme(request) != null) {
+ this.logger.debug('Cannot authenticate requests with `Authorization` header.');
return AuthenticationResult.notHandled();
}
let authenticationResult = AuthenticationResult.notHandled();
- if (authenticationScheme) {
- // We should get rid of `Bearer` scheme support as soon as Reporting doesn't need it anymore.
- authenticationResult = await this.authenticateWithBearerScheme(request);
- }
-
- if (state && authenticationResult.notHandled()) {
+ if (state) {
authenticationResult = await this.authenticateViaState(request, state);
// If access token expired or doesn't match to the certificate fingerprint we should try to get
@@ -120,23 +102,11 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider {
}
/**
- * Tries to authenticate request with `Bearer ***` Authorization header by passing it to the Elasticsearch backend.
- * @param request Request instance.
+ * Returns HTTP authentication scheme (`Bearer`) that's used within `Authorization` HTTP header
+ * that provider attaches to all successfully authenticated requests to Elasticsearch.
*/
- private async authenticateWithBearerScheme(request: KibanaRequest) {
- this.logger.debug('Trying to authenticate request using "Bearer" authentication scheme.');
-
- try {
- const user = await this.getUser(request);
-
- this.logger.debug('Request has been authenticated using "Bearer" authentication scheme.');
- return AuthenticationResult.succeeded(user);
- } catch (err) {
- this.logger.debug(
- `Failed to authenticate request using "Bearer" authentication scheme: ${err.message}`
- );
- return AuthenticationResult.failed(err);
- }
+ public getHTTPAuthenticationScheme() {
+ return 'bearer';
}
/**
diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts
index c4fdf0b25061b7..d97a6c0838b86a 100644
--- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts
@@ -5,19 +5,33 @@
*/
import Boom from 'boom';
-import sinon from 'sinon';
import { ByteSizeValue } from '@kbn/config-schema';
-import { httpServerMock } from '../../../../../../src/core/server/mocks';
+import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
-import {
- MockAuthenticationProviderOptions,
- mockAuthenticationProviderOptions,
- mockScopedClusterClient,
-} from './base.mock';
+import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock';
+import {
+ ElasticsearchErrorHelpers,
+ IClusterClient,
+ ScopeableRequest,
+} from '../../../../../../src/core/server';
+import { AuthenticationResult } from '../authentication_result';
+import { DeauthenticationResult } from '../deauthentication_result';
import { SAMLAuthenticationProvider, SAMLLoginStep } from './saml';
+function expectAuthenticateCall(
+ mockClusterClient: jest.Mocked,
+ scopeableRequest: ScopeableRequest
+) {
+ expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1);
+ expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest);
+
+ const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value;
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
+}
+
describe('SAMLAuthenticationProvider', () => {
let provider: SAMLAuthenticationProvider;
let mockOptions: MockAuthenticationProviderOptions;
@@ -63,294 +77,317 @@ describe('SAMLAuthenticationProvider', () => {
it('gets token and redirects user to requested URL if SAML Response is valid.', async () => {
const request = httpServerMock.createKibanaRequest();
- mockOptions.client.callAsInternalUser.withArgs('shield.samlAuthenticate').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
username: 'user',
access_token: 'some-token',
refresh_token: 'some-refresh-token',
});
- const authenticationResult = await provider.login(
- request,
- { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
- { requestId: 'some-request-id', redirectURL: '/test-base-path/some-path#some-app' }
+ await expect(
+ provider.login(
+ request,
+ { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ { requestId: 'some-request-id', redirectURL: '/test-base-path/some-path#some-app' }
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo('/test-base-path/some-path#some-app', {
+ state: {
+ username: 'user',
+ accessToken: 'some-token',
+ refreshToken: 'some-refresh-token',
+ },
+ })
);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.samlAuthenticate',
{ body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } }
);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/test-base-path/some-path#some-app');
- expect(authenticationResult.state).toEqual({
- username: 'user',
- accessToken: 'some-token',
- refreshToken: 'some-refresh-token',
- });
});
it('fails if SAML Response payload is presented but state does not contain SAML Request token.', async () => {
const request = httpServerMock.createKibanaRequest();
- const authenticationResult = await provider.login(
- request,
- { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
- {}
+ await expect(
+ provider.login(
+ request,
+ { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ {}
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.failed(
+ Boom.badRequest('SAML response state does not have corresponding request id.')
+ )
);
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(
- Boom.badRequest('SAML response state does not have corresponding request id.')
- );
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('redirects to the default location if state contains empty redirect URL.', async () => {
const request = httpServerMock.createKibanaRequest();
- mockOptions.client.callAsInternalUser.withArgs('shield.samlAuthenticate').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
access_token: 'user-initiated-login-token',
refresh_token: 'user-initiated-login-refresh-token',
});
- const authenticationResult = await provider.login(
- request,
- { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
- { requestId: 'some-request-id', redirectURL: '' }
+ await expect(
+ provider.login(
+ request,
+ { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ { requestId: 'some-request-id', redirectURL: '' }
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo('/base-path/', {
+ state: {
+ accessToken: 'user-initiated-login-token',
+ refreshToken: 'user-initiated-login-refresh-token',
+ },
+ })
);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.samlAuthenticate',
{ body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } }
);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/base-path/');
- expect(authenticationResult.state).toEqual({
- accessToken: 'user-initiated-login-token',
- refreshToken: 'user-initiated-login-refresh-token',
- });
});
it('redirects to the default location if state is not presented.', async () => {
const request = httpServerMock.createKibanaRequest();
- mockOptions.client.callAsInternalUser.withArgs('shield.samlAuthenticate').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
access_token: 'idp-initiated-login-token',
refresh_token: 'idp-initiated-login-refresh-token',
});
- const authenticationResult = await provider.login(request, {
- step: SAMLLoginStep.SAMLResponseReceived,
- samlResponse: 'saml-response-xml',
- });
+ await expect(
+ provider.login(request, {
+ step: SAMLLoginStep.SAMLResponseReceived,
+ samlResponse: 'saml-response-xml',
+ })
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo('/base-path/', {
+ state: {
+ accessToken: 'idp-initiated-login-token',
+ refreshToken: 'idp-initiated-login-refresh-token',
+ },
+ })
+ );
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.samlAuthenticate',
{ body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' } }
);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/base-path/');
- expect(authenticationResult.state).toEqual({
- accessToken: 'idp-initiated-login-token',
- refreshToken: 'idp-initiated-login-refresh-token',
- });
});
it('fails if SAML Response is rejected.', async () => {
const request = httpServerMock.createKibanaRequest();
const failureReason = new Error('SAML response is stale!');
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlAuthenticate')
- .rejects(failureReason);
-
- const authenticationResult = await provider.login(
- request,
- { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
- { requestId: 'some-request-id', redirectURL: '/test-base-path/some-path' }
- );
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
+
+ await expect(
+ provider.login(
+ request,
+ { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ { requestId: 'some-request-id', redirectURL: '/test-base-path/some-path' }
+ )
+ ).resolves.toEqual(AuthenticationResult.failed(failureReason));
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.samlAuthenticate',
{ body: { ids: ['some-request-id'], content: 'saml-response-xml', realm: 'test-realm' } }
);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
describe('IdP initiated login with existing session', () => {
it('fails if new SAML Response is rejected.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
+ const authorization = 'Bearer some-valid-token';
const user = mockAuthenticatedUser();
- mockScopedClusterClient(mockOptions.client)
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
const failureReason = new Error('SAML response is invalid!');
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlAuthenticate')
- .rejects(failureReason);
-
- const authenticationResult = await provider.login(
- request,
- { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
+
+ await expect(
+ provider.login(
+ request,
+ { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ {
+ username: 'user',
+ accessToken: 'some-valid-token',
+ refreshToken: 'some-valid-refresh-token',
+ }
+ )
+ ).resolves.toEqual(AuthenticationResult.failed(failureReason));
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
+ 'shield.samlAuthenticate',
{
- username: 'user',
- accessToken: 'some-valid-token',
- refreshToken: 'some-valid-refresh-token',
+ body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
}
);
-
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlAuthenticate',
- { body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' } }
- );
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('fails if fails to invalidate existing access/refresh tokens.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const state = {
username: 'user',
accessToken: 'existing-valid-token',
refreshToken: 'existing-valid-refresh-token',
};
+ const authorization = `Bearer ${state.accessToken}`;
const user = mockAuthenticatedUser();
- mockScopedClusterClient(mockOptions.client)
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- mockOptions.client.callAsInternalUser.withArgs('shield.samlAuthenticate').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
username: 'user',
access_token: 'new-valid-token',
refresh_token: 'new-valid-refresh-token',
});
const failureReason = new Error('Failed to invalidate token!');
- mockOptions.tokens.invalidate.rejects(failureReason);
+ mockOptions.tokens.invalidate.mockRejectedValue(failureReason);
- const authenticationResult = await provider.login(
- request,
- { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
- state
- );
+ await expect(
+ provider.login(
+ request,
+ { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ state
+ )
+ ).resolves.toEqual(AuthenticationResult.failed(failureReason));
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.samlAuthenticate',
- { body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' } }
+ {
+ body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
+ }
);
- sinon.assert.calledOnce(mockOptions.tokens.invalidate);
- sinon.assert.calledWithExactly(mockOptions.tokens.invalidate, {
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({
accessToken: state.accessToken,
refreshToken: state.refreshToken,
});
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('redirects to the home page if new SAML Response is for the same user.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const state = {
username: 'user',
accessToken: 'existing-valid-token',
refreshToken: 'existing-valid-refresh-token',
};
+ const authorization = `Bearer ${state.accessToken}`;
const user = { username: 'user' };
- mockScopedClusterClient(mockOptions.client)
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- mockOptions.client.callAsInternalUser.withArgs('shield.samlAuthenticate').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
username: 'user',
access_token: 'new-valid-token',
refresh_token: 'new-valid-refresh-token',
});
- mockOptions.tokens.invalidate.resolves();
-
- const authenticationResult = await provider.login(
- request,
- { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
- state
+ mockOptions.tokens.invalidate.mockResolvedValue(undefined);
+
+ await expect(
+ provider.login(
+ request,
+ { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ state
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo('/base-path/', {
+ state: {
+ username: 'user',
+ accessToken: 'new-valid-token',
+ refreshToken: 'new-valid-refresh-token',
+ },
+ })
);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.samlAuthenticate',
- { body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' } }
+ {
+ body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
+ }
);
- sinon.assert.calledOnce(mockOptions.tokens.invalidate);
- sinon.assert.calledWithExactly(mockOptions.tokens.invalidate, {
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({
accessToken: state.accessToken,
refreshToken: state.refreshToken,
});
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/base-path/');
});
it('redirects to `overwritten_session` if new SAML Response is for the another user.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const state = {
username: 'user',
accessToken: 'existing-valid-token',
refreshToken: 'existing-valid-refresh-token',
};
+ const authorization = `Bearer ${state.accessToken}`;
const existingUser = { username: 'user' };
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(existingUser);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(existingUser);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- mockOptions.client.callAsInternalUser.withArgs('shield.samlAuthenticate').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
username: 'new-user',
access_token: 'new-valid-token',
refresh_token: 'new-valid-refresh-token',
});
- mockOptions.tokens.invalidate.resolves();
-
- const authenticationResult = await provider.login(
- request,
- { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
- state
+ mockOptions.tokens.invalidate.mockResolvedValue(undefined);
+
+ await expect(
+ provider.login(
+ request,
+ { step: SAMLLoginStep.SAMLResponseReceived, samlResponse: 'saml-response-xml' },
+ state
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo('/base-path/overwritten_session', {
+ state: {
+ username: 'new-user',
+ accessToken: 'new-valid-token',
+ refreshToken: 'new-valid-refresh-token',
+ },
+ })
);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith(
'shield.samlAuthenticate',
- { body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' } }
+ {
+ body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
+ }
);
- sinon.assert.calledOnce(mockOptions.tokens.invalidate);
- sinon.assert.calledWithExactly(mockOptions.tokens.invalidate, {
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith({
accessToken: state.accessToken,
refreshToken: state.refreshToken,
});
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/base-path/overwritten_session');
});
});
@@ -358,170 +395,171 @@ describe('SAMLAuthenticationProvider', () => {
it('fails if state is not available', async () => {
const request = httpServerMock.createKibanaRequest();
- const authenticationResult = await provider.login(request, {
- step: SAMLLoginStep.RedirectURLFragmentCaptured,
- redirectURLFragment: '#some-fragment',
- });
-
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(
- Boom.badRequest('State does not include URL path to redirect to.')
+ await expect(
+ provider.login(request, {
+ step: SAMLLoginStep.RedirectURLFragmentCaptured,
+ redirectURLFragment: '#some-fragment',
+ })
+ ).resolves.toEqual(
+ AuthenticationResult.failed(
+ Boom.badRequest('State does not include URL path to redirect to.')
+ )
);
+
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('does not handle AJAX requests.', async () => {
const request = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } });
- const authenticationResult = await provider.login(
- request,
- {
- step: SAMLLoginStep.RedirectURLFragmentCaptured,
- redirectURLFragment: '#some-fragment',
- },
- { redirectURL: '/test-base-path/some-path' }
- );
-
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
-
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(
+ provider.login(
+ request,
+ {
+ step: SAMLLoginStep.RedirectURLFragmentCaptured,
+ redirectURLFragment: '#some-fragment',
+ },
+ { redirectURL: '/test-base-path/some-path' }
+ )
+ ).resolves.toEqual(AuthenticationResult.notHandled());
+
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('redirects non-AJAX requests to the IdP remembering combined redirect URL.', async () => {
const request = httpServerMock.createKibanaRequest();
- mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
id: 'some-request-id',
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
});
- const authenticationResult = await provider.login(
- request,
- {
- step: SAMLLoginStep.RedirectURLFragmentCaptured,
- redirectURLFragment: '#some-fragment',
- },
- { redirectURL: '/test-base-path/some-path' }
+ await expect(
+ provider.login(
+ request,
+ {
+ step: SAMLLoginStep.RedirectURLFragmentCaptured,
+ redirectURLFragment: '#some-fragment',
+ },
+ { redirectURL: '/test-base-path/some-path' }
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ 'https://idp-host/path/login?SAMLRequest=some%20request%20',
+ {
+ state: {
+ requestId: 'some-request-id',
+ redirectURL: '/test-base-path/some-path#some-fragment',
+ },
+ }
+ )
);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlPrepare',
- { body: { realm: 'test-realm' } }
- );
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', {
+ body: { realm: 'test-realm' },
+ });
expect(mockOptions.logger.warn).not.toHaveBeenCalled();
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- 'https://idp-host/path/login?SAMLRequest=some%20request%20'
- );
- expect(authenticationResult.state).toEqual({
- requestId: 'some-request-id',
- redirectURL: '/test-base-path/some-path#some-fragment',
- });
});
it('prepends redirect URL fragment with `#` if it does not have one.', async () => {
const request = httpServerMock.createKibanaRequest();
- mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
id: 'some-request-id',
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
});
- const authenticationResult = await provider.login(
- request,
- {
- step: SAMLLoginStep.RedirectURLFragmentCaptured,
- redirectURLFragment: '../some-fragment',
- },
- { redirectURL: '/test-base-path/some-path' }
+ await expect(
+ provider.login(
+ request,
+ {
+ step: SAMLLoginStep.RedirectURLFragmentCaptured,
+ redirectURLFragment: '../some-fragment',
+ },
+ { redirectURL: '/test-base-path/some-path' }
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ 'https://idp-host/path/login?SAMLRequest=some%20request%20',
+ {
+ state: {
+ requestId: 'some-request-id',
+ redirectURL: '/test-base-path/some-path#../some-fragment',
+ },
+ }
+ )
);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlPrepare',
- { body: { realm: 'test-realm' } }
- );
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', {
+ body: { realm: 'test-realm' },
+ });
expect(mockOptions.logger.warn).toHaveBeenCalledTimes(1);
expect(mockOptions.logger.warn).toHaveBeenCalledWith(
'Redirect URL fragment does not start with `#`.'
);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- 'https://idp-host/path/login?SAMLRequest=some%20request%20'
- );
- expect(authenticationResult.state).toEqual({
- requestId: 'some-request-id',
- redirectURL: '/test-base-path/some-path#../some-fragment',
- });
});
it('redirects non-AJAX requests to the IdP remembering only redirect URL path if fragment is too large.', async () => {
const request = httpServerMock.createKibanaRequest();
- mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
id: 'some-request-id',
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
});
- const authenticationResult = await provider.login(
- request,
- {
- step: SAMLLoginStep.RedirectURLFragmentCaptured,
- redirectURLFragment: '#some-fragment'.repeat(10),
- },
- { redirectURL: '/test-base-path/some-path' }
+ await expect(
+ provider.login(
+ request,
+ {
+ step: SAMLLoginStep.RedirectURLFragmentCaptured,
+ redirectURLFragment: '#some-fragment'.repeat(10),
+ },
+ { redirectURL: '/test-base-path/some-path' }
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ 'https://idp-host/path/login?SAMLRequest=some%20request%20',
+ {
+ state: {
+ requestId: 'some-request-id',
+ redirectURL: '/test-base-path/some-path',
+ },
+ }
+ )
);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlPrepare',
- { body: { realm: 'test-realm' } }
- );
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', {
+ body: { realm: 'test-realm' },
+ });
expect(mockOptions.logger.warn).toHaveBeenCalledTimes(1);
expect(mockOptions.logger.warn).toHaveBeenCalledWith(
'Max URL size should not exceed 100b but it was 165b. Only URL path is captured.'
);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- 'https://idp-host/path/login?SAMLRequest=some%20request%20'
- );
- expect(authenticationResult.state).toEqual({
- requestId: 'some-request-id',
- redirectURL: '/test-base-path/some-path',
- });
});
it('fails if SAML request preparation fails.', async () => {
const request = httpServerMock.createKibanaRequest();
const failureReason = new Error('Realm is misconfigured!');
- mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').rejects(failureReason);
-
- const authenticationResult = await provider.login(
- request,
- {
- step: SAMLLoginStep.RedirectURLFragmentCaptured,
- redirectURLFragment: '#some-fragment',
- },
- { redirectURL: '/test-base-path/some-path' }
- );
-
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlPrepare',
- { body: { realm: 'test-realm' } }
- );
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
+
+ await expect(
+ provider.login(
+ request,
+ {
+ step: SAMLLoginStep.RedirectURLFragmentCaptured,
+ redirectURLFragment: '#some-fragment',
+ },
+ { redirectURL: '/test-base-path/some-path' }
+ )
+ ).resolves.toEqual(AuthenticationResult.failed(failureReason));
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', {
+ body: { realm: 'test-realm' },
+ });
});
});
});
@@ -530,44 +568,57 @@ describe('SAMLAuthenticationProvider', () => {
it('does not handle AJAX request that can not be authenticated.', async () => {
const request = httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } });
- const authenticationResult = await provider.authenticate(request, null);
-
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
});
- it('does not handle `authorization` header with unsupported schema even if state contains a valid token.', async () => {
+ it('does not handle authentication via `authorization` header.', async () => {
const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Basic some:credentials' },
+ headers: { authorization: 'Bearer some-token' },
});
- const authenticationResult = await provider.authenticate(request, {
- username: 'user',
- accessToken: 'some-valid-token',
- refreshToken: 'some-valid-refresh-token',
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
+ );
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
+ });
+
+ it('does not handle authentication via `authorization` header even if state contains a valid token.', async () => {
+ const request = httpServerMock.createKibanaRequest({
+ headers: { authorization: 'Bearer some-token' },
});
- sinon.assert.notCalled(mockOptions.client.asScoped);
- expect(request.headers.authorization).toBe('Basic some:credentials');
- expect(authenticationResult.notHandled()).toBe(true);
+ await expect(
+ provider.authenticate(request, {
+ username: 'user',
+ accessToken: 'some-valid-token',
+ refreshToken: 'some-valid-refresh-token',
+ })
+ ).resolves.toEqual(AuthenticationResult.notHandled());
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
});
it('redirects non-AJAX request that can not be authenticated to the "capture fragment" page.', async () => {
const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' });
- mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
id: 'some-request-id',
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
});
- const authenticationResult = await provider.authenticate(request);
-
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- '/mock-server-basepath/api/security/saml/capture-url-fragment'
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ '/mock-server-basepath/api/security/saml/capture-url-fragment',
+ { state: { redirectURL: '/base-path/s/foo/some-path' } }
+ )
);
- expect(authenticationResult.state).toEqual({ redirectURL: '/base-path/s/foo/some-path' });
+
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('redirects non-AJAX request that can not be authenticated to the IdP if request path is too large.', async () => {
@@ -575,14 +626,19 @@ describe('SAMLAuthenticationProvider', () => {
path: `/s/foo/${'some-path'.repeat(10)}`,
});
- mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
id: 'some-request-id',
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
});
- const authenticationResult = await provider.authenticate(request);
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ 'https://idp-host/path/login?SAMLRequest=some%20request%20',
+ { state: { requestId: 'some-request-id', redirectURL: '' } }
+ )
+ );
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.samlPrepare', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', {
body: { realm: 'test-realm' },
});
@@ -590,12 +646,6 @@ describe('SAMLAuthenticationProvider', () => {
expect(mockOptions.logger.warn).toHaveBeenCalledWith(
'Max URL path size should not exceed 100b but it was 107b. URL is not captured.'
);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- 'https://idp-host/path/login?SAMLRequest=some%20request%20'
- );
- expect(authenticationResult.state).toEqual({ requestId: 'some-request-id', redirectURL: '' });
});
it('fails if SAML request preparation fails.', async () => {
@@ -604,21 +654,20 @@ describe('SAMLAuthenticationProvider', () => {
});
const failureReason = new Error('Realm is misconfigured!');
- mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').rejects(failureReason);
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- const authenticationResult = await provider.authenticate(request, null);
+ await expect(provider.authenticate(request, null)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason)
+ );
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.samlPrepare', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', {
body: { realm: 'test-realm' },
});
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('succeeds if state contains a valid token.', async () => {
const user = mockAuthenticatedUser();
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const state = {
username: 'user',
accessToken: 'some-valid-token',
@@ -626,40 +675,43 @@ describe('SAMLAuthenticationProvider', () => {
};
const authorization = `Bearer ${state.accessToken}`;
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'saml' },
+ { authHeaders: { authorization } }
+ )
+ );
- const authenticationResult = await provider.authenticate(request, state);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toEqual({ authorization });
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'saml' });
- expect(authenticationResult.state).toBeUndefined();
});
it('fails if token from the state is rejected because of unknown reason.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const state = {
username: 'user',
accessToken: 'some-valid-token',
refreshToken: 'some-valid-refresh-token',
};
+ const authorization = `Bearer ${state.accessToken}`;
const failureReason = { statusCode: 500, message: 'Token is not valid!' };
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.failed(failureReason as any)
+ );
- const authenticationResult = await provider.authenticate(request, state);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('succeeds if token from the state is expired, but has been successfully refreshed.', async () => {
@@ -671,65 +723,80 @@ describe('SAMLAuthenticationProvider', () => {
refreshToken: 'valid-refresh-token',
};
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ mockOptions.client.asScoped.mockImplementation(scopeableRequest => {
+ if (scopeableRequest?.headers.authorization === `Bearer ${state.accessToken}`) {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ return mockScopedClusterClient;
+ }
+
+ if (scopeableRequest?.headers.authorization === 'Bearer new-access-token') {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ return mockScopedClusterClient;
+ }
+
+ throw new Error('Unexpected call');
+ });
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer new-access-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ mockOptions.tokens.refresh.mockResolvedValue({
+ accessToken: 'new-access-token',
+ refreshToken: 'new-refresh-token',
+ });
- mockOptions.tokens.refresh
- .withArgs(state.refreshToken)
- .resolves({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token' });
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'saml' },
+ {
+ authHeaders: { authorization: 'Bearer new-access-token' },
+ state: {
+ username: 'user',
+ accessToken: 'new-access-token',
+ refreshToken: 'new-refresh-token',
+ },
+ }
+ )
+ );
- const authenticationResult = await provider.authenticate(request, state);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(state.refreshToken);
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toEqual({
- authorization: 'Bearer new-access-token',
- });
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'saml' });
- expect(authenticationResult.state).toEqual({
- username: 'user',
- accessToken: 'new-access-token',
- refreshToken: 'new-refresh-token',
- });
});
it('fails if token from the state is expired and refresh attempt failed with unknown reason too.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const state = {
username: 'user',
accessToken: 'expired-token',
refreshToken: 'invalid-refresh-token',
};
+ const authorization = `Bearer ${state.accessToken}`;
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
const refreshFailureReason = {
statusCode: 500,
message: 'Something is wrong with refresh token.',
};
- mockOptions.tokens.refresh.withArgs(state.refreshToken).rejects(refreshFailureReason);
+ mockOptions.tokens.refresh.mockRejectedValue(refreshFailureReason);
- const authenticationResult = await provider.authenticate(request, state);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.failed(refreshFailureReason as any)
+ );
+
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(state.refreshToken);
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(refreshFailureReason);
});
it('fails for AJAX requests with user friendly message if refresh token is expired.', async () => {
@@ -739,80 +806,100 @@ describe('SAMLAuthenticationProvider', () => {
accessToken: 'expired-token',
refreshToken: 'expired-refresh-token',
};
+ const authorization = `Bearer ${state.accessToken}`;
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ mockOptions.tokens.refresh.mockResolvedValue(null);
+
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.badRequest('Both access and refresh tokens are expired.'))
+ );
- mockOptions.tokens.refresh.withArgs(state.refreshToken).resolves(null);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(state.refreshToken);
- const authenticationResult = await provider.authenticate(request, state);
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { 'kbn-xsrf': 'xsrf', authorization },
+ });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(
- Boom.badRequest('Both access and refresh tokens are expired.')
- );
});
it('re-capture URL for non-AJAX requests if refresh token is expired.', async () => {
- const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' });
+ const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path', headers: {} });
const state = {
username: 'user',
accessToken: 'expired-token',
refreshToken: 'expired-refresh-token',
};
+ const authorization = `Bearer ${state.accessToken}`;
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- mockOptions.tokens.refresh.withArgs(state.refreshToken).resolves(null);
+ mockOptions.tokens.refresh.mockResolvedValue(null);
- const authenticationResult = await provider.authenticate(request, state);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ '/mock-server-basepath/api/security/saml/capture-url-fragment',
+ { state: { redirectURL: '/base-path/s/foo/some-path' } }
+ )
+ );
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(state.refreshToken);
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- '/mock-server-basepath/api/security/saml/capture-url-fragment'
- );
- expect(authenticationResult.state).toEqual({ redirectURL: '/base-path/s/foo/some-path' });
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
+
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('initiates SAML handshake for non-AJAX requests if refresh token is expired and request path is too large.', async () => {
const request = httpServerMock.createKibanaRequest({
path: `/s/foo/${'some-path'.repeat(10)}`,
+ headers: {},
});
const state = {
username: 'user',
accessToken: 'expired-token',
refreshToken: 'expired-refresh-token',
};
+ const authorization = `Bearer ${state.accessToken}`;
- mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').resolves({
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
id: 'some-request-id',
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
});
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- mockOptions.tokens.refresh.withArgs(state.refreshToken).resolves(null);
+ mockOptions.tokens.refresh.mockResolvedValue(null);
- const authenticationResult = await provider.authenticate(request, state);
+ await expect(provider.authenticate(request, state)).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ 'https://idp-host/path/login?SAMLRequest=some%20request%20',
+ { state: { requestId: 'some-request-id', redirectURL: '' } }
+ )
+ );
+
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(state.refreshToken);
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.samlPrepare', {
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlPrepare', {
body: { realm: 'test-realm' },
});
@@ -820,71 +907,6 @@ describe('SAMLAuthenticationProvider', () => {
expect(mockOptions.logger.warn).toHaveBeenCalledWith(
'Max URL path size should not exceed 100b but it was 107b. URL is not captured.'
);
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- 'https://idp-host/path/login?SAMLRequest=some%20request%20'
- );
- expect(authenticationResult.state).toEqual({ requestId: 'some-request-id', redirectURL: '' });
- });
-
- it('succeeds if `authorization` contains a valid token.', async () => {
- const user = mockAuthenticatedUser();
- const authorization = 'Bearer some-valid-token';
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
-
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request);
-
- expect(request.headers.authorization).toBe('Bearer some-valid-token');
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.authHeaders).toBeUndefined();
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'saml' });
- expect(authenticationResult.state).toBeUndefined();
- });
-
- it('fails if token from `authorization` header is rejected.', async () => {
- const authorization = 'Bearer some-invalid-token';
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
-
- const failureReason = { statusCode: 401 };
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
-
- const authenticationResult = await provider.authenticate(request);
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
- });
-
- it('fails if token from `authorization` header is rejected even if state contains a valid one.', async () => {
- const user = mockAuthenticatedUser();
- const authorization = 'Bearer some-invalid-token';
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
-
- const failureReason = { statusCode: 401 };
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(failureReason);
-
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer some-valid-token' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request, {
- accessToken: 'some-valid-token',
- refreshToken: 'some-valid-refresh-token',
- });
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
});
@@ -892,16 +914,15 @@ describe('SAMLAuthenticationProvider', () => {
it('returns `notHandled` if state is not presented or does not include access token.', async () => {
const request = httpServerMock.createKibanaRequest();
- let deauthenticateResult = await provider.logout(request);
- expect(deauthenticateResult.notHandled()).toBe(true);
-
- deauthenticateResult = await provider.logout(request, {} as any);
- expect(deauthenticateResult.notHandled()).toBe(true);
-
- deauthenticateResult = await provider.logout(request, { somethingElse: 'x' } as any);
- expect(deauthenticateResult.notHandled()).toBe(true);
+ await expect(provider.logout(request)).resolves.toEqual(DeauthenticationResult.notHandled());
+ await expect(provider.logout(request, {} as any)).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
+ await expect(provider.logout(request, { somethingElse: 'x' } as any)).resolves.toEqual(
+ DeauthenticationResult.notHandled()
+ );
- sinon.assert.notCalled(mockOptions.client.callAsInternalUser);
+ expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled();
});
it('fails if SAML logout call fails.', async () => {
@@ -910,42 +931,32 @@ describe('SAMLAuthenticationProvider', () => {
const refreshToken = 'x-saml-refresh-token';
const failureReason = new Error('Realm is misconfigured!');
- mockOptions.client.callAsInternalUser.withArgs('shield.samlLogout').rejects(failureReason);
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- const authenticationResult = await provider.logout(request, {
- username: 'user',
- accessToken,
- refreshToken,
- });
+ await expect(
+ provider.logout(request, { username: 'user', accessToken, refreshToken })
+ ).resolves.toEqual(DeauthenticationResult.failed(failureReason));
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.samlLogout', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', {
body: { token: accessToken, refresh_token: refreshToken },
});
-
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
});
it('fails if SAML invalidate call fails.', async () => {
const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } });
const failureReason = new Error('Realm is misconfigured!');
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlInvalidate')
- .rejects(failureReason);
+ mockOptions.client.callAsInternalUser.mockRejectedValue(failureReason);
- const authenticationResult = await provider.logout(request);
-
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlInvalidate',
- { body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' } }
+ await expect(provider.logout(request)).resolves.toEqual(
+ DeauthenticationResult.failed(failureReason)
);
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', {
+ body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' },
+ });
});
it('redirects to /logged_out if `redirect` field in SAML logout response is null.', async () => {
@@ -953,23 +964,16 @@ describe('SAMLAuthenticationProvider', () => {
const accessToken = 'x-saml-token';
const refreshToken = 'x-saml-refresh-token';
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlLogout')
- .resolves({ redirect: null });
+ mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null });
- const authenticationResult = await provider.logout(request, {
- username: 'user',
- accessToken,
- refreshToken,
- });
+ await expect(
+ provider.logout(request, { username: 'user', accessToken, refreshToken })
+ ).resolves.toEqual(DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out'));
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.samlLogout', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', {
body: { token: accessToken, refresh_token: refreshToken },
});
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
});
it('redirects to /logged_out if `redirect` field in SAML logout response is not defined.', async () => {
@@ -977,23 +981,16 @@ describe('SAMLAuthenticationProvider', () => {
const accessToken = 'x-saml-token';
const refreshToken = 'x-saml-refresh-token';
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlLogout')
- .resolves({ redirect: undefined });
+ mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: undefined });
- const authenticationResult = await provider.logout(request, {
- username: 'user',
- accessToken,
- refreshToken,
- });
+ await expect(
+ provider.logout(request, { username: 'user', accessToken, refreshToken })
+ ).resolves.toEqual(DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out'));
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.samlLogout', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', {
body: { token: accessToken, refresh_token: refreshToken },
});
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
});
it('relies on SAML logout if query string is not empty, but does not include SAMLRequest.', async () => {
@@ -1003,87 +1000,65 @@ describe('SAMLAuthenticationProvider', () => {
const accessToken = 'x-saml-token';
const refreshToken = 'x-saml-refresh-token';
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlLogout')
- .resolves({ redirect: null });
+ mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null });
- const authenticationResult = await provider.logout(request, {
- username: 'user',
- accessToken,
- refreshToken,
- });
+ await expect(
+ provider.logout(request, { username: 'user', accessToken, refreshToken })
+ ).resolves.toEqual(DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out'));
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.samlLogout', {
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', {
body: { token: accessToken, refresh_token: refreshToken },
});
-
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
});
it('relies on SAML invalidate call even if access token is presented.', async () => {
const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } });
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlInvalidate')
- .resolves({ redirect: null });
-
- const authenticationResult = await provider.logout(request, {
- username: 'user',
- accessToken: 'x-saml-token',
- refreshToken: 'x-saml-refresh-token',
- });
+ mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null });
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlInvalidate',
- { body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' } }
- );
+ await expect(
+ provider.logout(request, {
+ username: 'user',
+ accessToken: 'x-saml-token',
+ refreshToken: 'x-saml-refresh-token',
+ })
+ ).resolves.toEqual(DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out'));
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', {
+ body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' },
+ });
});
it('redirects to /logged_out if `redirect` field in SAML invalidate response is null.', async () => {
const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } });
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlInvalidate')
- .resolves({ redirect: null });
+ mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null });
- const authenticationResult = await provider.logout(request);
-
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlInvalidate',
- { body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' } }
+ await expect(provider.logout(request)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out')
);
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', {
+ body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' },
+ });
});
it('redirects to /logged_out if `redirect` field in SAML invalidate response is not defined.', async () => {
const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } });
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlInvalidate')
- .resolves({ redirect: undefined });
+ mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: undefined });
- const authenticationResult = await provider.logout(request);
-
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- sinon.assert.calledWithExactly(
- mockOptions.client.callAsInternalUser,
- 'shield.samlInvalidate',
- { body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' } }
+ await expect(provider.logout(request)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/mock-server-basepath/logged_out')
);
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/mock-server-basepath/logged_out');
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', {
+ body: { queryString: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' },
+ });
});
it('redirects user to the IdP if SLO is supported by IdP in case of SP initiated logout.', async () => {
@@ -1091,37 +1066,41 @@ describe('SAMLAuthenticationProvider', () => {
const accessToken = 'x-saml-token';
const refreshToken = 'x-saml-refresh-token';
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlLogout')
- .resolves({ redirect: 'http://fake-idp/SLO?SAMLRequest=7zlH37H' });
-
- const authenticationResult = await provider.logout(request, {
- username: 'user',
- accessToken,
- refreshToken,
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
+ redirect: 'http://fake-idp/SLO?SAMLRequest=7zlH37H',
});
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('http://fake-idp/SLO?SAMLRequest=7zlH37H');
+ await expect(
+ provider.logout(request, { username: 'user', accessToken, refreshToken })
+ ).resolves.toEqual(
+ DeauthenticationResult.redirectTo('http://fake-idp/SLO?SAMLRequest=7zlH37H')
+ );
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
});
it('redirects user to the IdP if SLO is supported by IdP in case of IdP initiated logout.', async () => {
const request = httpServerMock.createKibanaRequest({ query: { SAMLRequest: 'xxx yyy' } });
- mockOptions.client.callAsInternalUser
- .withArgs('shield.samlInvalidate')
- .resolves({ redirect: 'http://fake-idp/SLO?SAMLRequest=7zlH37H' });
-
- const authenticationResult = await provider.logout(request, {
- username: 'user',
- accessToken: 'x-saml-token',
- refreshToken: 'x-saml-refresh-token',
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
+ redirect: 'http://fake-idp/SLO?SAMLRequest=7zlH37H',
});
- sinon.assert.calledOnce(mockOptions.client.callAsInternalUser);
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('http://fake-idp/SLO?SAMLRequest=7zlH37H');
+ await expect(
+ provider.logout(request, {
+ username: 'user',
+ accessToken: 'x-saml-token',
+ refreshToken: 'x-saml-refresh-token',
+ })
+ ).resolves.toEqual(
+ DeauthenticationResult.redirectTo('http://fake-idp/SLO?SAMLRequest=7zlH37H')
+ );
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
});
});
+
+ it('`getHTTPAuthenticationScheme` method', () => {
+ expect(provider.getHTTPAuthenticationScheme()).toBe('bearer');
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts
index a817159fcd445c..1ac59d66a22359 100644
--- a/x-pack/plugins/security/server/authentication/providers/saml.ts
+++ b/x-pack/plugins/security/server/authentication/providers/saml.ts
@@ -9,9 +9,10 @@ import { ByteSizeValue } from '@kbn/config-schema';
import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
-import { AuthenticationProviderOptions, BaseAuthenticationProvider } from './base';
import { canRedirectRequest } from '../can_redirect_request';
+import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
import { Tokens, TokenPair } from '../tokens';
+import { AuthenticationProviderOptions, BaseAuthenticationProvider } from './base';
/**
* The state supported by the provider (for the SAML handshake or established session).
@@ -180,17 +181,13 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- // We should get rid of `Bearer` scheme support as soon as Reporting doesn't need it anymore.
- let {
- authenticationResult,
- // eslint-disable-next-line prefer-const
- headerNotRecognized,
- } = await this.authenticateViaHeader(request);
- if (headerNotRecognized) {
- return authenticationResult;
+ if (getHTTPAuthenticationScheme(request) != null) {
+ this.logger.debug('Cannot authenticate requests with `Authorization` header.');
+ return AuthenticationResult.notHandled();
}
- if (state && authenticationResult.notHandled()) {
+ let authenticationResult = AuthenticationResult.notHandled();
+ if (state) {
authenticationResult = await this.authenticateViaState(request, state);
if (
authenticationResult.failed() &&
@@ -243,37 +240,11 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
}
/**
- * Validates whether request contains `Bearer ***` Authorization header and just passes it
- * forward to Elasticsearch backend.
- * @param request Request instance.
+ * Returns HTTP authentication scheme (`Bearer`) that's used within `Authorization` HTTP header
+ * that provider attaches to all successfully authenticated requests to Elasticsearch.
*/
- private async authenticateViaHeader(request: KibanaRequest) {
- this.logger.debug('Trying to authenticate via header.');
-
- const authorization = request.headers.authorization;
- if (!authorization || typeof authorization !== 'string') {
- this.logger.debug('Authorization header is not presented.');
- return { authenticationResult: AuthenticationResult.notHandled() };
- }
-
- const authenticationSchema = authorization.split(/\s+/)[0];
- if (authenticationSchema.toLowerCase() !== 'bearer') {
- this.logger.debug(`Unsupported authentication schema: ${authenticationSchema}`);
- return {
- authenticationResult: AuthenticationResult.notHandled(),
- headerNotRecognized: true,
- };
- }
-
- try {
- const user = await this.getUser(request);
-
- this.logger.debug('Request has been authenticated via header.');
- return { authenticationResult: AuthenticationResult.succeeded(user) };
- } catch (err) {
- this.logger.debug(`Failed to authenticate request via header: ${err.message}`);
- return { authenticationResult: AuthenticationResult.failed(err) };
- }
+ public getHTTPAuthenticationScheme() {
+ return 'bearer';
}
/**
diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts
index 0a55219e25d910..e81d14e8bf9f35 100644
--- a/x-pack/plugins/security/server/authentication/providers/token.test.ts
+++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts
@@ -6,18 +6,32 @@
import Boom from 'boom';
import { errors } from 'elasticsearch';
-import sinon from 'sinon';
-import { httpServerMock } from '../../../../../../src/core/server/mocks';
+import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks';
import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock';
-import {
- MockAuthenticationProviderOptions,
- mockAuthenticationProviderOptions,
- mockScopedClusterClient,
-} from './base.mock';
+import { MockAuthenticationProviderOptions, mockAuthenticationProviderOptions } from './base.mock';
+import {
+ ElasticsearchErrorHelpers,
+ IClusterClient,
+ ScopeableRequest,
+} from '../../../../../../src/core/server';
+import { AuthenticationResult } from '../authentication_result';
+import { DeauthenticationResult } from '../deauthentication_result';
import { TokenAuthenticationProvider } from './token';
+function expectAuthenticateCall(
+ mockClusterClient: jest.Mocked,
+ scopeableRequest: ScopeableRequest
+) {
+ expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1);
+ expect(mockClusterClient.asScoped).toHaveBeenCalledWith(scopeableRequest);
+
+ const mockScopedClusterClient = mockClusterClient.asScoped.mock.results[0].value;
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1);
+ expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate');
+}
+
describe('TokenAuthenticationProvider', () => {
let provider: TokenAuthenticationProvider;
let mockOptions: MockAuthenticationProviderOptions;
@@ -28,29 +42,35 @@ describe('TokenAuthenticationProvider', () => {
describe('`login` method', () => {
it('succeeds with valid login attempt, creates session and authHeaders', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const user = mockAuthenticatedUser();
const credentials = { username: 'user', password: 'password' };
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
const authorization = `Bearer ${tokenPair.accessToken}`;
- mockOptions.client.callAsInternalUser
- .withArgs('shield.getAccessToken', {
- body: { grant_type: 'password', ...credentials },
- })
- .resolves({ access_token: tokenPair.accessToken, refresh_token: tokenPair.refreshToken });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
+ access_token: tokenPair.accessToken,
+ refresh_token: tokenPair.refreshToken,
+ });
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ await expect(provider.login(request, credentials)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'token' },
+ { authHeaders: { authorization }, state: tokenPair }
+ )
+ );
- const authenticationResult = await provider.login(request, credentials);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'token' });
- expect(authenticationResult.state).toEqual(tokenPair);
- expect(authenticationResult.authHeaders).toEqual({ authorization });
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: 'password', ...credentials },
+ });
});
it('fails if token cannot be generated during login attempt', async () => {
@@ -58,109 +78,125 @@ describe('TokenAuthenticationProvider', () => {
const credentials = { username: 'user', password: 'password' };
const authenticationError = new Error('Invalid credentials');
- mockOptions.client.callAsInternalUser
- .withArgs('shield.getAccessToken', {
- body: { grant_type: 'password', ...credentials },
- })
- .rejects(authenticationError);
+ mockOptions.client.callAsInternalUser.mockRejectedValue(authenticationError);
- const authenticationResult = await provider.login(request, credentials);
+ await expect(provider.login(request, credentials)).resolves.toEqual(
+ AuthenticationResult.failed(authenticationError)
+ );
+
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
- sinon.assert.notCalled(mockOptions.client.asScoped);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: 'password', ...credentials },
+ });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.error).toEqual(authenticationError);
});
it('fails if user cannot be retrieved during login attempt', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const credentials = { username: 'user', password: 'password' };
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
+ const authorization = `Bearer ${tokenPair.accessToken}`;
- mockOptions.client.callAsInternalUser
- .withArgs('shield.getAccessToken', {
- body: { grant_type: 'password', ...credentials },
- })
- .resolves({ access_token: tokenPair.accessToken, refresh_token: tokenPair.refreshToken });
+ mockOptions.client.callAsInternalUser.mockResolvedValue({
+ access_token: tokenPair.accessToken,
+ refresh_token: tokenPair.refreshToken,
+ });
const authenticationError = new Error('Some error');
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(authenticationError);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- const authenticationResult = await provider.login(request, credentials);
+ await expect(provider.login(request, credentials)).resolves.toEqual(
+ AuthenticationResult.failed(authenticationError)
+ );
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
+
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1);
+ expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.getAccessToken', {
+ body: { grant_type: 'password', ...credentials },
+ });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.error).toEqual(authenticationError);
});
});
describe('`authenticate` method', () => {
- it('does not redirect AJAX requests that can not be authenticated to the login page.', async () => {
- // Add `kbn-xsrf` header to make `can_redirect_request` think that it's AJAX request and
- // avoid triggering of redirect logic.
- const authenticationResult = await provider.authenticate(
- httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }),
- null
- );
-
- expect(authenticationResult.notHandled()).toBe(true);
- });
+ it('does not handle authentication via `authorization` header.', async () => {
+ const request = httpServerMock.createKibanaRequest({
+ headers: { authorization: 'Bearer some-token' },
+ });
- it('redirects non-AJAX requests that can not be authenticated to the login page.', async () => {
- const authenticationResult = await provider.authenticate(
- httpServerMock.createKibanaRequest({ path: '/s/foo/some-path # that needs to be encoded' }),
- null
+ await expect(provider.authenticate(request)).resolves.toEqual(
+ AuthenticationResult.notHandled()
);
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- '/base-path/login?next=%2Fbase-path%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded'
- );
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
});
- it('succeeds if only `authorization` header is available and returns neither state nor authHeaders.', async () => {
- const authorization = 'Bearer foo';
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
- const user = mockAuthenticatedUser();
+ it('does not handle authentication via `authorization` header even if state contains valid credentials.', async () => {
+ const request = httpServerMock.createKibanaRequest({
+ headers: { authorization: 'Bearer some-token' },
+ });
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ await expect(
+ provider.authenticate(request, { accessToken: 'foo', refreshToken: 'bar' })
+ ).resolves.toEqual(AuthenticationResult.notHandled());
- const authenticationResult = await provider.authenticate(request);
+ expect(mockOptions.client.asScoped).not.toHaveBeenCalled();
+ expect(request.headers.authorization).toBe('Bearer some-token');
+ });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'token' });
- expect(authenticationResult.authHeaders).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
+ it('does not redirect AJAX requests that can not be authenticated to the login page.', async () => {
+ // Add `kbn-xsrf` header to make `can_redirect_request` think that it's AJAX request and
+ // avoid triggering of redirect logic.
+ await expect(
+ provider.authenticate(
+ httpServerMock.createKibanaRequest({ headers: { 'kbn-xsrf': 'xsrf' } }),
+ null
+ )
+ ).resolves.toEqual(AuthenticationResult.notHandled());
+ });
+
+ it('redirects non-AJAX requests that can not be authenticated to the login page.', async () => {
+ await expect(
+ provider.authenticate(
+ httpServerMock.createKibanaRequest({
+ path: '/s/foo/some-path # that needs to be encoded',
+ }),
+ null
+ )
+ ).resolves.toEqual(
+ AuthenticationResult.redirectTo(
+ '/base-path/login?next=%2Fbase-path%2Fs%2Ffoo%2Fsome-path%20%23%20that%20needs%20to%20be%20encoded'
+ )
+ );
});
it('succeeds if only state is available.', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
const user = mockAuthenticatedUser();
const authorization = `Bearer ${tokenPair.accessToken}`;
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'token' },
+ { authHeaders: { authorization } }
+ )
+ );
+
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'token' });
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.authHeaders).toEqual({ authorization });
expect(request.headers).not.toHaveProperty('authorization');
});
@@ -169,162 +205,115 @@ describe('TokenAuthenticationProvider', () => {
const request = httpServerMock.createKibanaRequest();
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
-
- mockOptions.tokens.refresh
- .withArgs(tokenPair.refreshToken)
- .resolves({ accessToken: 'newfoo', refreshToken: 'newbar' });
+ mockOptions.client.asScoped.mockImplementation(scopeableRequest => {
+ if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ return mockScopedClusterClient;
+ }
+
+ if (scopeableRequest?.headers.authorization === 'Bearer newfoo') {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(user);
+ return mockScopedClusterClient;
+ }
+
+ throw new Error('Unexpected call');
+ });
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer newfoo' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
+ mockOptions.tokens.refresh.mockResolvedValue({
+ accessToken: 'newfoo',
+ refreshToken: 'newbar',
+ });
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.succeeded(
+ { ...user, authentication_provider: 'token' },
+ {
+ authHeaders: { authorization: 'Bearer newfoo' },
+ state: { accessToken: 'newfoo', refreshToken: 'newbar' },
+ }
+ )
+ );
- sinon.assert.calledOnce(mockOptions.tokens.refresh);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'token' });
- expect(authenticationResult.state).toEqual({ accessToken: 'newfoo', refreshToken: 'newbar' });
- expect(authenticationResult.authHeaders).toEqual({ authorization: 'Bearer newfoo' });
expect(request.headers).not.toHaveProperty('authorization');
});
- it('does not handle `authorization` header with unsupported schema even if state contains valid credentials.', async () => {
- const request = httpServerMock.createKibanaRequest({
- headers: { authorization: 'Basic ***' },
- });
+ it('fails if authentication with token from state fails with unknown error.', async () => {
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
- const user = mockAuthenticatedUser();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const authorization = `Bearer ${tokenPair.accessToken}`;
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request, tokenPair);
-
- sinon.assert.notCalled(mockOptions.client.asScoped);
- expect(request.headers.authorization).toBe('Basic ***');
- expect(authenticationResult.notHandled()).toBe(true);
- });
-
- it('authenticates only via `authorization` header even if state is available.', async () => {
- const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
- const authorization = `Bearer foo-from-header`;
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
- const user = mockAuthenticatedUser();
-
- // GetUser will be called with request's `authorization` header.
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .resolves(user);
-
- const authenticationResult = await provider.authenticate(request, tokenPair);
-
- expect(authenticationResult.succeeded()).toBe(true);
- expect(authenticationResult.user).toEqual({ ...user, authentication_provider: 'token' });
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.authHeaders).toBeUndefined();
- expect(request.headers.authorization).toEqual('Bearer foo-from-header');
- });
-
- it('fails if authentication with token from header fails with unknown error', async () => {
- const authorization = `Bearer foo`;
- const request = httpServerMock.createKibanaRequest({ headers: { authorization } });
-
const authenticationError = new errors.InternalServerError('something went wrong');
- mockScopedClusterClient(mockOptions.client, sinon.match({ headers: { authorization } }))
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(authenticationError);
-
- const authenticationResult = await provider.authenticate(request);
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError);
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.error).toEqual(authenticationError);
- });
-
- it('fails if authentication with token from state fails with unknown error.', async () => {
- const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
- const request = httpServerMock.createKibanaRequest();
-
- const authenticationError = new errors.InternalServerError('something went wrong');
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(authenticationError);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(authenticationError)
+ );
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.error).toEqual(authenticationError);
});
it('fails if token refresh is rejected with unknown error', async () => {
- const request = httpServerMock.createKibanaRequest();
+ const request = httpServerMock.createKibanaRequest({ headers: {} });
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
+ const authorization = `Bearer ${tokenPair.accessToken}`;
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
const refreshError = new errors.InternalServerError('failed to refresh token');
- mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).rejects(refreshError);
+ mockOptions.tokens.refresh.mockRejectedValue(refreshError);
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(refreshError)
+ );
+
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
- sinon.assert.calledOnce(mockOptions.tokens.refresh);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.error).toEqual(refreshError);
});
it('redirects non-AJAX requests to /login and clears session if token cannot be refreshed', async () => {
- const request = httpServerMock.createKibanaRequest({ path: '/some-path' });
+ const request = httpServerMock.createKibanaRequest({ path: '/some-path', headers: {} });
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
+ const authorization = `Bearer ${tokenPair.accessToken}`;
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
+
+ mockOptions.tokens.refresh.mockResolvedValue(null);
- mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.redirectTo('/base-path/login?next=%2Fbase-path%2Fsome-path', {
+ state: null,
+ })
+ );
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
- sinon.assert.calledOnce(mockOptions.tokens.refresh);
+ expectAuthenticateCall(mockOptions.client, { headers: { authorization } });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe(
- '/base-path/login?next=%2Fbase-path%2Fsome-path'
- );
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toEqual(null);
- expect(authenticationResult.error).toBeUndefined();
});
it('does not redirect AJAX requests if token token cannot be refreshed', async () => {
@@ -333,61 +322,66 @@ describe('TokenAuthenticationProvider', () => {
path: '/some-path',
});
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
+ const authorization = `Bearer ${tokenPair.accessToken}`;
+
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
+ mockOptions.tokens.refresh.mockResolvedValue(null);
- mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null);
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(Boom.badRequest('Both access and refresh tokens are expired.'))
+ );
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
- sinon.assert.calledOnce(mockOptions.tokens.refresh);
+ expectAuthenticateCall(mockOptions.client, {
+ headers: { 'kbn-xsrf': 'xsrf', authorization },
+ });
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toEqual(
- Boom.badRequest('Both access and refresh tokens are expired.')
- );
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
});
it('fails if new access token is rejected after successful refresh', async () => {
const request = httpServerMock.createKibanaRequest();
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects({ statusCode: 401 });
-
- mockOptions.tokens.refresh
- .withArgs(tokenPair.refreshToken)
- .resolves({ accessToken: 'newfoo', refreshToken: 'newbar' });
-
const authenticationError = new errors.AuthenticationException('Some error');
- mockScopedClusterClient(
- mockOptions.client,
- sinon.match({ headers: { authorization: 'Bearer newfoo' } })
- )
- .callAsCurrentUser.withArgs('shield.authenticate')
- .rejects(authenticationError);
+ mockOptions.client.asScoped.mockImplementation(scopeableRequest => {
+ if (scopeableRequest?.headers.authorization === `Bearer ${tokenPair.accessToken}`) {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(
+ ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error())
+ );
+ return mockScopedClusterClient;
+ }
+
+ if (scopeableRequest?.headers.authorization === 'Bearer newfoo') {
+ const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
+ mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(authenticationError);
+ return mockScopedClusterClient;
+ }
+
+ throw new Error('Unexpected call');
+ });
- const authenticationResult = await provider.authenticate(request, tokenPair);
+ mockOptions.tokens.refresh.mockResolvedValue({
+ accessToken: 'newfoo',
+ refreshToken: 'newbar',
+ });
+
+ await expect(provider.authenticate(request, tokenPair)).resolves.toEqual(
+ AuthenticationResult.failed(authenticationError)
+ );
- sinon.assert.calledOnce(mockOptions.tokens.refresh);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.refresh).toHaveBeenCalledWith(tokenPair.refreshToken);
expect(request.headers).not.toHaveProperty('authorization');
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.user).toBeUndefined();
- expect(authenticationResult.state).toBeUndefined();
- expect(authenticationResult.error).toEqual(authenticationError);
});
});
@@ -395,13 +389,15 @@ describe('TokenAuthenticationProvider', () => {
it('returns `redirected` if state is not presented.', async () => {
const request = httpServerMock.createKibanaRequest();
- let deauthenticateResult = await provider.logout(request);
- expect(deauthenticateResult.redirected()).toBe(true);
+ await expect(provider.logout(request)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/base-path/login?msg=LOGGED_OUT')
+ );
- deauthenticateResult = await provider.logout(request, null);
- expect(deauthenticateResult.redirected()).toBe(true);
+ await expect(provider.logout(request, null)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/base-path/login?msg=LOGGED_OUT')
+ );
- sinon.assert.notCalled(mockOptions.tokens.invalidate);
+ expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled();
});
it('fails if `tokens.invalidate` fails', async () => {
@@ -409,45 +405,46 @@ describe('TokenAuthenticationProvider', () => {
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
const failureReason = new Error('failed to delete token');
- mockOptions.tokens.invalidate.withArgs(tokenPair).rejects(failureReason);
-
- const authenticationResult = await provider.logout(request, tokenPair);
+ mockOptions.tokens.invalidate.mockRejectedValue(failureReason);
- sinon.assert.calledOnce(mockOptions.tokens.invalidate);
- sinon.assert.calledWithExactly(mockOptions.tokens.invalidate, tokenPair);
+ await expect(provider.logout(request, tokenPair)).resolves.toEqual(
+ DeauthenticationResult.failed(failureReason)
+ );
- expect(authenticationResult.failed()).toBe(true);
- expect(authenticationResult.error).toBe(failureReason);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair);
});
it('redirects to /login if tokens are invalidated successfully', async () => {
const request = httpServerMock.createKibanaRequest();
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
- mockOptions.tokens.invalidate.withArgs(tokenPair).resolves();
+ mockOptions.tokens.invalidate.mockResolvedValue(undefined);
- const authenticationResult = await provider.logout(request, tokenPair);
-
- sinon.assert.calledOnce(mockOptions.tokens.invalidate);
- sinon.assert.calledWithExactly(mockOptions.tokens.invalidate, tokenPair);
+ await expect(provider.logout(request, tokenPair)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/base-path/login?msg=LOGGED_OUT')
+ );
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/base-path/login?msg=LOGGED_OUT');
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair);
});
it('redirects to /login with optional search parameters if tokens are invalidated successfully', async () => {
const request = httpServerMock.createKibanaRequest({ query: { yep: 'nope' } });
const tokenPair = { accessToken: 'foo', refreshToken: 'bar' };
- mockOptions.tokens.invalidate.withArgs(tokenPair).resolves();
-
- const authenticationResult = await provider.logout(request, tokenPair);
+ mockOptions.tokens.invalidate.mockResolvedValue(undefined);
- sinon.assert.calledOnce(mockOptions.tokens.invalidate);
- sinon.assert.calledWithExactly(mockOptions.tokens.invalidate, tokenPair);
+ await expect(provider.logout(request, tokenPair)).resolves.toEqual(
+ DeauthenticationResult.redirectTo('/base-path/login?yep=nope')
+ );
- expect(authenticationResult.redirected()).toBe(true);
- expect(authenticationResult.redirectURL).toBe('/base-path/login?yep=nope');
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
+ expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair);
});
});
+
+ it('`getHTTPAuthenticationScheme` method', () => {
+ expect(provider.getHTTPAuthenticationScheme()).toBe('bearer');
+ });
});
diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts
index 03fd003e2cbde2..fffac254ed30ac 100644
--- a/x-pack/plugins/security/server/authentication/providers/token.ts
+++ b/x-pack/plugins/security/server/authentication/providers/token.ts
@@ -8,9 +8,10 @@ import Boom from 'boom';
import { KibanaRequest } from '../../../../../../src/core/server';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';
-import { BaseAuthenticationProvider } from './base';
import { canRedirectRequest } from '../can_redirect_request';
+import { getHTTPAuthenticationScheme } from '../get_http_authentication_scheme';
import { Tokens, TokenPair } from '../tokens';
+import { BaseAuthenticationProvider } from './base';
/**
* Describes the parameters that are required by the provider to process the initial login request.
@@ -34,12 +35,6 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider {
*/
static readonly type = 'token';
- /**
- * Performs initial login request using username and password.
- * @param request Request instance.
- * @param loginAttempt Login attempt description.
- * @param [state] Optional state object associated with the provider.
- */
/**
* Performs initial login request using username and password.
* @param request Request instance.
@@ -87,18 +82,13 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider {
public async authenticate(request: KibanaRequest, state?: ProviderState | null) {
this.logger.debug(`Trying to authenticate user request to ${request.url.path}.`);
- // if there isn't a payload, try header-based token auth
- const {
- authenticationResult: headerAuthResult,
- headerNotRecognized,
- } = await this.authenticateViaHeader(request);
- if (headerNotRecognized) {
- return headerAuthResult;
+ if (getHTTPAuthenticationScheme(request) != null) {
+ this.logger.debug('Cannot authenticate requests with `Authorization` header.');
+ return AuthenticationResult.notHandled();
}
- let authenticationResult = headerAuthResult;
- // if we still can't attempt auth, try authenticating via state (session token)
- if (authenticationResult.notHandled() && state) {
+ let authenticationResult = AuthenticationResult.notHandled();
+ if (state) {
authenticationResult = await this.authenticateViaState(request, state);
if (
authenticationResult.failed() &&
@@ -111,6 +101,7 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider {
// finally, if authentication still can not be handled for this
// request/state combination, redirect to the login page if appropriate
if (authenticationResult.notHandled() && canRedirectRequest(request)) {
+ this.logger.debug('Redirecting request to Login page.');
authenticationResult = AuthenticationResult.redirectTo(this.getLoginPageURL(request));
}
@@ -144,37 +135,11 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider {
}
/**
- * Validates whether request contains `Bearer ***` Authorization header and just passes it
- * forward to Elasticsearch backend.
- * @param request Request instance.
+ * Returns HTTP authentication scheme (`Bearer`) that's used within `Authorization` HTTP header
+ * that provider attaches to all successfully authenticated requests to Elasticsearch.
*/
- private async authenticateViaHeader(request: KibanaRequest) {
- this.logger.debug('Trying to authenticate via header.');
-
- const authorization = request.headers.authorization;
- if (!authorization || typeof authorization !== 'string') {
- this.logger.debug('Authorization header is not presented.');
- return { authenticationResult: AuthenticationResult.notHandled() };
- }
-
- const authenticationSchema = authorization.split(/\s+/)[0];
- if (authenticationSchema.toLowerCase() !== 'bearer') {
- this.logger.debug(`Unsupported authentication schema: ${authenticationSchema}`);
- return { authenticationResult: AuthenticationResult.notHandled(), headerNotRecognized: true };
- }
-
- try {
- const user = await this.getUser(request);
-
- this.logger.debug('Request has been authenticated via header.');
-
- // We intentionally do not store anything in session state because token
- // header auth can only be used on a request by request basis.
- return { authenticationResult: AuthenticationResult.succeeded(user) };
- } catch (err) {
- this.logger.debug(`Failed to authenticate request via header: ${err.message}`);
- return { authenticationResult: AuthenticationResult.failed(err) };
- }
+ public getHTTPAuthenticationScheme() {
+ return 'bearer';
}
/**
diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts
index f7374eedb5520d..64c695670fa19f 100644
--- a/x-pack/plugins/security/server/config.test.ts
+++ b/x-pack/plugins/security/server/config.test.ts
@@ -13,57 +13,78 @@ import { createConfig$, ConfigSchema } from './config';
describe('config schema', () => {
it('generates proper defaults', () => {
expect(ConfigSchema.validate({})).toMatchInlineSnapshot(`
- Object {
- "authc": Object {
- "providers": Array [
- "basic",
- ],
- },
- "cookieName": "sid",
- "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- "loginAssistanceMessage": "",
- "secureCookies": false,
- "session": Object {
- "idleTimeout": null,
- "lifespan": null,
- },
- }
- `);
+ Object {
+ "authc": Object {
+ "http": Object {
+ "autoSchemesEnabled": true,
+ "enabled": true,
+ "schemes": Array [
+ "apikey",
+ ],
+ },
+ "providers": Array [
+ "basic",
+ ],
+ },
+ "cookieName": "sid",
+ "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "loginAssistanceMessage": "",
+ "secureCookies": false,
+ "session": Object {
+ "idleTimeout": null,
+ "lifespan": null,
+ },
+ }
+ `);
expect(ConfigSchema.validate({}, { dist: false })).toMatchInlineSnapshot(`
- Object {
- "authc": Object {
- "providers": Array [
- "basic",
- ],
- },
- "cookieName": "sid",
- "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
- "loginAssistanceMessage": "",
- "secureCookies": false,
- "session": Object {
- "idleTimeout": null,
- "lifespan": null,
- },
- }
- `);
+ Object {
+ "authc": Object {
+ "http": Object {
+ "autoSchemesEnabled": true,
+ "enabled": true,
+ "schemes": Array [
+ "apikey",
+ ],
+ },
+ "providers": Array [
+ "basic",
+ ],
+ },
+ "cookieName": "sid",
+ "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "loginAssistanceMessage": "",
+ "secureCookies": false,
+ "session": Object {
+ "idleTimeout": null,
+ "lifespan": null,
+ },
+ }
+ `);
expect(ConfigSchema.validate({}, { dist: true })).toMatchInlineSnapshot(`
- Object {
- "authc": Object {
- "providers": Array [
- "basic",
- ],
- },
- "cookieName": "sid",
- "loginAssistanceMessage": "",
- "secureCookies": false,
- "session": Object {
- "idleTimeout": null,
- "lifespan": null,
- },
- }
- `);
+ Object {
+ "authc": Object {
+ "http": Object {
+ "autoSchemesEnabled": true,
+ "enabled": true,
+ "schemes": Array [
+ "apikey",
+ ],
+ },
+ "providers": Array [
+ "basic",
+ ],
+ },
+ "cookieName": "sid",
+ "loginAssistanceMessage": "",
+ "secureCookies": false,
+ "session": Object {
+ "idleTimeout": null,
+ "lifespan": null,
+ },
+ }
+ `);
});
it('should throw error if xpack.security.encryptionKey is less than 32 characters', () => {
@@ -101,15 +122,22 @@ describe('config schema', () => {
authc: { providers: ['oidc'], oidc: { realm: 'realm-1' } },
}).authc
).toMatchInlineSnapshot(`
- Object {
- "oidc": Object {
- "realm": "realm-1",
- },
- "providers": Array [
- "oidc",
- ],
- }
- `);
+ Object {
+ "http": Object {
+ "autoSchemesEnabled": true,
+ "enabled": true,
+ "schemes": Array [
+ "apikey",
+ ],
+ },
+ "oidc": Object {
+ "realm": "realm-1",
+ },
+ "providers": Array [
+ "oidc",
+ ],
+ }
+ `);
});
it(`returns a validation error when authc.providers is "['oidc', 'basic']" and realm is unspecified`, async () => {
@@ -126,16 +154,23 @@ describe('config schema', () => {
authc: { providers: ['oidc', 'basic'], oidc: { realm: 'realm-1' } },
}).authc
).toMatchInlineSnapshot(`
- Object {
- "oidc": Object {
- "realm": "realm-1",
- },
- "providers": Array [
- "oidc",
- "basic",
- ],
- }
- `);
+ Object {
+ "http": Object {
+ "autoSchemesEnabled": true,
+ "enabled": true,
+ "schemes": Array [
+ "apikey",
+ ],
+ },
+ "oidc": Object {
+ "realm": "realm-1",
+ },
+ "providers": Array [
+ "oidc",
+ "basic",
+ ],
+ }
+ `);
});
it(`realm is not allowed when authc.providers is "['basic']"`, async () => {
@@ -164,18 +199,25 @@ describe('config schema', () => {
authc: { providers: ['saml'], saml: { realm: 'realm-1' } },
}).authc
).toMatchInlineSnapshot(`
- Object {
- "providers": Array [
- "saml",
- ],
- "saml": Object {
- "maxRedirectURLSize": ByteSizeValue {
- "valueInBytes": 2048,
- },
- "realm": "realm-1",
- },
- }
- `);
+ Object {
+ "http": Object {
+ "autoSchemesEnabled": true,
+ "enabled": true,
+ "schemes": Array [
+ "apikey",
+ ],
+ },
+ "providers": Array [
+ "saml",
+ ],
+ "saml": Object {
+ "maxRedirectURLSize": ByteSizeValue {
+ "valueInBytes": 2048,
+ },
+ "realm": "realm-1",
+ },
+ }
+ `);
});
it('`realm` is not allowed if saml provider is not enabled', async () => {
diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts
index db8c48f314d7ce..8663a6e61c2030 100644
--- a/x-pack/plugins/security/server/config.ts
+++ b/x-pack/plugins/security/server/config.ts
@@ -49,6 +49,11 @@ export const ConfigSchema = schema.object(
maxRedirectURLSize: schema.byteSize({ defaultValue: '2kb' }),
})
),
+ http: schema.object({
+ enabled: schema.boolean({ defaultValue: true }),
+ autoSchemesEnabled: schema.boolean({ defaultValue: true }),
+ schemes: schema.arrayOf(schema.string(), { defaultValue: ['apikey'] }),
+ }),
}),
},
// This option should be removed as soon as we entirely migrate config from legacy Security plugin.
diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts
index c0e86b289fe545..e1167af0be7f05 100644
--- a/x-pack/plugins/security/server/index.ts
+++ b/x-pack/plugins/security/server/index.ts
@@ -32,6 +32,17 @@ export const config: PluginConfigDescriptor> = {
deprecations: ({ rename, unused }) => [
rename('sessionTimeout', 'session.idleTimeout'),
unused('authorization.legacyFallback.enabled'),
+ (settings, fromPath, log) => {
+ const hasProvider = (provider: string) =>
+ settings?.xpack?.security?.authc?.providers?.includes(provider) ?? false;
+
+ if (hasProvider('basic') && hasProvider('token')) {
+ log(
+ 'Enabling both `basic` and `token` authentication providers in `xpack.security.authc.providers` is deprecated. Login page will only use `token` provider.'
+ );
+ }
+ return settings;
+ },
],
};
export const plugin: PluginInitializer<
diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts
index 56aad4ece3e958..6f5c79e873e868 100644
--- a/x-pack/plugins/security/server/plugin.test.ts
+++ b/x-pack/plugins/security/server/plugin.test.ts
@@ -28,6 +28,7 @@ describe('Security Plugin', () => {
authc: {
providers: ['saml', 'token'],
saml: { realm: 'saml1', maxRedirectURLSize: new ByteSizeValue(2048) },
+ http: { enabled: true, autoSchemesEnabled: true, schemes: ['apikey'] },
},
})
);
@@ -77,6 +78,7 @@ describe('Security Plugin', () => {
"getSessionInfo": [Function],
"invalidateAPIKey": [Function],
"isAuthenticated": [Function],
+ "isProviderEnabled": [Function],
"login": [Function],
"logout": [Function],
},
diff --git a/x-pack/plugins/security/server/routes/authentication/basic.test.ts b/x-pack/plugins/security/server/routes/authentication/basic.test.ts
index be17b3e29f8548..cc1c94d799be61 100644
--- a/x-pack/plugins/security/server/routes/authentication/basic.test.ts
+++ b/x-pack/plugins/security/server/routes/authentication/basic.test.ts
@@ -33,7 +33,9 @@ describe('Basic authentication routes', () => {
let mockContext: RequestHandlerContext;
beforeEach(() => {
router = httpServiceMock.createRouter();
+
authc = authenticationMock.create();
+ authc.isProviderEnabled.mockImplementation(provider => provider === 'basic');
mockContext = ({
licensing: {
@@ -166,6 +168,22 @@ describe('Basic authentication routes', () => {
value: { username: 'user', password: 'password' },
});
});
+
+ it('prefers `token` authentication provider if it is enabled', async () => {
+ authc.login.mockResolvedValue(AuthenticationResult.succeeded(mockAuthenticatedUser()));
+ authc.isProviderEnabled.mockImplementation(
+ provider => provider === 'token' || provider === 'basic'
+ );
+
+ const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory);
+
+ expect(response.status).toBe(204);
+ expect(response.payload).toBeUndefined();
+ expect(authc.login).toHaveBeenCalledWith(mockRequest, {
+ provider: 'token',
+ value: { username: 'user', password: 'password' },
+ });
+ });
});
});
});
diff --git a/x-pack/plugins/security/server/routes/authentication/basic.ts b/x-pack/plugins/security/server/routes/authentication/basic.ts
index 453dc1c4ea3b55..db36e45fc07e89 100644
--- a/x-pack/plugins/security/server/routes/authentication/basic.ts
+++ b/x-pack/plugins/security/server/routes/authentication/basic.ts
@@ -25,16 +25,13 @@ export function defineBasicRoutes({ router, authc, config }: RouteDefinitionPara
options: { authRequired: false },
},
createLicensedRouteHandler(async (context, request, response) => {
- const { username, password } = request.body;
+ // We should prefer `token` over `basic` if possible.
+ const loginAttempt = authc.isProviderEnabled('token')
+ ? { provider: 'token', value: request.body }
+ : { provider: 'basic', value: request.body };
try {
- // We should prefer `token` over `basic` if possible.
- const providerToLoginWith = config.authc.providers.includes('token') ? 'token' : 'basic';
- const authenticationResult = await authc.login(request, {
- provider: providerToLoginWith,
- value: { username, password },
- });
-
+ const authenticationResult = await authc.login(request, loginAttempt);
if (!authenticationResult.succeeded()) {
return response.unauthorized({ body: authenticationResult.error });
}
diff --git a/x-pack/plugins/security/server/routes/authentication/index.ts b/x-pack/plugins/security/server/routes/authentication/index.ts
index 6035025564cbfe..a774edfb4ab2c0 100644
--- a/x-pack/plugins/security/server/routes/authentication/index.ts
+++ b/x-pack/plugins/security/server/routes/authentication/index.ts
@@ -27,18 +27,15 @@ export function defineAuthenticationRoutes(params: RouteDefinitionParams) {
defineSessionRoutes(params);
defineCommonRoutes(params);
- if (
- params.config.authc.providers.includes('basic') ||
- params.config.authc.providers.includes('token')
- ) {
+ if (params.authc.isProviderEnabled('basic') || params.authc.isProviderEnabled('token')) {
defineBasicRoutes(params);
}
- if (params.config.authc.providers.includes('saml')) {
+ if (params.authc.isProviderEnabled('saml')) {
defineSAMLRoutes(params);
}
- if (params.config.authc.providers.includes('oidc')) {
+ if (params.authc.isProviderEnabled('oidc')) {
defineOIDCRoutes(params);
}
}
diff --git a/x-pack/plugins/spaces/server/capabilities/capabilities_provider.test.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_provider.test.ts
new file mode 100644
index 00000000000000..8678bdceb70f9b
--- /dev/null
+++ b/x-pack/plugins/spaces/server/capabilities/capabilities_provider.test.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { capabilitiesProvider } from './capabilities_provider';
+
+describe('Capabilities provider', () => {
+ it('provides the expected capabilities', () => {
+ expect(capabilitiesProvider()).toMatchInlineSnapshot(`
+ Object {
+ "management": Object {
+ "kibana": Object {
+ "spaces": true,
+ },
+ },
+ "spaces": Object {
+ "manage": true,
+ },
+ }
+ `);
+ });
+});
diff --git a/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_provider.ts
similarity index 61%
rename from x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts
rename to x-pack/plugins/spaces/server/capabilities/capabilities_provider.ts
index 26fdff73b34606..5976aabfa66e8a 100644
--- a/x-pack/legacy/plugins/ml/server/lib/security_utils.d.ts
+++ b/x-pack/plugins/spaces/server/capabilities/capabilities_provider.ts
@@ -4,6 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { XPackMainPlugin } from '../../../xpack_main/server/xpack_main';
-
-export function isSecurityDisabled(xpackMainPlugin: XPackMainPlugin): boolean;
+export const capabilitiesProvider = () => ({
+ spaces: {
+ manage: true,
+ },
+ management: {
+ kibana: {
+ spaces: true,
+ },
+ },
+});
diff --git a/x-pack/plugins/spaces/server/lib/toggle_ui_capabilities.test.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts
similarity index 53%
rename from x-pack/plugins/spaces/server/lib/toggle_ui_capabilities.test.ts
rename to x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts
index b92922def2eb81..3f7b93c754aef3 100644
--- a/x-pack/plugins/spaces/server/lib/toggle_ui_capabilities.test.ts
+++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.test.ts
@@ -6,8 +6,12 @@
import { Feature } from '../../../../plugins/features/server';
import { Space } from '../../common/model/space';
-import { toggleUICapabilities } from './toggle_ui_capabilities';
-import { Capabilities } from 'src/core/public';
+import { setupCapabilitiesSwitcher } from './capabilities_switcher';
+import { Capabilities, CoreSetup } from 'src/core/server';
+import { coreMock, httpServerMock, loggingServiceMock } from 'src/core/server/mocks';
+import { featuresPluginMock } from '../../../features/server/mocks';
+import { spacesServiceMock } from '../spaces_service/spaces_service.mock';
+import { PluginsStart } from '../plugin';
const features: Feature[] = [
{
@@ -91,8 +95,33 @@ const buildCapabilities = () =>
},
}) as Capabilities;
-describe('toggleUiCapabilities', () => {
- it('does not toggle capabilities when the space has no disabled features', () => {
+const setup = (space: Space) => {
+ const coreSetup = coreMock.createSetup();
+
+ const featuresStart = featuresPluginMock.createStart();
+ featuresStart.getFeatures.mockReturnValue(features);
+
+ coreSetup.getStartServices.mockResolvedValue([
+ coreMock.createStart(),
+ { features: featuresStart },
+ ]);
+
+ const spacesService = spacesServiceMock.createSetupContract();
+ spacesService.getActiveSpace.mockResolvedValue(space);
+
+ const logger = loggingServiceMock.createLogger();
+
+ const switcher = setupCapabilitiesSwitcher(
+ (coreSetup as unknown) as CoreSetup,
+ spacesService,
+ logger
+ );
+
+ return { switcher, logger, spacesService };
+};
+
+describe('capabilitiesSwitcher', () => {
+ it('does not toggle capabilities when the space has no disabled features', async () => {
const space: Space = {
id: 'space',
name: '',
@@ -100,11 +129,54 @@ describe('toggleUiCapabilities', () => {
};
const capabilities = buildCapabilities();
- const result = toggleUICapabilities(features, capabilities, space);
+
+ const { switcher } = setup(space);
+ const request = httpServerMock.createKibanaRequest();
+ const result = await switcher(request, capabilities);
+
+ expect(result).toEqual(buildCapabilities());
+ });
+
+ it('does not toggle capabilities when the request is not authenticated', async () => {
+ const space: Space = {
+ id: 'space',
+ name: '',
+ disabledFeatures: ['feature_1', 'feature_2', 'feature_3'],
+ };
+
+ const capabilities = buildCapabilities();
+
+ const { switcher } = setup(space);
+ const request = httpServerMock.createKibanaRequest({ routeAuthRequired: false });
+
+ const result = await switcher(request, capabilities);
+
+ expect(result).toEqual(buildCapabilities());
+ });
+
+ it('logs a warning, and does not toggle capabilities if an error is encountered', async () => {
+ const space: Space = {
+ id: 'space',
+ name: '',
+ disabledFeatures: ['feature_1', 'feature_2', 'feature_3'],
+ };
+
+ const capabilities = buildCapabilities();
+
+ const { switcher, logger, spacesService } = setup(space);
+ const request = httpServerMock.createKibanaRequest();
+
+ spacesService.getActiveSpace.mockRejectedValue(new Error('Something terrible happened'));
+
+ const result = await switcher(request, capabilities);
+
expect(result).toEqual(buildCapabilities());
+ expect(logger.warn).toHaveBeenCalledWith(
+ `Error toggling capabilities for request to /path: Error: Something terrible happened`
+ );
});
- it('ignores unknown disabledFeatures', () => {
+ it('ignores unknown disabledFeatures', async () => {
const space: Space = {
id: 'space',
name: '',
@@ -112,11 +184,15 @@ describe('toggleUiCapabilities', () => {
};
const capabilities = buildCapabilities();
- const result = toggleUICapabilities(features, capabilities, space);
+
+ const { switcher } = setup(space);
+ const request = httpServerMock.createKibanaRequest();
+ const result = await switcher(request, capabilities);
+
expect(result).toEqual(buildCapabilities());
});
- it('disables the corresponding navLink, catalogue, management sections, and all capability flags for disabled features', () => {
+ it('disables the corresponding navLink, catalogue, management sections, and all capability flags for disabled features', async () => {
const space: Space = {
id: 'space',
name: '',
@@ -124,7 +200,10 @@ describe('toggleUiCapabilities', () => {
};
const capabilities = buildCapabilities();
- const result = toggleUICapabilities(features, capabilities, space);
+
+ const { switcher } = setup(space);
+ const request = httpServerMock.createKibanaRequest();
+ const result = await switcher(request, capabilities);
const expectedCapabilities = buildCapabilities();
@@ -137,7 +216,7 @@ describe('toggleUiCapabilities', () => {
expect(result).toEqual(expectedCapabilities);
});
- it('can disable everything', () => {
+ it('can disable everything', async () => {
const space: Space = {
id: 'space',
name: '',
@@ -145,7 +224,10 @@ describe('toggleUiCapabilities', () => {
};
const capabilities = buildCapabilities();
- const result = toggleUICapabilities(features, capabilities, space);
+
+ const { switcher } = setup(space);
+ const request = httpServerMock.createKibanaRequest();
+ const result = await switcher(request, capabilities);
const expectedCapabilities = buildCapabilities();
diff --git a/x-pack/plugins/spaces/server/lib/toggle_ui_capabilities.ts b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts
similarity index 66%
rename from x-pack/plugins/spaces/server/lib/toggle_ui_capabilities.ts
rename to x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts
index 2de84ec05017b7..317cc7fe0e3c3c 100644
--- a/x-pack/plugins/spaces/server/lib/toggle_ui_capabilities.ts
+++ b/x-pack/plugins/spaces/server/capabilities/capabilities_switcher.ts
@@ -4,15 +4,41 @@
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
-import { UICapabilities } from 'ui/capabilities';
+import { Capabilities, CapabilitiesSwitcher, CoreSetup, Logger } from 'src/core/server';
import { Feature } from '../../../../plugins/features/server';
import { Space } from '../../common/model/space';
+import { SpacesServiceSetup } from '../spaces_service';
+import { PluginsStart } from '../plugin';
-export function toggleUICapabilities(
- features: Feature[],
- capabilities: UICapabilities,
- activeSpace: Space
-) {
+export function setupCapabilitiesSwitcher(
+ core: CoreSetup,
+ spacesService: SpacesServiceSetup,
+ logger: Logger
+): CapabilitiesSwitcher {
+ return async (request, capabilities) => {
+ const isAnonymousRequest = !request.route.options.authRequired;
+
+ if (isAnonymousRequest) {
+ return capabilities;
+ }
+
+ try {
+ const [activeSpace, [, { features }]] = await Promise.all([
+ spacesService.getActiveSpace(request),
+ core.getStartServices(),
+ ]);
+
+ const registeredFeatures = features.getFeatures();
+
+ return toggleCapabilities(registeredFeatures, capabilities, activeSpace);
+ } catch (e) {
+ logger.warn(`Error toggling capabilities for request to ${request.url.pathname}: ${e}`);
+ return capabilities;
+ }
+ };
+}
+
+function toggleCapabilities(features: Feature[], capabilities: Capabilities, activeSpace: Space) {
const clonedCapabilities = _.cloneDeep(capabilities);
toggleDisabledFeatures(features, clonedCapabilities, activeSpace);
@@ -22,7 +48,7 @@ export function toggleUICapabilities(
function toggleDisabledFeatures(
features: Feature[],
- capabilities: UICapabilities,
+ capabilities: Capabilities,
activeSpace: Space
) {
const disabledFeatureKeys = activeSpace.disabledFeatures;
diff --git a/x-pack/plugins/spaces/server/capabilities/index.ts b/x-pack/plugins/spaces/server/capabilities/index.ts
new file mode 100644
index 00000000000000..56a72a2eeaf197
--- /dev/null
+++ b/x-pack/plugins/spaces/server/capabilities/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { CoreSetup, Logger } from 'src/core/server';
+import { capabilitiesProvider } from './capabilities_provider';
+import { setupCapabilitiesSwitcher } from './capabilities_switcher';
+import { PluginsStart } from '../plugin';
+import { SpacesServiceSetup } from '../spaces_service';
+
+export const setupCapabilities = (
+ core: CoreSetup,
+ spacesService: SpacesServiceSetup,
+ logger: Logger
+) => {
+ core.capabilities.registerProvider(capabilitiesProvider);
+ core.capabilities.registerSwitcher(setupCapabilitiesSwitcher(core, spacesService, logger));
+};
diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts
index 92be88b91c6523..61157a93187815 100644
--- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts
+++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts
@@ -201,12 +201,10 @@ describe('onPostAuthInterceptor', () => {
// interceptor to parse out the space id and rewrite the request's URL. Rather than duplicating that logic,
// we are including the already tested interceptor here in the test chain.
initSpacesOnRequestInterceptor({
- getLegacyAPI: () => legacyAPI,
http: (http as unknown) as CoreSetup['http'],
});
initSpacesOnPostAuthRequestInterceptor({
- getLegacyAPI: () => legacyAPI,
http: (http as unknown) as CoreSetup['http'],
log: loggingMock,
features: featuresPlugin,
diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts
index 4674f3641084ae..b07ff11f6efc62 100644
--- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts
+++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.ts
@@ -7,13 +7,12 @@ import { Logger, CoreSetup } from 'src/core/server';
import { Space } from '../../../common/model/space';
import { wrapError } from '../errors';
import { SpacesServiceSetup } from '../../spaces_service/spaces_service';
-import { LegacyAPI, PluginsSetup } from '../../plugin';
+import { PluginsSetup } from '../../plugin';
import { getSpaceSelectorUrl } from '../get_space_selector_url';
import { DEFAULT_SPACE_ID, ENTER_SPACE_PATH } from '../../../common/constants';
import { addSpaceIdToPath } from '../../../common';
export interface OnPostAuthInterceptorDeps {
- getLegacyAPI(): LegacyAPI;
http: CoreSetup['http'];
features: PluginsSetup['features'];
spacesService: SpacesServiceSetup;
@@ -22,7 +21,6 @@ export interface OnPostAuthInterceptorDeps {
export function initSpacesOnPostAuthRequestInterceptor({
features,
- getLegacyAPI,
spacesService,
log,
http,
diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts
index 5e6cf67ee8c907..448bc39eb606e0 100644
--- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts
+++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts
@@ -16,7 +16,6 @@ import {
} from '../../../../../../src/core/server';
import * as kbnTestServer from '../../../../../../src/test_utils/kbn_server';
-import { LegacyAPI } from '../../plugin';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
describe('onRequestInterceptor', () => {
@@ -110,10 +109,6 @@ describe('onRequestInterceptor', () => {
elasticsearch.esNodesCompatibility$ = elasticsearchServiceMock.createInternalSetup().esNodesCompatibility$;
initSpacesOnRequestInterceptor({
- getLegacyAPI: () =>
- ({
- legacyConfig: {},
- } as LegacyAPI),
http: (http as unknown) as CoreSetup['http'],
});
diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts
index 22d704c1b7e13c..c59851f8b80617 100644
--- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts
+++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.ts
@@ -12,14 +12,12 @@ import {
import { format } from 'url';
import { DEFAULT_SPACE_ID } from '../../../common/constants';
import { modifyUrl } from '../utils/url';
-import { LegacyAPI } from '../../plugin';
import { getSpaceIdFromPath } from '../../../common';
export interface OnRequestInterceptorDeps {
- getLegacyAPI(): LegacyAPI;
http: CoreSetup['http'];
}
-export function initSpacesOnRequestInterceptor({ getLegacyAPI, http }: OnRequestInterceptorDeps) {
+export function initSpacesOnRequestInterceptor({ http }: OnRequestInterceptorDeps) {
http.registerOnPreAuth(async function spacesOnPreAuthHandler(
request: KibanaRequest,
response: LifecycleResponseFactory,
diff --git a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts
index a3396e98c35122..094ca8a11816ea 100644
--- a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts
+++ b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts
@@ -23,7 +23,6 @@ import { securityMock } from '../../../security/server/mocks';
const log = loggingServiceMock.createLogger();
const legacyAPI: LegacyAPI = {
- legacyConfig: {},
savedObjects: {} as SavedObjectsLegacyService,
} as LegacyAPI;
diff --git a/x-pack/plugins/spaces/server/plugin.test.ts b/x-pack/plugins/spaces/server/plugin.test.ts
new file mode 100644
index 00000000000000..4e3f4f52cbeb49
--- /dev/null
+++ b/x-pack/plugins/spaces/server/plugin.test.ts
@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { CoreSetup } from 'src/core/server';
+import { coreMock } from 'src/core/server/mocks';
+import { featuresPluginMock } from '../../features/server/mocks';
+import { licensingMock } from '../../licensing/server/mocks';
+import { Plugin, PluginsSetup } from './plugin';
+import { usageCollectionPluginMock } from '../../../../src/plugins/usage_collection/server/mocks';
+
+describe('Spaces Plugin', () => {
+ describe('#setup', () => {
+ it('can setup with all optional plugins disabled, exposing the expected contract', async () => {
+ const initializerContext = coreMock.createPluginInitializerContext({});
+ const core = coreMock.createSetup() as CoreSetup;
+ const features = featuresPluginMock.createSetup();
+ const licensing = licensingMock.createSetup();
+
+ const plugin = new Plugin(initializerContext);
+ const spacesSetup = await plugin.setup(core, { features, licensing });
+ expect(spacesSetup).toMatchInlineSnapshot(`
+ Object {
+ "__legacyCompat": Object {
+ "createDefaultSpace": [Function],
+ "registerLegacyAPI": [Function],
+ },
+ "spacesService": Object {
+ "getActiveSpace": [Function],
+ "getBasePath": [Function],
+ "getSpaceId": [Function],
+ "isInDefaultSpace": [Function],
+ "namespaceToSpaceId": [Function],
+ "scopedClient": [Function],
+ "spaceIdToNamespace": [Function],
+ },
+ }
+ `);
+ });
+
+ it('registers the capabilities provider and switcher', async () => {
+ const initializerContext = coreMock.createPluginInitializerContext({});
+ const core = coreMock.createSetup() as CoreSetup;
+ const features = featuresPluginMock.createSetup();
+ const licensing = licensingMock.createSetup();
+
+ const plugin = new Plugin(initializerContext);
+
+ await plugin.setup(core, { features, licensing });
+
+ expect(core.capabilities.registerProvider).toHaveBeenCalledTimes(1);
+ expect(core.capabilities.registerSwitcher).toHaveBeenCalledTimes(1);
+ });
+
+ it('registers the usage collector', async () => {
+ const initializerContext = coreMock.createPluginInitializerContext({});
+ const core = coreMock.createSetup() as CoreSetup;
+ const features = featuresPluginMock.createSetup();
+ const licensing = licensingMock.createSetup();
+
+ const usageCollection = usageCollectionPluginMock.createSetupContract();
+
+ const plugin = new Plugin(initializerContext);
+
+ await plugin.setup(core, { features, licensing, usageCollection });
+
+ expect(usageCollection.getCollectorByType('spaces')).toBeDefined();
+ });
+ });
+});
diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts
index 90c2da6e69df8c..d125e0f54e9c1f 100644
--- a/x-pack/plugins/spaces/server/plugin.ts
+++ b/x-pack/plugins/spaces/server/plugin.ts
@@ -13,7 +13,10 @@ import {
Logger,
PluginInitializerContext,
} from '../../../../src/core/server';
-import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
+import {
+ PluginSetupContract as FeaturesPluginSetup,
+ PluginStartContract as FeaturesPluginStart,
+} from '../../features/server';
import { SecurityPluginSetup } from '../../security/server';
import { LicensingPluginSetup } from '../../licensing/server';
import { createDefaultSpace } from './lib/create_default_space';
@@ -22,15 +25,15 @@ import { AuditLogger } from '../../../../server/lib/audit_logger';
import { spacesSavedObjectsClientWrapperFactory } from './lib/saved_objects_client/saved_objects_client_wrapper_factory';
import { SpacesAuditLogger } from './lib/audit_logger';
import { createSpacesTutorialContextFactory } from './lib/spaces_tutorial_context_factory';
-import { registerSpacesUsageCollector } from './lib/spaces_usage_collector';
+import { registerSpacesUsageCollector } from './usage_collection';
import { SpacesService } from './spaces_service';
import { SpacesServiceSetup } from './spaces_service';
import { ConfigType } from './config';
-import { toggleUICapabilities } from './lib/toggle_ui_capabilities';
import { initSpacesRequestInterceptors } from './lib/request_interceptors';
import { initExternalSpacesApi } from './routes/api/external';
import { initInternalSpacesApi } from './routes/api/internal';
import { initSpacesViewsRoutes } from './routes/views';
+import { setupCapabilities } from './capabilities';
/**
* Describes a set of APIs that is available in the legacy platform only and required by this plugin
@@ -41,9 +44,6 @@ export interface LegacyAPI {
auditLogger: {
create: (pluginId: string) => AuditLogger;
};
- legacyConfig: {
- kibanaIndex: string;
- };
}
export interface PluginsSetup {
@@ -54,6 +54,10 @@ export interface PluginsSetup {
home?: HomeServerPluginSetup;
}
+export interface PluginsStart {
+ features: FeaturesPluginStart;
+}
+
export interface SpacesPluginSetup {
spacesService: SpacesServiceSetup;
__legacyCompat: {
@@ -70,6 +74,8 @@ export class Plugin {
private readonly config$: Observable;
+ private readonly kibanaIndexConfig$: Observable<{ kibana: { index: string } }>;
+
private readonly log: Logger;
private legacyAPI?: LegacyAPI;
@@ -92,12 +98,16 @@ export class Plugin {
constructor(initializerContext: PluginInitializerContext) {
this.config$ = initializerContext.config.create();
+ this.kibanaIndexConfig$ = initializerContext.config.legacy.globalConfig$;
this.log = initializerContext.logger.get();
}
public async start() {}
- public async setup(core: CoreSetup, plugins: PluginsSetup): Promise {
+ public async setup(
+ core: CoreSetup,
+ plugins: PluginsSetup
+ ): Promise {
const service = new SpacesService(this.log, this.getLegacyAPI);
const spacesService = await service.setup({
@@ -131,20 +141,19 @@ export class Plugin {
initSpacesRequestInterceptors({
http: core.http,
log: this.log,
- getLegacyAPI: this.getLegacyAPI,
spacesService,
features: plugins.features,
});
- core.capabilities.registerSwitcher(async (request, uiCapabilities) => {
- try {
- const activeSpace = await spacesService.getActiveSpace(request);
- const features = plugins.features.getFeatures();
- return toggleUICapabilities(features, uiCapabilities, activeSpace);
- } catch (e) {
- return uiCapabilities;
- }
- });
+ setupCapabilities(core, spacesService, this.log);
+
+ if (plugins.usageCollection) {
+ registerSpacesUsageCollector(plugins.usageCollection, {
+ kibanaIndexConfig$: this.kibanaIndexConfig$,
+ features: plugins.features,
+ licensing: plugins.licensing,
+ });
+ }
if (plugins.security) {
plugins.security.registerSpacesService(spacesService);
@@ -161,12 +170,7 @@ export class Plugin {
__legacyCompat: {
registerLegacyAPI: (legacyAPI: LegacyAPI) => {
this.legacyAPI = legacyAPI;
- this.setupLegacyComponents(
- spacesService,
- plugins.features,
- plugins.licensing,
- plugins.usageCollection
- );
+ this.setupLegacyComponents(spacesService);
},
createDefaultSpace: async () => {
return await createDefaultSpace({
@@ -180,12 +184,7 @@ export class Plugin {
public stop() {}
- private setupLegacyComponents(
- spacesService: SpacesServiceSetup,
- featuresSetup: FeaturesPluginSetup,
- licensingSetup: LicensingPluginSetup,
- usageCollectionSetup?: UsageCollectionSetup
- ) {
+ private setupLegacyComponents(spacesService: SpacesServiceSetup) {
const legacyAPI = this.getLegacyAPI();
const { addScopedSavedObjectsClientWrapperFactory, types } = legacyAPI.savedObjects;
addScopedSavedObjectsClientWrapperFactory(
@@ -193,11 +192,5 @@ export class Plugin {
'spaces',
spacesSavedObjectsClientWrapperFactory(spacesService, types)
);
- // Register a function with server to manage the collection of usage stats
- registerSpacesUsageCollector(usageCollectionSetup, {
- kibanaIndex: legacyAPI.legacyConfig.kibanaIndex,
- features: featuresSetup,
- licensing: licensingSetup,
- });
}
}
diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts
index 812b02e94f591b..7765cc3c52e966 100644
--- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts
+++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts
@@ -100,9 +100,6 @@ export const createLegacyAPI = ({
} as unknown) as jest.Mocked;
const legacyAPI: jest.Mocked = {
- legacyConfig: {
- kibanaIndex: '',
- },
auditLogger: {} as any,
savedObjects: savedObjectsService,
};
diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts
index 68d096e046ed4f..fc5ff397805244 100644
--- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts
+++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts
@@ -28,7 +28,6 @@ const mockLogger = loggingServiceMock.createLogger();
const createService = async (serverBasePath: string = '') => {
const legacyAPI = {
- legacyConfig: {},
savedObjects: ({
getSavedObjectsRepository: jest.fn().mockReturnValue({
get: jest.fn().mockImplementation((type, id) => {
diff --git a/x-pack/legacy/plugins/transform/public/app/services/documentation/index.ts b/x-pack/plugins/spaces/server/usage_collection/index.ts
similarity index 76%
rename from x-pack/legacy/plugins/transform/public/app/services/documentation/index.ts
rename to x-pack/plugins/spaces/server/usage_collection/index.ts
index 34209d10c6b8b4..01df2b815f5ff3 100644
--- a/x-pack/legacy/plugins/transform/public/app/services/documentation/index.ts
+++ b/x-pack/plugins/spaces/server/usage_collection/index.ts
@@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { documentationLinksService } from './documentation_links';
+export { registerSpacesUsageCollector } from './spaces_usage_collector';
diff --git a/x-pack/plugins/spaces/server/lib/spaces_usage_collector.test.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts
similarity index 85%
rename from x-pack/plugins/spaces/server/lib/spaces_usage_collector.test.ts
rename to x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts
index c0a6a152c8322b..57ec688ab70e83 100644
--- a/x-pack/plugins/spaces/server/lib/spaces_usage_collector.test.ts
+++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.test.ts
@@ -9,6 +9,7 @@ import * as Rx from 'rxjs';
import { PluginsSetup } from '../plugin';
import { Feature } from '../../../features/server';
import { ILicense, LicensingPluginSetup } from '../../../licensing/server';
+import { pluginInitializerContextConfigMock } from 'src/core/server/mocks';
interface SetupOpts {
license?: Partial;
@@ -72,7 +73,7 @@ describe('error handling', () => {
license: { isAvailable: true, type: 'basic' },
});
const { fetch: getSpacesUsage } = getSpacesUsageCollector(usageCollecion as any, {
- kibanaIndex: '.kibana',
+ kibanaIndexConfig$: Rx.of({ kibana: { index: '.kibana' } }),
features,
licensing,
});
@@ -85,7 +86,7 @@ describe('error handling', () => {
license: { isAvailable: true, type: 'basic' },
});
const { fetch: getSpacesUsage } = getSpacesUsageCollector(usageCollecion as any, {
- kibanaIndex: '.kibana',
+ kibanaIndexConfig$: Rx.of({ kibana: { index: '.kibana' } }),
features,
licensing,
});
@@ -105,11 +106,25 @@ describe('with a basic license', () => {
license: { isAvailable: true, type: 'basic' },
});
const { fetch: getSpacesUsage } = getSpacesUsageCollector(usageCollecion as any, {
- kibanaIndex: '.kibana',
+ kibanaIndexConfig$: pluginInitializerContextConfigMock({}).legacy.globalConfig$,
features,
licensing,
});
usageStats = await getSpacesUsage(defaultCallClusterMock);
+
+ expect(defaultCallClusterMock).toHaveBeenCalledWith('search', {
+ body: {
+ aggs: {
+ disabledFeatures: {
+ terms: { field: 'space.disabledFeatures', include: ['feature1', 'feature2'], size: 2 },
+ },
+ },
+ query: { term: { type: { value: 'space' } } },
+ size: 0,
+ track_total_hits: true,
+ },
+ index: '.kibana-tests',
+ });
});
test('sets enabled to true', () => {
@@ -139,7 +154,7 @@ describe('with no license', () => {
beforeAll(async () => {
const { features, licensing, usageCollecion } = setup({ license: { isAvailable: false } });
const { fetch: getSpacesUsage } = getSpacesUsageCollector(usageCollecion as any, {
- kibanaIndex: '.kibana',
+ kibanaIndexConfig$: pluginInitializerContextConfigMock({}).legacy.globalConfig$,
features,
licensing,
});
@@ -170,7 +185,7 @@ describe('with platinum license', () => {
license: { isAvailable: true, type: 'platinum' },
});
const { fetch: getSpacesUsage } = getSpacesUsageCollector(usageCollecion as any, {
- kibanaIndex: '.kibana',
+ kibanaIndexConfig$: pluginInitializerContextConfigMock({}).legacy.globalConfig$,
features,
licensing,
});
diff --git a/x-pack/plugins/spaces/server/lib/spaces_usage_collector.ts b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts
similarity index 90%
rename from x-pack/plugins/spaces/server/lib/spaces_usage_collector.ts
rename to x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts
index af77f2d3a72bac..90187b7853185a 100644
--- a/x-pack/plugins/spaces/server/lib/spaces_usage_collector.ts
+++ b/x-pack/plugins/spaces/server/usage_collection/spaces_usage_collector.ts
@@ -4,11 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { get } from 'lodash';
import { CallAPIOptions } from 'src/core/server';
import { take } from 'rxjs/operators';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
-// @ts-ignore
+import { Observable } from 'rxjs';
import { KIBANA_STATS_TYPE_MONITORING } from '../../../../legacy/plugins/monitoring/common/constants';
import { KIBANA_SPACES_STATS_TYPE } from '../../common/constants';
import { PluginsSetup } from '../plugin';
@@ -85,8 +84,8 @@ async function getSpacesUsage(
const { hits, aggregations } = resp!;
- const count = get(hits, 'total.value', 0);
- const disabledFeatureBuckets = get(aggregations, 'disabledFeatures.buckets', []);
+ const count = hits?.total?.value ?? 0;
+ const disabledFeatureBuckets = aggregations?.disabledFeatures?.buckets ?? [];
const initialCounts = knownFeatureIds.reduce(
(acc, featureId) => ({ ...acc, [featureId]: 0 }),
@@ -125,7 +124,7 @@ export interface UsageStats {
}
interface CollectorDeps {
- kibanaIndex: string;
+ kibanaIndexConfig$: Observable<{ kibana: { index: string } }>;
features: PluginsSetup['features'];
licensing: PluginsSetup['licensing'];
}
@@ -145,12 +144,9 @@ export function getSpacesUsageCollector(
const license = await deps.licensing.license$.pipe(take(1)).toPromise();
const available = license.isAvailable; // some form of spaces is available for all valid licenses
- const usageStats = await getSpacesUsage(
- callCluster,
- deps.kibanaIndex,
- deps.features,
- available
- );
+ const kibanaIndex = (await deps.kibanaIndexConfig$.pipe(take(1)).toPromise()).kibana.index;
+
+ const usageStats = await getSpacesUsage(callCluster, kibanaIndex, deps.features, available);
return {
available,
@@ -178,12 +174,9 @@ export function getSpacesUsageCollector(
}
export function registerSpacesUsageCollector(
- usageCollection: UsageCollectionSetup | undefined,
+ usageCollection: UsageCollectionSetup,
deps: CollectorDeps
) {
- if (!usageCollection) {
- return;
- }
const collector = getSpacesUsageCollector(usageCollection, deps);
usageCollection.registerCollector(collector);
}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx
index ba30b838a8593d..b478a9f0ced8b7 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx
@@ -16,8 +16,10 @@ import {
EuiTab,
EuiTabs,
EuiTitle,
+ EuiBetaBadge,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { BASE_PATH, Section, routeToConnectors, routeToAlerts } from './constants';
import { getCurrentBreadcrumb } from './lib/breadcrumb';
import { getCurrentDocTitle } from './lib/doc_title';
@@ -91,6 +93,17 @@ export const TriggersActionsUIHome: React.FunctionComponent
+
+
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx
index 0749ae1d30e9e2..1eabf2441da4f2 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx
@@ -17,6 +17,7 @@ import {
EuiButtonEmpty,
EuiButton,
EuiFlyoutBody,
+ EuiBetaBadge,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useActionsConnectorsContext } from '../../context/actions_connectors_context';
@@ -146,6 +147,17 @@ export const ConnectorAddFlyout = () => {
actionTypeName: actionType.name,
}}
/>
+
+
@@ -159,6 +171,17 @@ export const ConnectorAddFlyout = () => {
defaultMessage="Select a connector"
id="xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle"
/>
+
+
)}
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx
index 55386ec6d61f9e..64862927256603 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx
@@ -5,7 +5,7 @@
*/
import React, { useCallback, useReducer, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup } from '@elastic/eui';
+import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup, EuiBetaBadge } from '@elastic/eui';
import {
EuiModal,
EuiButton,
@@ -129,6 +129,17 @@ export const ConnectorAddModal = ({
actionTypeName: actionType.name,
}}
/>
+
+
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
index f7ad6f95d048f8..6fe555fd74b398 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx
@@ -16,6 +16,7 @@ import {
EuiFlyoutFooter,
EuiButtonEmpty,
EuiButton,
+ EuiBetaBadge,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useActionsConnectorsContext } from '../../context/actions_connectors_context';
@@ -96,6 +97,17 @@ export const ConnectorEditFlyout = ({ initialConnector }: ConnectorEditProps) =>
defaultMessage="Edit connector"
id="xpack.triggersActionsUI.sections.editConnectorForm.flyoutTitle"
/>
+
+
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
index bed285f668e01f..f48e27791419dc 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
@@ -14,6 +14,7 @@ import {
EuiEmptyPrompt,
EuiTitle,
EuiLink,
+ EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -370,8 +371,9 @@ export const ActionsConnectorsList: React.FunctionComponent = () => {
/>
{/* Render the view based on if there's data or if they can save */}
+ {(isLoadingActions || isLoadingActionTypes) && }
{data.length !== 0 && table}
- {data.length === 0 && canSave && emptyPrompt}
+ {data.length === 0 && canSave && !isLoadingActions && !isLoadingActionTypes && emptyPrompt}
{data.length === 0 && !canSave && noPermissionPrompt}
+
+
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx
index d2cf2decc4a160..2625768dc72421 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx
@@ -8,9 +8,17 @@ import uuid from 'uuid';
import { shallow } from 'enzyme';
import { AlertDetails } from './alert_details';
import { Alert, ActionType } from '../../../../types';
-import { EuiTitle, EuiBadge, EuiFlexItem, EuiButtonEmpty, EuiSwitch } from '@elastic/eui';
+import {
+ EuiTitle,
+ EuiBadge,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiSwitch,
+ EuiBetaBadge,
+} from '@elastic/eui';
import { times, random } from 'lodash';
import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
jest.mock('../../../app_context', () => ({
useAppDependencies: jest.fn(() => ({
@@ -54,7 +62,19 @@ describe('alert_details', () => {
).containsMatchingElement(
- {alert.name}
+
+ {alert.name}
+
+
+
)
).toBeTruthy();
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx
index 9c3b69962879f3..1952e35c229242 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx
@@ -21,8 +21,10 @@ import {
EuiSwitch,
EuiCallOut,
EuiSpacer,
+ EuiBetaBadge,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
+import { i18n } from '@kbn/i18n';
import { useAppDependencies } from '../../../app_context';
import { hasSaveAlertsCapability } from '../../../lib/capabilities';
import { Alert, AlertType, ActionType } from '../../../../types';
@@ -66,7 +68,20 @@ export const AlertDetails: React.FunctionComponent = ({
- {alert.name}
+
+ {alert.name}
+
+
+
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx
index d9ccb84452e47e..49e25dfbbf957c 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx
@@ -17,6 +17,7 @@ import {
EuiSpacer,
EuiEmptyPrompt,
EuiLink,
+ EuiLoadingSpinner,
} from '@elastic/eui';
import { useHistory } from 'react-router-dom';
@@ -389,7 +390,10 @@ export const AlertsList: React.FunctionComponent = () => {
{convertAlertsToTableItems(alertsState.data, alertTypesState.data).length !== 0 && table}
{convertAlertsToTableItems(alertsState.data, alertTypesState.data).length === 0 &&
+ !alertTypesState.isLoading &&
+ !alertsState.isLoading &&
emptyPrompt}
+ {(alertTypesState.isLoading || alertsState.isLoading) && }
{
+ // FLAKY: https://github.com/elastic/kibana/issues/58785
+ describe.skip('Privileges', () => {
describe('GET /api/security/privileges', () => {
it('should return a privilege map with all known privileges, without actions', async () => {
await supertest
diff --git a/x-pack/test/api_integration/apis/security/session.ts b/x-pack/test/api_integration/apis/security/session.ts
index d819dd38dddb1b..ef7e48388ff660 100644
--- a/x-pack/test/api_integration/apis/security/session.ts
+++ b/x-pack/test/api_integration/apis/security/session.ts
@@ -9,7 +9,7 @@ import expect from '@kbn/expect/expect.js';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function({ getService }: FtrProviderContext) {
- const supertest = getService('supertest');
+ const supertestWithoutAuth = getService('supertestWithoutAuth');
const config = getService('config');
const kibanaServerConfig = config.get('servers.kibana');
@@ -25,7 +25,7 @@ export default function({ getService }: FtrProviderContext) {
return response;
};
const getSessionInfo = async () =>
- supertest
+ supertestWithoutAuth
.get('/internal/security/session')
.set('kbn-xsrf', 'xxx')
.set('kbn-system-request', 'true')
@@ -33,7 +33,7 @@ export default function({ getService }: FtrProviderContext) {
.send()
.expect(200);
const extendSession = async () =>
- supertest
+ supertestWithoutAuth
.post('/internal/security/session')
.set('kbn-xsrf', 'xxx')
.set('Cookie', sessionCookie.cookieString())
@@ -42,7 +42,7 @@ export default function({ getService }: FtrProviderContext) {
.then(saveCookie);
beforeEach(async () => {
- await supertest
+ await supertestWithoutAuth
.post('/internal/security/login')
.set('kbn-xsrf', 'xxx')
.send({ username: validUsername, password: validPassword })
diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts
index a5bd4cc4802873..f6e93cd14e4971 100644
--- a/x-pack/test/functional/page_objects/uptime_page.ts
+++ b/x-pack/test/functional/page_objects/uptime_page.ts
@@ -57,7 +57,7 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo
}
public async pageUrlContains(value: string, expected: boolean = true) {
- retry.try(async () => {
+ await retry.try(async () => {
expect(await uptimeService.urlContains(value)).to.eql(expected);
});
}
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
index 307f39382a2363..f049406b639c79 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
@@ -23,7 +23,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await log.debug('Checking for section heading to say Triggers and Actions.');
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
- expect(headingText).to.be('Alerts and Actions');
+ expect(headingText).to.be('Alerts and Actions BETA');
});
describe('Connectors tab', () => {