diff --git a/.eslintrc.js b/.eslintrc.js index 367ac892107abf..8762c1dc02d2d4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -131,12 +131,6 @@ module.exports = { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['src/plugins/eui_utils/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, { files: ['src/plugins/kibana_react/**/*.{js,ts,tsx}'], rules: { diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index 0e902e3608e729..ec0863b09d653c 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -42,3 +42,22 @@ Finally, this problem can also occur if you've changed the index name that you w The default index pattern can be found {apm-server-ref}/elasticsearch-output.html#index-option-es[here]. If you change this setting, you must also configure the `setup.template.name` and `setup.template.pattern` options. See {apm-server-ref}/configuration-template.html[Load the Elasticsearch index template]. + +==== Unknown route + +The {apm-app-ref}/transactions.html[transaction overview] will only display helpful information +when the transactions in your services are named correctly. +If you're seeing "GET unknown route" or "unknown route" in the APM app, +it could be a sign that something isn't working like it should. + +Elastic APM Agents come with built-in support for popular frameworks out-of-the-box. +This means, among other things, that the Agent will try to automatically name HTTP requests. +As an example, the Node.js Agent uses the route that handled the request, while the Java Agent uses the Servlet name. + +"Unknown route" indicates that the Agent can't determine what to name the request, +perhaps because the technology you're using isn't supported, the Agent has been installed incorrectly, +or because something is happening to the request that the Agent doesn't understand. + +To resolve this, you'll need to head over to the relevant {apm-agents-ref}[Agent documentation]. +Specifically, view the Agent's supported technologies page. +You can also use the Agent's public API to manually set a name for the transaction. diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index fceabd1237665d..1506fdbb2b37f7 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -197,5 +197,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | +| [SharedGlobalConfig](./kibana-plugin-server.sharedglobalconfig.md) | | | [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | diff --git a/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md b/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md new file mode 100644 index 00000000000000..418d406d4c8905 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sharedglobalconfig.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SharedGlobalConfig](./kibana-plugin-server.sharedglobalconfig.md) + +## SharedGlobalConfig type + + +Signature: + +```typescript +export declare type SharedGlobalConfig = RecursiveReadonly<{ + kibana: Pick; + elasticsearch: Pick; + path: Pick; +}>; +``` diff --git a/docs/images/kibana-status-page-7_5_0.png b/docs/images/kibana-status-page-7_5_0.png new file mode 100644 index 00000000000000..2dac4c3f94c351 Binary files /dev/null and b/docs/images/kibana-status-page-7_5_0.png differ diff --git a/docs/images/kibana-status-page.png b/docs/images/kibana-status-page.png deleted file mode 100644 index b269dbd3573039..00000000000000 Binary files a/docs/images/kibana-status-page.png and /dev/null differ diff --git a/docs/maps/images/top_hits.png b/docs/maps/images/top_hits.png new file mode 100644 index 00000000000000..45bbf575f10dd5 Binary files /dev/null and b/docs/maps/images/top_hits.png differ diff --git a/docs/maps/indexing-geojson-data-tutorial.asciidoc b/docs/maps/indexing-geojson-data-tutorial.asciidoc index 5284bd9ac2ac55..22b736032cb796 100644 --- a/docs/maps/indexing-geojson-data-tutorial.asciidoc +++ b/docs/maps/indexing-geojson-data-tutorial.asciidoc @@ -101,11 +101,6 @@ aggregation. The remaining default settings are good, but there are a couple of settings that you might want to change. -. Under *Source settings* > *Grid resolution*, select from the different heat map resolutions. -+ -The default "Coarse" looks -good, but feel free to select a different resolution. - . Play around with the *Layer Style* > *Color range* setting. + diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index cd01acb2df7de5..98aa21f6a07a35 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -34,18 +34,23 @@ The point location is the weighted centroid for all geo-points in the gridded ce [role="xpack"] [[maps-top-hits-aggregation]] -=== Most recent entities +=== Top hits per entity -Most recent entities uses {es} {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation] to group your documents by entity. -Then, {ref}/search-aggregations-metrics-top-hits-aggregation.html[top hits metric aggregation] accumulates the most recent documents for each entry. +You can display the most relevant documents per entity, for example, the most recent GPS tracks per flight. +To get this data, {es} first groups your data using a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation], +then accumulates the most relevant documents based on sort order for each entry using a {ref}/search-aggregations-metrics-top-hits-aggregation.html[top hits metric aggregation]. -Most recent entities is available for <> with *Documents* source. -To enable most recent entities, click "Show most recent documents by entity" and configure the following: +Top hits per entity is available for <> with *Documents* source. +To enable top hits: +. In *Sorting*, select the *Show documents per entity* checkbox. . Set *Entity* to the field that identifies entities in your documents. This field will be used in the terms aggregation to group your documents into entity buckets. . Set *Documents per entity* to configure the maximum number of documents accumulated per entity. +[role="screenshot"] +image::maps/images/top_hits.png[] + [role="xpack"] [[point-to-point]] === Point to point diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 97b10e389963eb..8a93352798d2c8 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -89,7 +89,7 @@ The most common cause for empty layers are searches for a field that exists in o You can prevent the search bar from applying search context to a layer by configuring the following: -* In *Source settings*, clear the *Apply global filter to source* checkbox to turn off the global search context for the layer source. +* In *Filtering*, clear the *Apply global filter to layer data* checkbox to turn off the global search context for the layer source. * In *Term joins*, clear the *Apply global filter to join* checkbox to turn off the global search context for the <>. diff --git a/docs/setup/access.asciidoc b/docs/setup/access.asciidoc index 538b42781b127a..a7374a37ddaec4 100644 --- a/docs/setup/access.asciidoc +++ b/docs/setup/access.asciidoc @@ -2,8 +2,8 @@ == Accessing Kibana Kibana is a web application that you access through port 5601. All you need to do is point your web browser at the -machine where Kibana is running and specify the port number. For example, `localhost:5601` or -`http://YOURDOMAIN.com:5601`. +machine where Kibana is running and specify the port number. For example, `localhost:5601` or `http://YOURDOMAIN.com:5601`. +If you want to allow remote users to connect, set the parameter `server.host` in `kibana.yml` to a non-loopback address. When you access Kibana, the <> page loads by default with the default index pattern selected. The time filter is set to the last 15 minutes and the search query is set to match-all (\*). @@ -15,9 +15,10 @@ If you still don't see any results, it's possible that you don't *have* any docu [[status]] === Checking Kibana Status -You can reach the Kibana server's status page by navigating to `localhost:5601/status`. The status page displays +You can reach the Kibana server's status page by navigating to the status endpoint, for example, `localhost:5601/status`. The status page displays information about the server's resource usage and lists the installed plugins. -image::images/kibana-status-page.png[] +[role="screenshot"] +image::images/kibana-status-page-7_5_0.png[] NOTE: For JSON-formatted server status details, use the API endpoint at `localhost:5601/api/status` diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 5cda7b2b214f0b..414d4ef34db555 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -7,9 +7,7 @@ if you installed {kib} from an archive distribution (`.tar.gz` or `.zip`), by default it is in `$KIBANA_HOME/config`. By default, with package distributions (Debian or RPM), it is in `/etc/kibana`. -The default settings configure Kibana to run on `localhost:5601`. To change the -host or port number, or connect to Elasticsearch running on a different machine, -you'll need to update your `kibana.yml` file. You can also enable SSL and set a +The default host and port settings configure {kib} to run on `localhost:5601`. To change this behavior and allow remote users to connect, you'll need to update your `kibana.yml` file. You can also enable SSL and set a variety of other options. Finally, environment variables can be injected into configuration using `${MY_ENV_VAR}` syntax. @@ -32,7 +30,7 @@ strongly recommend that you keep the default CSP rules that ship with Kibana. `csp.strict:`:: *Default: `true`* Blocks access to Kibana to any browser that does not enforce even rudimentary CSP rules. In practice, this will disable -support for older, less safe browsers like Internet Explorer. +support for older, less safe browsers like Internet Explorer. See <> for more information. `csp.warnLegacyBrowsers:`:: *Default: `true`* Shows a warning message after @@ -65,7 +63,7 @@ connects to this Kibana instance. `elasticsearch.requestHeadersWhitelist:`:: *Default: `[ 'authorization' ]`* List of Kibana client-side headers to send to Elasticsearch. To send *no* client-side headers, set this value to [] (an empty list). -Removing the `authorization` header from being whitelisted means that you cannot +Removing the `authorization` header from being whitelisted means that you cannot use <> in Kibana. `elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait @@ -164,19 +162,19 @@ The following example shows a valid logging rotate configuration: enable log rotation. If you do not have a `logging.dest` set that is different from `stdout` that feature would not take any effect. -`logging.rotate.everyBytes:`:: [experimental] *Default: 10485760* The maximum size of a log file (that is `not an exact` limit). After the +`logging.rotate.everyBytes:`:: [experimental] *Default: 10485760* The maximum size of a log file (that is `not an exact` limit). After the limit is reached, a new log file is generated. The default size limit is 10485760 (10 MB) and this option should be at least greater than 1024. -`logging.rotate.keepFiles:`:: [experimental] *Default: 7* The number of most recent rotated log files to keep -on disk. Older files are deleted during log rotation. The default value is 7. The `logging.rotate.keepFiles` +`logging.rotate.keepFiles:`:: [experimental] *Default: 7* The number of most recent rotated log files to keep +on disk. Older files are deleted during log rotation. The default value is 7. The `logging.rotate.keepFiles` option has to be in the range of 2 to 1024 files. -`logging.rotate.pollingInterval:`:: [experimental] *Default: 10000* The number of milliseconds for the polling strategy in case +`logging.rotate.pollingInterval:`:: [experimental] *Default: 10000* The number of milliseconds for the polling strategy in case the `logging.rotate.usePolling` is enabled. That option has to be in the range of 5000 to 3600000 milliseconds. -`logging.rotate.usePolling:`:: [experimental] *Default: false* By default we try to understand the best way to monitoring -the log file. However, there is some systems where it could not be always accurate. In those cases, if needed, +`logging.rotate.usePolling:`:: [experimental] *Default: false* By default we try to understand the best way to monitoring +the log file. However, there is some systems where it could not be always accurate. In those cases, if needed, the `polling` method could be used enabling that option. `logging.silent:`:: *Default: false* Set the value of this setting to `true` to @@ -304,7 +302,7 @@ This setting may not be used when `server.compression.enabled` is set to `false` send on all responses to the client from the Kibana server. `server.host:`:: *Default: "localhost"* This setting specifies the host of the -back end server. +back end server. To allow remote users to connect, set the value to the IP address or DNS name of the {kib} server. `server.keepaliveTimeout:`:: *Default: "120000"* The number of milliseconds to wait for additional data before restarting the `server.socketTimeout` counter. @@ -358,15 +356,15 @@ supported protocols with versions. Valid protocols: `TLSv1`, `TLSv1.1`, `TLSv1.2 setting this to `true` enables unauthenticated users to access the Kibana server status API and status page. -`telemetry.allowChangingOptInStatus`:: *Default: true*. If `true`, -users are able to change the telemetry setting at a later time in -<>. If `false`, -{kib} looks at the value of `telemetry.optIn` to determine whether to send +`telemetry.allowChangingOptInStatus`:: *Default: true*. If `true`, +users are able to change the telemetry setting at a later time in +<>. If `false`, +{kib} looks at the value of `telemetry.optIn` to determine whether to send telemetry data or not. `telemetry.allowChangingOptInStatus` and `telemetry.optIn` cannot be `false` at the same time. -`telemetry.optIn`:: *Default: true* If `true`, telemetry data is sent to Elastic. - If `false`, collection of telemetry data is disabled. +`telemetry.optIn`:: *Default: true* If `true`, telemetry data is sent to Elastic. + If `false`, collection of telemetry data is disabled. To enable telemetry and prevent users from disabling it, set `telemetry.allowChangingOptInStatus` to `false` and `telemetry.optIn` to `true`. diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index 32f341a9c1b7cc..2e2aaf688e8b68 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -163,7 +163,7 @@ required by {kib}. If you want to use Third Party initiated SSO , then you must + [source,yaml] -------------------------------------------------------------------------------- -server.xsrf.whitelist: [/api/security/v1/oidc] +server.xsrf.whitelist: [/api/security/oidc/initiate_login] -------------------------------------------------------------------------------- [float] diff --git a/package.json b/package.json index f0d2743174c7f8..1c729d339deabd 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "resolutions": { "**/@types/node": "10.12.27", "**/@types/react": "^16.9.13", + "**/@types/react-router": "^5.1.3", "**/@types/hapi": "^17.0.18", "**/@types/angular": "^1.6.56", "**/typescript": "3.7.2", @@ -233,7 +234,7 @@ "react-monaco-editor": "~0.27.0", "react-redux": "^5.1.2", "react-resize-detector": "^4.2.0", - "react-router-dom": "^4.3.1", + "react-router-dom": "^5.1.2", "react-sizeme": "^2.3.6", "react-use": "^13.10.2", "reactcss": "1.2.3", @@ -347,7 +348,8 @@ "@types/react-dom": "^16.9.4", "@types/react-redux": "^6.0.6", "@types/react-resize-detector": "^4.0.1", - "@types/react-router-dom": "^4.3.1", + "@types/react-router": "^5.1.3", + "@types/react-router-dom": "^5.1.3", "@types/react-virtualized": "^9.18.7", "@types/redux": "^3.6.31", "@types/redux-actions": "^2.2.1", diff --git a/packages/kbn-analytics/package.json b/packages/kbn-analytics/package.json index f59fbf4720835e..9eefa16aaca017 100644 --- a/packages/kbn-analytics/package.json +++ b/packages/kbn-analytics/package.json @@ -14,7 +14,7 @@ "kbn:watch": "node scripts/build --source-maps --watch" }, "devDependencies": { - "@babel/cli": "7.5.5", + "@babel/cli": "^7.5.5", "@kbn/dev-utils": "1.0.0", "@kbn/babel-preset": "1.0.0", "typescript": "3.7.2" diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts index 36412961ce75b4..11b9450f2af6eb 100644 --- a/packages/kbn-test/src/functional_test_runner/cli.ts +++ b/packages/kbn-test/src/functional_test_runner/cli.ts @@ -57,6 +57,14 @@ export function runFtrCli() { } ); + if (flags.throttle) { + process.env.TEST_THROTTLE_NETWORK = '1'; + } + + if (flags.headless) { + process.env.TEST_BROWSER_HEADLESS = '1'; + } + let teardownRun = false; const teardown = async (err?: Error) => { if (teardownRun) return; @@ -97,7 +105,7 @@ export function runFtrCli() { { flags: { string: ['config', 'grep', 'exclude', 'include-tag', 'exclude-tag', 'kibana-install-dir'], - boolean: ['bail', 'invert', 'test-stats', 'updateBaselines'], + boolean: ['bail', 'invert', 'test-stats', 'updateBaselines', 'throttle', 'headless'], default: { config: 'test/functional/config.js', debug: true, @@ -113,6 +121,8 @@ export function runFtrCli() { --test-stats print the number of tests (included and excluded) to STDERR --updateBaselines replace baseline screenshots with whatever is generated from the test --kibana-install-dir directory where the Kibana install being tested resides + --throttle enable network throttling in Chrome browser + --headless run browser in headless mode `, }, } diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js index 97b74a3b2b5412..9f9a8f59fde9ad 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js @@ -182,6 +182,22 @@ describe('run tests CLI', () => { expect(exitMock).not.toHaveBeenCalled(); }); + it('accepts network throttle option', async () => { + global.process.argv.push('--throttle'); + + await runTestsCli(['foo']); + + expect(exitMock).toHaveBeenCalledWith(1); + }); + + it('accepts headless option', async () => { + global.process.argv.push('--headless'); + + await runTestsCli(['foo']); + + expect(exitMock).toHaveBeenCalledWith(1); + }); + it('accepts extra server options', async () => { global.process.argv.push('--', '--server.foo=bar'); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 57156322e2849b..51444a76f17373 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -143,6 +143,7 @@ export { PluginInitializerContext, PluginManifest, PluginName, + SharedGlobalConfig, } from './plugins'; export { diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index b4c8c988642635..36205cb7f047b0 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -206,6 +206,9 @@ export const SharedGlobalConfigKeys = { path: ['data'] as const, }; +/** + * @public + */ export type SharedGlobalConfig = RecursiveReadonly<{ kibana: Pick; elasticsearch: Pick; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index c855e04e420f75..96c24dcd7c8b36 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1,1790 +1,1800 @@ -## API Report File for "kibana" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import Boom from 'boom'; -import { BulkIndexDocumentsParams } from 'elasticsearch'; -import { CatAliasesParams } from 'elasticsearch'; -import { CatAllocationParams } from 'elasticsearch'; -import { CatCommonParams } from 'elasticsearch'; -import { CatFielddataParams } from 'elasticsearch'; -import { CatHealthParams } from 'elasticsearch'; -import { CatHelpParams } from 'elasticsearch'; -import { CatIndicesParams } from 'elasticsearch'; -import { CatRecoveryParams } from 'elasticsearch'; -import { CatSegmentsParams } from 'elasticsearch'; -import { CatShardsParams } from 'elasticsearch'; -import { CatSnapshotsParams } from 'elasticsearch'; -import { CatTasksParams } from 'elasticsearch'; -import { CatThreadPoolParams } from 'elasticsearch'; -import { ClearScrollParams } from 'elasticsearch'; -import { Client } from 'elasticsearch'; -import { ClusterAllocationExplainParams } from 'elasticsearch'; -import { ClusterGetSettingsParams } from 'elasticsearch'; -import { ClusterHealthParams } from 'elasticsearch'; -import { ClusterPendingTasksParams } from 'elasticsearch'; -import { ClusterPutSettingsParams } from 'elasticsearch'; -import { ClusterRerouteParams } from 'elasticsearch'; -import { ClusterStateParams } from 'elasticsearch'; -import { ClusterStatsParams } from 'elasticsearch'; -import { ConfigOptions } from 'elasticsearch'; -import { CountParams } from 'elasticsearch'; -import { CreateDocumentParams } from 'elasticsearch'; -import { DeleteDocumentByQueryParams } from 'elasticsearch'; -import { DeleteDocumentParams } from 'elasticsearch'; -import { DeleteScriptParams } from 'elasticsearch'; -import { DeleteTemplateParams } from 'elasticsearch'; -import { DetailedPeerCertificate } from 'tls'; -import { Duration } from 'moment'; -import { ExistsParams } from 'elasticsearch'; -import { ExplainParams } from 'elasticsearch'; -import { FieldStatsParams } from 'elasticsearch'; -import { GenericParams } from 'elasticsearch'; -import { GetParams } from 'elasticsearch'; -import { GetResponse } from 'elasticsearch'; -import { GetScriptParams } from 'elasticsearch'; -import { GetSourceParams } from 'elasticsearch'; -import { GetTemplateParams } from 'elasticsearch'; -import { IncomingHttpHeaders } from 'http'; -import { IndexDocumentParams } from 'elasticsearch'; -import { IndicesAnalyzeParams } from 'elasticsearch'; -import { IndicesClearCacheParams } from 'elasticsearch'; -import { IndicesCloseParams } from 'elasticsearch'; -import { IndicesCreateParams } from 'elasticsearch'; -import { IndicesDeleteAliasParams } from 'elasticsearch'; -import { IndicesDeleteParams } from 'elasticsearch'; -import { IndicesDeleteTemplateParams } from 'elasticsearch'; -import { IndicesExistsAliasParams } from 'elasticsearch'; -import { IndicesExistsParams } from 'elasticsearch'; -import { IndicesExistsTemplateParams } from 'elasticsearch'; -import { IndicesExistsTypeParams } from 'elasticsearch'; -import { IndicesFlushParams } from 'elasticsearch'; -import { IndicesFlushSyncedParams } from 'elasticsearch'; -import { IndicesForcemergeParams } from 'elasticsearch'; -import { IndicesGetAliasParams } from 'elasticsearch'; -import { IndicesGetFieldMappingParams } from 'elasticsearch'; -import { IndicesGetMappingParams } from 'elasticsearch'; -import { IndicesGetParams } from 'elasticsearch'; -import { IndicesGetSettingsParams } from 'elasticsearch'; -import { IndicesGetTemplateParams } from 'elasticsearch'; -import { IndicesGetUpgradeParams } from 'elasticsearch'; -import { IndicesOpenParams } from 'elasticsearch'; -import { IndicesPutAliasParams } from 'elasticsearch'; -import { IndicesPutMappingParams } from 'elasticsearch'; -import { IndicesPutSettingsParams } from 'elasticsearch'; -import { IndicesPutTemplateParams } from 'elasticsearch'; -import { IndicesRecoveryParams } from 'elasticsearch'; -import { IndicesRefreshParams } from 'elasticsearch'; -import { IndicesRolloverParams } from 'elasticsearch'; -import { IndicesSegmentsParams } from 'elasticsearch'; -import { IndicesShardStoresParams } from 'elasticsearch'; -import { IndicesShrinkParams } from 'elasticsearch'; -import { IndicesStatsParams } from 'elasticsearch'; -import { IndicesUpdateAliasesParams } from 'elasticsearch'; -import { IndicesUpgradeParams } from 'elasticsearch'; -import { IndicesValidateQueryParams } from 'elasticsearch'; -import { InfoParams } from 'elasticsearch'; -import { IngestDeletePipelineParams } from 'elasticsearch'; -import { IngestGetPipelineParams } from 'elasticsearch'; -import { IngestPutPipelineParams } from 'elasticsearch'; -import { IngestSimulateParams } from 'elasticsearch'; -import { KibanaConfigType } from 'src/core/server/kibana_config'; -import { Logger as Logger_2 } from 'src/core/server/logging'; -import { MGetParams } from 'elasticsearch'; -import { MGetResponse } from 'elasticsearch'; -import { MSearchParams } from 'elasticsearch'; -import { MSearchResponse } from 'elasticsearch'; -import { MSearchTemplateParams } from 'elasticsearch'; -import { MTermVectorsParams } from 'elasticsearch'; -import { NodesHotThreadsParams } from 'elasticsearch'; -import { NodesInfoParams } from 'elasticsearch'; -import { NodesStatsParams } from 'elasticsearch'; -import { ObjectType } from '@kbn/config-schema'; -import { Observable } from 'rxjs'; -import { PeerCertificate } from 'tls'; -import { PingParams } from 'elasticsearch'; -import { PutScriptParams } from 'elasticsearch'; -import { PutTemplateParams } from 'elasticsearch'; -import { Readable } from 'stream'; -import { RecursiveReadonly as RecursiveReadonly_2 } from 'kibana/public'; -import { ReindexParams } from 'elasticsearch'; -import { ReindexRethrottleParams } from 'elasticsearch'; -import { RenderSearchTemplateParams } from 'elasticsearch'; -import { Request } from 'hapi'; -import { ResponseObject } from 'hapi'; -import { ResponseToolkit } from 'hapi'; -import { ScrollParams } from 'elasticsearch'; -import { SearchParams } from 'elasticsearch'; -import { SearchResponse } from 'elasticsearch'; -import { SearchShardsParams } from 'elasticsearch'; -import { SearchTemplateParams } from 'elasticsearch'; -import { Server } from 'hapi'; -import { ShallowPromise } from '@kbn/utility-types'; -import { SnapshotCreateParams } from 'elasticsearch'; -import { SnapshotCreateRepositoryParams } from 'elasticsearch'; -import { SnapshotDeleteParams } from 'elasticsearch'; -import { SnapshotDeleteRepositoryParams } from 'elasticsearch'; -import { SnapshotGetParams } from 'elasticsearch'; -import { SnapshotGetRepositoryParams } from 'elasticsearch'; -import { SnapshotRestoreParams } from 'elasticsearch'; -import { SnapshotStatusParams } from 'elasticsearch'; -import { SnapshotVerifyRepositoryParams } from 'elasticsearch'; -import { Stream } from 'stream'; -import { SuggestParams } from 'elasticsearch'; -import { TasksCancelParams } from 'elasticsearch'; -import { TasksGetParams } from 'elasticsearch'; -import { TasksListParams } from 'elasticsearch'; -import { TermvectorsParams } from 'elasticsearch'; -import { Type } from '@kbn/config-schema'; -import { TypeOf } from '@kbn/config-schema'; -import { UpdateDocumentByQueryParams } from 'elasticsearch'; -import { UpdateDocumentParams } from 'elasticsearch'; -import { Url } from 'url'; - -// @public (undocumented) -export interface APICaller { - // (undocumented) - (endpoint: 'cluster.state', params: ClusterStateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'count', params: CountParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'create', params: CreateDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'delete', params: DeleteDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteScript', params: DeleteScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'exists', params: ExistsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'explain', params: ExplainParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'fieldStats', params: FieldStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'get', params: GetParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'getScript', params: GetScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'getSource', params: GetSourceParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'getTemplate', params: GetTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'index', params: IndexDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'info', params: InfoParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'mget', params: MGetParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'msearch', params: MSearchParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ping', params: PingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'putScript', params: PutScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'putTemplate', params: PutTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'reindex', params: ReindexParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'scroll', params: ScrollParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'search', params: SearchParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'searchShards', params: SearchShardsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'suggest', params: SuggestParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'termvectors', params: TermvectorsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'update', params: UpdateDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.aliases', params: CatAliasesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.allocation', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.count', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.health', params: CatHealthParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.help', params: CatHelpParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.indices', params: CatIndicesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.master', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.nodes', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.plugins', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.repositories', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.segments', params: CatSegmentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.shards', params: CatShardsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.tasks', params: CatTasksParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.health', params: ClusterHealthParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'clearScroll', params: ClearScrollParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.close', params: IndicesCloseParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.create', params: IndicesCreateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.exists', params: IndicesExistsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.flush', params: IndicesFlushParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.get', params: IndicesGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.open', params: IndicesOpenParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.stats', params: IndicesStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.info', params: NodesInfoParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.stats', params: NodesStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.get', params: TasksGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.list', params: TasksListParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: CallAPIOptions): Promise; - // (undocumented) - (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: CallAPIOptions): Promise; - // (undocumented) - (endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; -} - -// @public (undocumented) -export interface AssistanceAPIResponse { - // (undocumented) - indices: { - [indexName: string]: { - action_required: MIGRATION_ASSISTANCE_INDEX_ACTION; - }; - }; -} - -// @public (undocumented) -export interface AssistantAPIClientParams extends GenericParams { - // (undocumented) - method: 'GET'; - // (undocumented) - path: '/_migration/assistance'; -} - -// @public (undocumented) -export interface Authenticated extends AuthResultParams { - // (undocumented) - type: AuthResultType.authenticated; -} - -// @public -export type AuthenticationHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: AuthToolkit) => AuthResult | IKibanaResponse | Promise; - -// @public -export type AuthHeaders = Record; - -// @public (undocumented) -export type AuthResult = Authenticated; - -// @public -export interface AuthResultParams { - requestHeaders?: AuthHeaders; - responseHeaders?: AuthHeaders; - state?: Record; -} - -// @public (undocumented) -export enum AuthResultType { - // (undocumented) - authenticated = "authenticated" -} - -// @public -export enum AuthStatus { - authenticated = "authenticated", - unauthenticated = "unauthenticated", - unknown = "unknown" -} - -// @public -export interface AuthToolkit { - authenticated: (data?: AuthResultParams) => AuthResult; -} - -// @public -export class BasePath { - // @internal - constructor(serverBasePath?: string); - get: (request: KibanaRequest | LegacyRequest) => string; - prepend: (path: string) => string; - remove: (path: string) => string; - readonly serverBasePath: string; - set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; -} - -// Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts -// -// @internal (undocumented) -export function bootstrap({ configs, cliArgs, applyConfigOverrides, features, }: BootstrapArgs): Promise; - -// @public -export interface CallAPIOptions { - signal?: AbortSignal; - wrap401Errors?: boolean; -} - -// @public -export interface Capabilities { - [key: string]: Record>; - catalogue: Record; - management: { - [sectionId: string]: Record; - }; - navLinks: Record; -} - -// @public -export type CapabilitiesProvider = () => Partial; - -// @public -export interface CapabilitiesSetup { - registerProvider(provider: CapabilitiesProvider): void; - registerSwitcher(switcher: CapabilitiesSwitcher): void; -} - -// @public -export interface CapabilitiesStart { - resolveCapabilities(request: KibanaRequest): Promise; -} - -// @public -export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capabilities) => Partial | Promise>; - -// @public -export class ClusterClient implements IClusterClient { - constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); - asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient; - callAsInternalUser: APICaller; - close(): void; - } - -// @public (undocumented) -export type ConfigPath = string | string[]; - -// @internal (undocumented) -export class ConfigService { - // Warning: (ae-forgotten-export) The symbol "Config" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "Env" needs to be exported by the entry point index.d.ts - constructor(config$: Observable, env: Env, logger: LoggerFactory); - atPath(path: ConfigPath): Observable; - getConfig$(): Observable; - // (undocumented) - getUnusedPaths(): Promise; - // (undocumented) - getUsedPaths(): Promise; - // (undocumented) - isEnabledAtPath(path: ConfigPath): Promise; - optionalAtPath(path: ConfigPath): Observable; - setSchema(path: ConfigPath, schema: Type): Promise; - } - -// @public -export interface ContextSetup { - createContextContainer>(): IContextContainer; -} - -// @internal (undocumented) -export type CoreId = symbol; - -// @public -export interface CoreSetup { - // (undocumented) - capabilities: CapabilitiesSetup; - // (undocumented) - context: ContextSetup; - // (undocumented) - elasticsearch: ElasticsearchServiceSetup; - // (undocumented) - http: HttpServiceSetup; - // (undocumented) - savedObjects: SavedObjectsServiceSetup; - // (undocumented) - uiSettings: UiSettingsServiceSetup; -} - -// @public -export interface CoreStart { - // (undocumented) - capabilities: CapabilitiesStart; - // (undocumented) - savedObjects: SavedObjectsServiceStart; -} - -// @public -export interface CustomHttpResponseOptions { - body?: T; - headers?: ResponseHeaders; - // (undocumented) - statusCode: number; -} - -// @public (undocumented) -export interface DeprecationAPIClientParams extends GenericParams { - // (undocumented) - method: 'GET'; - // (undocumented) - path: '/_migration/deprecations'; -} - -// @public (undocumented) -export interface DeprecationAPIResponse { - // (undocumented) - cluster_settings: DeprecationInfo[]; - // (undocumented) - index_settings: IndexSettingsDeprecationInfo; - // (undocumented) - ml_settings: DeprecationInfo[]; - // (undocumented) - node_settings: DeprecationInfo[]; -} - -// @public (undocumented) -export interface DeprecationInfo { - // (undocumented) - details?: string; - // (undocumented) - level: MIGRATION_DEPRECATION_LEVEL; - // (undocumented) - message: string; - // (undocumented) - url: string; -} - -// @public -export interface DiscoveredPlugin { - readonly configPath: ConfigPath; - readonly id: PluginName; - readonly optionalPlugins: readonly PluginName[]; - readonly requiredPlugins: readonly PluginName[]; -} - -// Warning: (ae-forgotten-export) The symbol "ElasticsearchConfig" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type ElasticsearchClientConfig = Pick & Pick & { - pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; - requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; - sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; - ssl?: Partial; -}; - -// @public (undocumented) -export interface ElasticsearchError extends Boom { - // (undocumented) - [code]?: string; -} - -// @public -export class ElasticsearchErrorHelpers { - // (undocumented) - static decorateNotAuthorizedError(error: Error, reason?: string): ElasticsearchError; - // (undocumented) - static isNotAuthorizedError(error: any): error is ElasticsearchError; -} - -// @public (undocumented) -export interface ElasticsearchServiceSetup { - readonly adminClient$: Observable; - readonly createClient: (type: string, clientConfig?: Partial) => IClusterClient; - readonly dataClient$: Observable; -} - -// @public (undocumented) -export interface EnvironmentMode { - // (undocumented) - dev: boolean; - // (undocumented) - name: 'development' | 'production'; - // (undocumented) - prod: boolean; -} - -// @public -export interface ErrorHttpResponseOptions { - body?: ResponseError; - headers?: ResponseHeaders; -} - -// @public -export interface FakeRequest { - headers: Headers; -} - -// @public -export type GetAuthHeaders = (request: KibanaRequest | LegacyRequest) => AuthHeaders | undefined; - -// @public -export type GetAuthState = (request: KibanaRequest | LegacyRequest) => { - status: AuthStatus; - state: unknown; -}; - -// @public -export type HandlerContextType> = T extends HandlerFunction ? U : never; - -// @public -export type HandlerFunction = (context: T, ...args: any[]) => any; - -// @public -export type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; - -// @public -export type Headers = { - [header in KnownHeaders]?: string | string[] | undefined; -} & { - [header: string]: string | string[] | undefined; -}; - -// @public -export interface HttpResponseOptions { - body?: HttpResponsePayload; - headers?: ResponseHeaders; -} - -// @public -export type HttpResponsePayload = undefined | string | Record | Buffer | Stream; - -// @public -export interface HttpServiceSetup { - basePath: IBasePath; - createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; - createRouter: () => IRouter; - isTlsEnabled: boolean; - registerAuth: (handler: AuthenticationHandler) => void; - registerOnPostAuth: (handler: OnPostAuthHandler) => void; - registerOnPreAuth: (handler: OnPreAuthHandler) => void; - registerOnPreResponse: (handler: OnPreResponseHandler) => void; - registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; -} - -// @public (undocumented) -export interface HttpServiceStart { - isListening: (port: number) => boolean; -} - -// @public -export type IBasePath = Pick; - -// @public -export type IClusterClient = Pick; - -// @public -export interface IContextContainer> { - createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; - registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; -} - -// @public -export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; - -// @public -export interface IKibanaResponse { - // (undocumented) - readonly options: HttpResponseOptions; - // (undocumented) - readonly payload?: T; - // (undocumented) - readonly status: number; -} - -// @public -export interface IKibanaSocket { - readonly authorizationError?: Error; - readonly authorized?: boolean; - // (undocumented) - getPeerCertificate(detailed: true): DetailedPeerCertificate | null; - // (undocumented) - getPeerCertificate(detailed: false): PeerCertificate | null; - getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate | null; -} - -// @public (undocumented) -export interface IndexSettingsDeprecationInfo { - // (undocumented) - [indexName: string]: DeprecationInfo[]; -} - -// @public -export interface IRouter { - delete: RouteRegistrar<'delete'>; - get: RouteRegistrar<'get'>; - // Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts - // - // @internal - getRoutes: () => RouterRoute[]; - handleLegacyErrors:

(handler: RequestHandler) => RequestHandler; - patch: RouteRegistrar<'patch'>; - post: RouteRegistrar<'post'>; - put: RouteRegistrar<'put'>; - routerPath: string; -} - -// @public -export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; - -// @public -export type ISavedObjectsRepository = Pick; - -// @public -export type IScopedClusterClient = Pick; - -// @public -export interface IUiSettingsClient { - get: (key: string) => Promise; - getAll: () => Promise>; - getRegistered: () => Readonly>; - getUserProvided: () => Promise>>; - isOverridden: (key: string) => boolean; - remove: (key: string) => Promise; - removeMany: (keys: string[]) => Promise; - set: (key: string, value: any) => Promise; - setMany: (changes: Record) => Promise; -} - -// @public -export class KibanaRequest { - // @internal (undocumented) - protected readonly [requestSymbol]: Request; - constructor(request: Request, params: Params, query: Query, body: Body, withoutSecretHeaders: boolean); - // (undocumented) - readonly body: Body; - // @internal - static from

| Type>(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders?: boolean): KibanaRequest; - readonly headers: Headers; - // (undocumented) - readonly params: Params; - // (undocumented) - readonly query: Query; - readonly route: RecursiveReadonly>; - // (undocumented) - readonly socket: IKibanaSocket; - readonly url: Url; - } - -// @public -export interface KibanaRequestRoute { - // (undocumented) - method: Method; - // (undocumented) - options: KibanaRequestRouteOptions; - // (undocumented) - path: string; -} - -// @public -export type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; - -// @public -export type KibanaResponseFactory = typeof kibanaResponseFactory; - -// @public -export const kibanaResponseFactory: { - custom: | Buffer | Stream | { - message: string | Error; - attributes?: Record | undefined; - } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; - badRequest: (options?: ErrorHttpResponseOptions) => KibanaResponse; - unauthorized: (options?: ErrorHttpResponseOptions) => KibanaResponse; - forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; - notFound: (options?: ErrorHttpResponseOptions) => KibanaResponse; - conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; - internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; - customError: (options: CustomHttpResponseOptions) => KibanaResponse; - redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; - ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; - accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; - noContent: (options?: HttpResponseOptions) => KibanaResponse; -}; - -// Warning: (ae-forgotten-export) The symbol "KnownKeys" needs to be exported by the entry point index.d.ts -// -// @public -export type KnownHeaders = KnownKeys; - -// @public @deprecated (undocumented) -export interface LegacyRequest extends Request { -} - -// @public @deprecated (undocumented) -export interface LegacyServiceSetupDeps { - // Warning: (ae-forgotten-export) The symbol "InternalCoreSetup" needs to be exported by the entry point index.d.ts - // - // (undocumented) - core: InternalCoreSetup & { - plugins: PluginsServiceSetup; - }; - // (undocumented) - plugins: Record; -} - -// @public @deprecated (undocumented) -export interface LegacyServiceStartDeps { - // Warning: (ae-forgotten-export) The symbol "InternalCoreStart" needs to be exported by the entry point index.d.ts - // - // (undocumented) - core: InternalCoreStart & { - plugins: PluginsServiceStart; - }; - // (undocumented) - plugins: Record; -} - -// Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts -// -// @public -export type LifecycleResponseFactory = typeof lifecycleResponseFactory; - -// @public -export interface Logger { - debug(message: string, meta?: LogMeta): void; - error(errorOrMessage: string | Error, meta?: LogMeta): void; - fatal(errorOrMessage: string | Error, meta?: LogMeta): void; - info(message: string, meta?: LogMeta): void; - // @internal (undocumented) - log(record: LogRecord): void; - trace(message: string, meta?: LogMeta): void; - warn(errorOrMessage: string | Error, meta?: LogMeta): void; -} - -// @public -export interface LoggerFactory { - get(...contextParts: string[]): Logger; -} - -// @internal -export class LogLevel { - // (undocumented) - static readonly All: LogLevel; - // (undocumented) - static readonly Debug: LogLevel; - // (undocumented) - static readonly Error: LogLevel; - // (undocumented) - static readonly Fatal: LogLevel; - static fromId(level: LogLevelId): LogLevel; - // Warning: (ae-forgotten-export) The symbol "LogLevelId" needs to be exported by the entry point index.d.ts - // - // (undocumented) - readonly id: LogLevelId; - // (undocumented) - static readonly Info: LogLevel; - // (undocumented) - static readonly Off: LogLevel; - supports(level: LogLevel): boolean; - // (undocumented) - static readonly Trace: LogLevel; - // (undocumented) - readonly value: number; - // (undocumented) - static readonly Warn: LogLevel; -} - -// @public -export interface LogMeta { - // (undocumented) - [key: string]: any; -} - -// @internal -export interface LogRecord { - // (undocumented) - context: string; - // (undocumented) - error?: Error; - // (undocumented) - level: LogLevel; - // (undocumented) - message: string; - // (undocumented) - meta?: { - [name: string]: any; - }; - // (undocumented) - timestamp: Date; -} - -// @public (undocumented) -export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; - -// @public (undocumented) -export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; - -// @public -export type MutatingOperationRefreshSetting = boolean | 'wait_for'; - -// Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts -// -// @public -export type OnPostAuthHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPostAuthToolkit) => OnPostAuthResult | KibanaResponse | Promise; - -// @public -export interface OnPostAuthToolkit { - next: () => OnPostAuthResult; -} - -// Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts -// -// @public -export type OnPreAuthHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPreAuthToolkit) => OnPreAuthResult | KibanaResponse | Promise; - -// @public -export interface OnPreAuthToolkit { - next: () => OnPreAuthResult; - rewriteUrl: (url: string) => OnPreAuthResult; -} - -// @public -export interface OnPreResponseExtensions { - headers?: ResponseHeaders; -} - -// Warning: (ae-forgotten-export) The symbol "OnPreResponseResult" needs to be exported by the entry point index.d.ts -// -// @public -export type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; - -// @public -export interface OnPreResponseInfo { - // (undocumented) - statusCode: number; -} - -// @public -export interface OnPreResponseToolkit { - next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; -} - -// @public (undocumented) -export interface PackageInfo { - // (undocumented) - branch: string; - // (undocumented) - buildNum: number; - // (undocumented) - buildSha: string; - // (undocumented) - dist: boolean; - // (undocumented) - version: string; -} - -// @public -export interface Plugin { - // (undocumented) - setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; - // (undocumented) - start(core: CoreStart, plugins: TPluginsStart): TStart | Promise; - // (undocumented) - stop?(): void; -} - -// @public -export interface PluginConfigDescriptor { - exposeToBrowser?: { - [P in keyof T]?: boolean; - }; - schema: PluginConfigSchema; -} - -// @public -export type PluginConfigSchema = Type; - -// @public -export type PluginInitializer = (core: PluginInitializerContext) => Plugin; - -// @public -export interface PluginInitializerContext { - // (undocumented) - config: { - legacy: { - globalConfig$: Observable; - }; - create: () => Observable; - createIfExists: () => Observable; - }; - // (undocumented) - env: { - mode: EnvironmentMode; - packageInfo: Readonly; - }; - // (undocumented) - logger: LoggerFactory; - // (undocumented) - opaqueId: PluginOpaqueId; -} - -// @public -export interface PluginManifest { - readonly configPath: ConfigPath; - readonly id: PluginName; - readonly kibanaVersion: string; - readonly optionalPlugins: readonly PluginName[]; - readonly requiredPlugins: readonly PluginName[]; - readonly server: boolean; - readonly ui: boolean; - readonly version: string; -} - -// @public -export type PluginName = string; - -// @public (undocumented) -export type PluginOpaqueId = symbol; - -// @public (undocumented) -export interface PluginsServiceSetup { - // (undocumented) - contracts: Map; - // (undocumented) - uiPlugins: { - internal: Map; - public: Map; - browserConfigs: Map>; - }; -} - -// @public (undocumented) -export interface PluginsServiceStart { - // (undocumented) - contracts: Map; -} - -// Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ - [K in keyof T]: RecursiveReadonly; -}> : T; - -// @public -export type RedirectResponseOptions = HttpResponseOptions & { - headers: { - location: string; - }; -}; - -// @public -export type RequestHandler

| Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; - -// @public -export interface RequestHandlerContext { - // (undocumented) - core: { - savedObjects: { - client: SavedObjectsClientContract; - }; - elasticsearch: { - dataClient: IScopedClusterClient; - adminClient: IScopedClusterClient; - }; - uiSettings: { - client: IUiSettingsClient; - }; - }; -} - -// @public -export type RequestHandlerContextContainer = IContextContainer>; - -// @public -export type RequestHandlerContextProvider = IContextProvider, TContextName>; - -// @public -export type ResponseError = string | Error | { - message: string | Error; - attributes?: ResponseErrorAttributes; -}; - -// @public -export type ResponseErrorAttributes = Record; - -// @public -export type ResponseHeaders = { - [header in KnownHeaders]?: string | string[]; -} & { - [header: string]: string | string[]; -}; - -// @public -export interface RouteConfig

| Type, Method extends RouteMethod> { - options?: RouteConfigOptions; - path: string; - validate: RouteSchemas | false; -} - -// @public -export interface RouteConfigOptions { - authRequired?: boolean; - body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; - tags?: readonly string[]; -} - -// @public -export interface RouteConfigOptionsBody { - accepts?: RouteContentType | RouteContentType[] | string | string[]; - maxBytes?: number; - output?: typeof validBodyOutput[number]; - parse?: boolean | 'gunzip'; -} - -// @public -export type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; - -// @public -export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; - -// @public -export type RouteRegistrar =

| Type>(route: RouteConfig, handler: RequestHandler) => void; - -// @public -export interface RouteSchemas

| Type> { - // (undocumented) - body?: B; - // (undocumented) - params?: P; - // (undocumented) - query?: Q; -} - -// @public (undocumented) -export interface SavedObject { - attributes: T; - // (undocumented) - error?: { - message: string; - statusCode: number; - }; - id: string; - migrationVersion?: SavedObjectsMigrationVersion; - references: SavedObjectReference[]; - type: string; - updated_at?: string; - version?: string; -} - -// @public -export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; - -// @public -export interface SavedObjectAttributes { - // (undocumented) - [key: string]: SavedObjectAttribute; -} - -// @public -export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; - -// @public -export interface SavedObjectReference { - // (undocumented) - id: string; - // (undocumented) - name: string; - // (undocumented) - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBaseOptions { - namespace?: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkCreateObject { - // (undocumented) - attributes: T; - // (undocumented) - id?: string; - migrationVersion?: SavedObjectsMigrationVersion; - // (undocumented) - references?: SavedObjectReference[]; - // (undocumented) - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkGetObject { - fields?: string[]; - // (undocumented) - id: string; - // (undocumented) - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkResponse { - // (undocumented) - saved_objects: Array>; -} - -// @public (undocumented) -export interface SavedObjectsBulkResponse { - // (undocumented) - saved_objects: Array>; -} - -// @public (undocumented) -export interface SavedObjectsBulkUpdateObject extends Pick { - attributes: Partial; - id: string; - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions { - refresh?: MutatingOperationRefreshSetting; -} - -// @public (undocumented) -export interface SavedObjectsBulkUpdateResponse { - // (undocumented) - saved_objects: Array>; -} - -// @public (undocumented) -export class SavedObjectsClient { - // @internal - constructor(repository: ISavedObjectsRepository); - bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; - bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; - bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; - create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; - delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; - // (undocumented) - errors: typeof SavedObjectsErrorHelpers; - // (undocumented) - static errors: typeof SavedObjectsErrorHelpers; - find(options: SavedObjectsFindOptions): Promise>; - get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; - update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; -} - -// @public -export type SavedObjectsClientContract = Pick; - -// @public -export type SavedObjectsClientFactory = ({ request, }: { - request: Request; -}) => SavedObjectsClientContract; - -// @public -export interface SavedObjectsClientProviderOptions { - // (undocumented) - excludedWrappers?: string[]; -} - -// @public -export type SavedObjectsClientWrapperFactory = (options: SavedObjectsClientWrapperOptions) => SavedObjectsClientContract; - -// @public -export interface SavedObjectsClientWrapperOptions { - // (undocumented) - client: SavedObjectsClientContract; - // (undocumented) - request: Request; -} - -// @public (undocumented) -export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { - id?: string; - migrationVersion?: SavedObjectsMigrationVersion; - overwrite?: boolean; - // (undocumented) - references?: SavedObjectReference[]; - refresh?: MutatingOperationRefreshSetting; -} - -// @public (undocumented) -export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { - refresh?: MutatingOperationRefreshSetting; -} - -// @public (undocumented) -export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { - refresh?: MutatingOperationRefreshSetting; -} - -// @public (undocumented) -export class SavedObjectsErrorHelpers { - // (undocumented) - static createBadRequestError(reason?: string): DecoratedError; - // (undocumented) - static createEsAutoCreateIndexError(): DecoratedError; - // (undocumented) - static createGenericNotFoundError(type?: string | null, id?: string | null): DecoratedError; - // (undocumented) - static createInvalidVersionError(versionInput?: string): DecoratedError; - // (undocumented) - static createUnsupportedTypeError(type: string): DecoratedError; - // (undocumented) - static decorateBadRequestError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateConflictError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateEsUnavailableError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateForbiddenError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateGeneralError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateNotAuthorizedError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static decorateRequestEntityTooLargeError(error: Error, reason?: string): DecoratedError; - // (undocumented) - static isBadRequestError(error: Error | DecoratedError): boolean; - // (undocumented) - static isConflictError(error: Error | DecoratedError): boolean; - // (undocumented) - static isEsAutoCreateIndexError(error: Error | DecoratedError): boolean; - // (undocumented) - static isEsUnavailableError(error: Error | DecoratedError): boolean; - // (undocumented) - static isForbiddenError(error: Error | DecoratedError): boolean; - // (undocumented) - static isInvalidVersionError(error: Error | DecoratedError): boolean; - // (undocumented) - static isNotAuthorizedError(error: Error | DecoratedError): boolean; - // (undocumented) - static isNotFoundError(error: Error | DecoratedError): boolean; - // (undocumented) - static isRequestEntityTooLargeError(error: Error | DecoratedError): boolean; - // Warning: (ae-forgotten-export) The symbol "DecoratedError" needs to be exported by the entry point index.d.ts - // - // (undocumented) - static isSavedObjectsClientError(error: any): error is DecoratedError; -} - -// @public -export interface SavedObjectsExportOptions { - excludeExportDetails?: boolean; - exportSizeLimit: number; - includeReferencesDeep?: boolean; - namespace?: string; - objects?: Array<{ - id: string; - type: string; - }>; - savedObjectsClient: SavedObjectsClientContract; - search?: string; - types?: string[]; -} - -// @public -export interface SavedObjectsExportResultDetails { - exportedCount: number; - missingRefCount: number; - missingReferences: Array<{ - id: string; - type: string; - }>; -} - -// @public (undocumented) -export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { - // (undocumented) - defaultSearchOperator?: 'AND' | 'OR'; - fields?: string[]; - // (undocumented) - filter?: string; - // (undocumented) - hasReference?: { - type: string; - id: string; - }; - // (undocumented) - page?: number; - // (undocumented) - perPage?: number; - search?: string; - searchFields?: string[]; - // (undocumented) - sortField?: string; - // (undocumented) - sortOrder?: string; - // (undocumented) - type: string | string[]; -} - -// @public -export interface SavedObjectsFindResponse { - // (undocumented) - page: number; - // (undocumented) - per_page: number; - // (undocumented) - saved_objects: Array>; - // (undocumented) - total: number; -} - -// @public -export interface SavedObjectsImportConflictError { - // (undocumented) - type: 'conflict'; -} - -// @public -export interface SavedObjectsImportError { - // (undocumented) - error: SavedObjectsImportConflictError | SavedObjectsImportUnsupportedTypeError | SavedObjectsImportMissingReferencesError | SavedObjectsImportUnknownError; - // (undocumented) - id: string; - // (undocumented) - title?: string; - // (undocumented) - type: string; -} - -// @public -export interface SavedObjectsImportMissingReferencesError { - // (undocumented) - blocking: Array<{ - type: string; - id: string; - }>; - // (undocumented) - references: Array<{ - type: string; - id: string; - }>; - // (undocumented) - type: 'missing_references'; -} - -// @public -export interface SavedObjectsImportOptions { - // (undocumented) - namespace?: string; - // (undocumented) - objectLimit: number; - // (undocumented) - overwrite: boolean; - // (undocumented) - readStream: Readable; - // (undocumented) - savedObjectsClient: SavedObjectsClientContract; - // (undocumented) - supportedTypes: string[]; -} - -// @public -export interface SavedObjectsImportResponse { - // (undocumented) - errors?: SavedObjectsImportError[]; - // (undocumented) - success: boolean; - // (undocumented) - successCount: number; -} - -// @public -export interface SavedObjectsImportRetry { - // (undocumented) - id: string; - // (undocumented) - overwrite: boolean; - // (undocumented) - replaceReferences: Array<{ - type: string; - from: string; - to: string; - }>; - // (undocumented) - type: string; -} - -// @public -export interface SavedObjectsImportUnknownError { - // (undocumented) - message: string; - // (undocumented) - statusCode: number; - // (undocumented) - type: 'unknown'; -} - -// @public -export interface SavedObjectsImportUnsupportedTypeError { - // (undocumented) - type: 'unsupported_type'; -} - -// @public (undocumented) -export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { - // (undocumented) - migrationVersion?: SavedObjectsMigrationVersion; - refresh?: MutatingOperationRefreshSetting; -} - -// @internal @deprecated (undocumented) -export interface SavedObjectsLegacyService { - // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts - // - // (undocumented) - addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; - // (undocumented) - getSavedObjectsRepository(...rest: any[]): any; - // (undocumented) - getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; - // (undocumented) - importExport: { - objectLimit: number; - importSavedObjects(options: SavedObjectsImportOptions): Promise; - resolveImportErrors(options: SavedObjectsResolveImportErrorsOptions): Promise; - getSortedObjectsForExport(options: SavedObjectsExportOptions): Promise; - }; - // (undocumented) - SavedObjectsClient: typeof SavedObjectsClient; - // (undocumented) - schema: SavedObjectsSchema; - // (undocumented) - setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; - // (undocumented) - types: string[]; -} - -// @public (undocumented) -export interface SavedObjectsMigrationLogger { - // (undocumented) - debug: (msg: string) => void; - // (undocumented) - info: (msg: string) => void; - // (undocumented) - warning: (msg: string) => void; -} - -// @public -export interface SavedObjectsMigrationVersion { - // (undocumented) - [pluginName: string]: string; -} - -// Warning: (ae-missing-release-tag) "RawDoc" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface SavedObjectsRawDoc { - // (undocumented) - _id: string; - // (undocumented) - _primary_term?: number; - // (undocumented) - _seq_no?: number; - // (undocumented) - _source: any; - // (undocumented) - _type?: string; -} - -// @public (undocumented) -export class SavedObjectsRepository { - bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; - bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; - bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; - create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; - // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "LegacyConfig" needs to be exported by the entry point index.d.ts - // - // @internal - static createRepository(migrator: KibanaMigrator, schema: SavedObjectsSchema, config: LegacyConfig, indexName: string, callCluster: APICaller, extraTypes?: string[], injectedConstructor?: any): any; - delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; - deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; - // (undocumented) - find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; - get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; - incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ - id: string; - type: string; - updated_at: string; - references: any; - version: string; - attributes: any; - }>; - update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; - } - -// @public -export interface SavedObjectsResolveImportErrorsOptions { - // (undocumented) - namespace?: string; - // (undocumented) - objectLimit: number; - // (undocumented) - readStream: Readable; - // (undocumented) - retries: SavedObjectsImportRetry[]; - // (undocumented) - savedObjectsClient: SavedObjectsClientContract; - // (undocumented) - supportedTypes: string[]; -} - -// @internal (undocumented) -export class SavedObjectsSchema { - // Warning: (ae-forgotten-export) The symbol "SavedObjectsSchemaDefinition" needs to be exported by the entry point index.d.ts - constructor(schemaDefinition?: SavedObjectsSchemaDefinition); - // (undocumented) - getConvertToAliasScript(type: string): string | undefined; - // (undocumented) - getIndexForType(config: LegacyConfig, type: string): string | undefined; - // (undocumented) - isHiddenType(type: string): boolean; - // (undocumented) - isNamespaceAgnostic(type: string): boolean; -} - -// @internal (undocumented) -export class SavedObjectsSerializer { - constructor(schema: SavedObjectsSchema); - generateRawId(namespace: string | undefined, type: string, id?: string): string; - isRawSavedObject(rawDoc: SavedObjectsRawDoc): any; - // Warning: (ae-forgotten-export) The symbol "SanitizedSavedObjectDoc" needs to be exported by the entry point index.d.ts - rawToSavedObject(doc: SavedObjectsRawDoc): SanitizedSavedObjectDoc; - savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): SavedObjectsRawDoc; - } - -// @public -export interface SavedObjectsServiceSetup { - addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; - createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; - setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; -} - -// @public -export interface SavedObjectsServiceStart { - getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; -} - -// @public (undocumented) -export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { - references?: SavedObjectReference[]; - refresh?: MutatingOperationRefreshSetting; - version?: string; -} - -// @public (undocumented) -export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> { - // (undocumented) - attributes: Partial; - // (undocumented) - references: SavedObjectReference[] | undefined; -} - -// @public -export class ScopedClusterClient implements IScopedClusterClient { - constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Headers | undefined); - callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; - callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; - } - -// @public -export interface SessionCookieValidationResult { - isValid: boolean; - path?: string; -} - -// @public -export interface SessionStorage { - clear(): void; - get(): Promise; - set(sessionValue: T): void; -} - -// @public -export interface SessionStorageCookieOptions { - encryptionKey: string; - isSecure: boolean; - name: string; - validate: (sessionValue: T | T[]) => SessionCookieValidationResult; -} - -// @public -export interface SessionStorageFactory { - // (undocumented) - asScoped: (request: KibanaRequest) => SessionStorage; -} - -// @public -export interface UiSettingsParams { - category?: string[]; - description?: string; - name?: string; - optionLabels?: Record; - options?: string[]; - readonly?: boolean; - requiresPageReload?: boolean; - type?: UiSettingsType; - value?: SavedObjectAttribute; -} - -// @public (undocumented) -export interface UiSettingsServiceSetup { - register(settings: Record): void; -} - -// @public -export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; - -// @public -export interface UserProvidedValues { - // (undocumented) - isOverridden?: boolean; - // (undocumented) - userValue?: T; -} - -// @public -export const validBodyOutput: readonly ["data", "stream"]; - - -// Warnings were encountered during analysis: -// -// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:228:15 - (ae-forgotten-export) The symbol "SharedGlobalConfig" needs to be exported by the entry point index.d.ts - -``` +## API Report File for "kibana" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import Boom from 'boom'; +import { BulkIndexDocumentsParams } from 'elasticsearch'; +import { CatAliasesParams } from 'elasticsearch'; +import { CatAllocationParams } from 'elasticsearch'; +import { CatCommonParams } from 'elasticsearch'; +import { CatFielddataParams } from 'elasticsearch'; +import { CatHealthParams } from 'elasticsearch'; +import { CatHelpParams } from 'elasticsearch'; +import { CatIndicesParams } from 'elasticsearch'; +import { CatRecoveryParams } from 'elasticsearch'; +import { CatSegmentsParams } from 'elasticsearch'; +import { CatShardsParams } from 'elasticsearch'; +import { CatSnapshotsParams } from 'elasticsearch'; +import { CatTasksParams } from 'elasticsearch'; +import { CatThreadPoolParams } from 'elasticsearch'; +import { ClearScrollParams } from 'elasticsearch'; +import { Client } from 'elasticsearch'; +import { ClusterAllocationExplainParams } from 'elasticsearch'; +import { ClusterGetSettingsParams } from 'elasticsearch'; +import { ClusterHealthParams } from 'elasticsearch'; +import { ClusterPendingTasksParams } from 'elasticsearch'; +import { ClusterPutSettingsParams } from 'elasticsearch'; +import { ClusterRerouteParams } from 'elasticsearch'; +import { ClusterStateParams } from 'elasticsearch'; +import { ClusterStatsParams } from 'elasticsearch'; +import { ConfigOptions } from 'elasticsearch'; +import { CountParams } from 'elasticsearch'; +import { CreateDocumentParams } from 'elasticsearch'; +import { DeleteDocumentByQueryParams } from 'elasticsearch'; +import { DeleteDocumentParams } from 'elasticsearch'; +import { DeleteScriptParams } from 'elasticsearch'; +import { DeleteTemplateParams } from 'elasticsearch'; +import { DetailedPeerCertificate } from 'tls'; +import { Duration } from 'moment'; +import { ExistsParams } from 'elasticsearch'; +import { ExplainParams } from 'elasticsearch'; +import { FieldStatsParams } from 'elasticsearch'; +import { GenericParams } from 'elasticsearch'; +import { GetParams } from 'elasticsearch'; +import { GetResponse } from 'elasticsearch'; +import { GetScriptParams } from 'elasticsearch'; +import { GetSourceParams } from 'elasticsearch'; +import { GetTemplateParams } from 'elasticsearch'; +import { IncomingHttpHeaders } from 'http'; +import { IndexDocumentParams } from 'elasticsearch'; +import { IndicesAnalyzeParams } from 'elasticsearch'; +import { IndicesClearCacheParams } from 'elasticsearch'; +import { IndicesCloseParams } from 'elasticsearch'; +import { IndicesCreateParams } from 'elasticsearch'; +import { IndicesDeleteAliasParams } from 'elasticsearch'; +import { IndicesDeleteParams } from 'elasticsearch'; +import { IndicesDeleteTemplateParams } from 'elasticsearch'; +import { IndicesExistsAliasParams } from 'elasticsearch'; +import { IndicesExistsParams } from 'elasticsearch'; +import { IndicesExistsTemplateParams } from 'elasticsearch'; +import { IndicesExistsTypeParams } from 'elasticsearch'; +import { IndicesFlushParams } from 'elasticsearch'; +import { IndicesFlushSyncedParams } from 'elasticsearch'; +import { IndicesForcemergeParams } from 'elasticsearch'; +import { IndicesGetAliasParams } from 'elasticsearch'; +import { IndicesGetFieldMappingParams } from 'elasticsearch'; +import { IndicesGetMappingParams } from 'elasticsearch'; +import { IndicesGetParams } from 'elasticsearch'; +import { IndicesGetSettingsParams } from 'elasticsearch'; +import { IndicesGetTemplateParams } from 'elasticsearch'; +import { IndicesGetUpgradeParams } from 'elasticsearch'; +import { IndicesOpenParams } from 'elasticsearch'; +import { IndicesPutAliasParams } from 'elasticsearch'; +import { IndicesPutMappingParams } from 'elasticsearch'; +import { IndicesPutSettingsParams } from 'elasticsearch'; +import { IndicesPutTemplateParams } from 'elasticsearch'; +import { IndicesRecoveryParams } from 'elasticsearch'; +import { IndicesRefreshParams } from 'elasticsearch'; +import { IndicesRolloverParams } from 'elasticsearch'; +import { IndicesSegmentsParams } from 'elasticsearch'; +import { IndicesShardStoresParams } from 'elasticsearch'; +import { IndicesShrinkParams } from 'elasticsearch'; +import { IndicesStatsParams } from 'elasticsearch'; +import { IndicesUpdateAliasesParams } from 'elasticsearch'; +import { IndicesUpgradeParams } from 'elasticsearch'; +import { IndicesValidateQueryParams } from 'elasticsearch'; +import { InfoParams } from 'elasticsearch'; +import { IngestDeletePipelineParams } from 'elasticsearch'; +import { IngestGetPipelineParams } from 'elasticsearch'; +import { IngestPutPipelineParams } from 'elasticsearch'; +import { IngestSimulateParams } from 'elasticsearch'; +import { KibanaConfigType } from 'src/core/server/kibana_config'; +import { Logger as Logger_2 } from 'src/core/server/logging'; +import { MGetParams } from 'elasticsearch'; +import { MGetResponse } from 'elasticsearch'; +import { MSearchParams } from 'elasticsearch'; +import { MSearchResponse } from 'elasticsearch'; +import { MSearchTemplateParams } from 'elasticsearch'; +import { MTermVectorsParams } from 'elasticsearch'; +import { NodesHotThreadsParams } from 'elasticsearch'; +import { NodesInfoParams } from 'elasticsearch'; +import { NodesStatsParams } from 'elasticsearch'; +import { ObjectType } from '@kbn/config-schema'; +import { Observable } from 'rxjs'; +import { PeerCertificate } from 'tls'; +import { PingParams } from 'elasticsearch'; +import { PutScriptParams } from 'elasticsearch'; +import { PutTemplateParams } from 'elasticsearch'; +import { Readable } from 'stream'; +import { RecursiveReadonly as RecursiveReadonly_2 } from 'kibana/public'; +import { ReindexParams } from 'elasticsearch'; +import { ReindexRethrottleParams } from 'elasticsearch'; +import { RenderSearchTemplateParams } from 'elasticsearch'; +import { Request } from 'hapi'; +import { ResponseObject } from 'hapi'; +import { ResponseToolkit } from 'hapi'; +import { ScrollParams } from 'elasticsearch'; +import { SearchParams } from 'elasticsearch'; +import { SearchResponse } from 'elasticsearch'; +import { SearchShardsParams } from 'elasticsearch'; +import { SearchTemplateParams } from 'elasticsearch'; +import { Server } from 'hapi'; +import { ShallowPromise } from '@kbn/utility-types'; +import { SnapshotCreateParams } from 'elasticsearch'; +import { SnapshotCreateRepositoryParams } from 'elasticsearch'; +import { SnapshotDeleteParams } from 'elasticsearch'; +import { SnapshotDeleteRepositoryParams } from 'elasticsearch'; +import { SnapshotGetParams } from 'elasticsearch'; +import { SnapshotGetRepositoryParams } from 'elasticsearch'; +import { SnapshotRestoreParams } from 'elasticsearch'; +import { SnapshotStatusParams } from 'elasticsearch'; +import { SnapshotVerifyRepositoryParams } from 'elasticsearch'; +import { Stream } from 'stream'; +import { SuggestParams } from 'elasticsearch'; +import { TasksCancelParams } from 'elasticsearch'; +import { TasksGetParams } from 'elasticsearch'; +import { TasksListParams } from 'elasticsearch'; +import { TermvectorsParams } from 'elasticsearch'; +import { Type } from '@kbn/config-schema'; +import { TypeOf } from '@kbn/config-schema'; +import { UpdateDocumentByQueryParams } from 'elasticsearch'; +import { UpdateDocumentParams } from 'elasticsearch'; +import { Url } from 'url'; + +// @public (undocumented) +export interface APICaller { + // (undocumented) + (endpoint: 'cluster.state', params: ClusterStateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'count', params: CountParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'create', params: CreateDocumentParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'delete', params: DeleteDocumentParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteScript', params: DeleteScriptParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'exists', params: ExistsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'explain', params: ExplainParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'fieldStats', params: FieldStatsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'get', params: GetParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'getScript', params: GetScriptParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'getSource', params: GetSourceParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'getTemplate', params: GetTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'index', params: IndexDocumentParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'info', params: InfoParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'mget', params: MGetParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'msearch', params: MSearchParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ping', params: PingParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'putScript', params: PutScriptParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'putTemplate', params: PutTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'reindex', params: ReindexParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'scroll', params: ScrollParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'search', params: SearchParams, options?: CallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'searchShards', params: SearchShardsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'suggest', params: SuggestParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'termvectors', params: TermvectorsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'update', params: UpdateDocumentParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.aliases', params: CatAliasesParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.allocation', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.count', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.health', params: CatHealthParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.help', params: CatHelpParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.indices', params: CatIndicesParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.master', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.nodes', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.plugins', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.repositories', params: CatCommonParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.segments', params: CatSegmentsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.shards', params: CatShardsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.tasks', params: CatTasksParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.health', params: ClusterHealthParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'clearScroll', params: ClearScrollParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.close', params: IndicesCloseParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.create', params: IndicesCreateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.exists', params: IndicesExistsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.flush', params: IndicesFlushParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.get', params: IndicesGetParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.open', params: IndicesOpenParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.stats', params: IndicesStatsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.info', params: NodesInfoParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.stats', params: NodesStatsParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.get', params: TasksGetParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.list', params: TasksListParams, options?: CallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: CallAPIOptions): Promise; + // (undocumented) + (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: CallAPIOptions): Promise; + // (undocumented) + (endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; +} + +// @public (undocumented) +export interface AssistanceAPIResponse { + // (undocumented) + indices: { + [indexName: string]: { + action_required: MIGRATION_ASSISTANCE_INDEX_ACTION; + }; + }; +} + +// @public (undocumented) +export interface AssistantAPIClientParams extends GenericParams { + // (undocumented) + method: 'GET'; + // (undocumented) + path: '/_migration/assistance'; +} + +// @public (undocumented) +export interface Authenticated extends AuthResultParams { + // (undocumented) + type: AuthResultType.authenticated; +} + +// @public +export type AuthenticationHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: AuthToolkit) => AuthResult | IKibanaResponse | Promise; + +// @public +export type AuthHeaders = Record; + +// @public (undocumented) +export type AuthResult = Authenticated; + +// @public +export interface AuthResultParams { + requestHeaders?: AuthHeaders; + responseHeaders?: AuthHeaders; + state?: Record; +} + +// @public (undocumented) +export enum AuthResultType { + // (undocumented) + authenticated = "authenticated" +} + +// @public +export enum AuthStatus { + authenticated = "authenticated", + unauthenticated = "unauthenticated", + unknown = "unknown" +} + +// @public +export interface AuthToolkit { + authenticated: (data?: AuthResultParams) => AuthResult; +} + +// @public +export class BasePath { + // @internal + constructor(serverBasePath?: string); + get: (request: KibanaRequest | LegacyRequest) => string; + prepend: (path: string) => string; + remove: (path: string) => string; + readonly serverBasePath: string; + set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; +} + +// Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts +// +// @internal (undocumented) +export function bootstrap({ configs, cliArgs, applyConfigOverrides, features, }: BootstrapArgs): Promise; + +// @public +export interface CallAPIOptions { + signal?: AbortSignal; + wrap401Errors?: boolean; +} + +// @public +export interface Capabilities { + [key: string]: Record>; + catalogue: Record; + management: { + [sectionId: string]: Record; + }; + navLinks: Record; +} + +// @public +export type CapabilitiesProvider = () => Partial; + +// @public +export interface CapabilitiesSetup { + registerProvider(provider: CapabilitiesProvider): void; + registerSwitcher(switcher: CapabilitiesSwitcher): void; +} + +// @public +export interface CapabilitiesStart { + resolveCapabilities(request: KibanaRequest): Promise; +} + +// @public +export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capabilities) => Partial | Promise>; + +// @public +export class ClusterClient implements IClusterClient { + constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); + asScoped(request?: KibanaRequest | LegacyRequest | FakeRequest): IScopedClusterClient; + callAsInternalUser: APICaller; + close(): void; + } + +// @public (undocumented) +export type ConfigPath = string | string[]; + +// @internal (undocumented) +export class ConfigService { + // Warning: (ae-forgotten-export) The symbol "Config" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "Env" needs to be exported by the entry point index.d.ts + constructor(config$: Observable, env: Env, logger: LoggerFactory); + atPath(path: ConfigPath): Observable; + getConfig$(): Observable; + // (undocumented) + getUnusedPaths(): Promise; + // (undocumented) + getUsedPaths(): Promise; + // (undocumented) + isEnabledAtPath(path: ConfigPath): Promise; + optionalAtPath(path: ConfigPath): Observable; + setSchema(path: ConfigPath, schema: Type): Promise; + } + +// @public +export interface ContextSetup { + createContextContainer>(): IContextContainer; +} + +// @internal (undocumented) +export type CoreId = symbol; + +// @public +export interface CoreSetup { + // (undocumented) + capabilities: CapabilitiesSetup; + // (undocumented) + context: ContextSetup; + // (undocumented) + elasticsearch: ElasticsearchServiceSetup; + // (undocumented) + http: HttpServiceSetup; + // (undocumented) + savedObjects: SavedObjectsServiceSetup; + // (undocumented) + uiSettings: UiSettingsServiceSetup; +} + +// @public +export interface CoreStart { + // (undocumented) + capabilities: CapabilitiesStart; + // (undocumented) + savedObjects: SavedObjectsServiceStart; +} + +// @public +export interface CustomHttpResponseOptions { + body?: T; + headers?: ResponseHeaders; + // (undocumented) + statusCode: number; +} + +// @public (undocumented) +export interface DeprecationAPIClientParams extends GenericParams { + // (undocumented) + method: 'GET'; + // (undocumented) + path: '/_migration/deprecations'; +} + +// @public (undocumented) +export interface DeprecationAPIResponse { + // (undocumented) + cluster_settings: DeprecationInfo[]; + // (undocumented) + index_settings: IndexSettingsDeprecationInfo; + // (undocumented) + ml_settings: DeprecationInfo[]; + // (undocumented) + node_settings: DeprecationInfo[]; +} + +// @public (undocumented) +export interface DeprecationInfo { + // (undocumented) + details?: string; + // (undocumented) + level: MIGRATION_DEPRECATION_LEVEL; + // (undocumented) + message: string; + // (undocumented) + url: string; +} + +// @public +export interface DiscoveredPlugin { + readonly configPath: ConfigPath; + readonly id: PluginName; + readonly optionalPlugins: readonly PluginName[]; + readonly requiredPlugins: readonly PluginName[]; +} + +// Warning: (ae-forgotten-export) The symbol "ElasticsearchConfig" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type ElasticsearchClientConfig = Pick & Pick & { + pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; + requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; + sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; + ssl?: Partial; +}; + +// @public (undocumented) +export interface ElasticsearchError extends Boom { + // (undocumented) + [code]?: string; +} + +// @public +export class ElasticsearchErrorHelpers { + // (undocumented) + static decorateNotAuthorizedError(error: Error, reason?: string): ElasticsearchError; + // (undocumented) + static isNotAuthorizedError(error: any): error is ElasticsearchError; +} + +// @public (undocumented) +export interface ElasticsearchServiceSetup { + readonly adminClient$: Observable; + readonly createClient: (type: string, clientConfig?: Partial) => IClusterClient; + readonly dataClient$: Observable; +} + +// @public (undocumented) +export interface EnvironmentMode { + // (undocumented) + dev: boolean; + // (undocumented) + name: 'development' | 'production'; + // (undocumented) + prod: boolean; +} + +// @public +export interface ErrorHttpResponseOptions { + body?: ResponseError; + headers?: ResponseHeaders; +} + +// @public +export interface FakeRequest { + headers: Headers; +} + +// @public +export type GetAuthHeaders = (request: KibanaRequest | LegacyRequest) => AuthHeaders | undefined; + +// @public +export type GetAuthState = (request: KibanaRequest | LegacyRequest) => { + status: AuthStatus; + state: unknown; +}; + +// @public +export type HandlerContextType> = T extends HandlerFunction ? U : never; + +// @public +export type HandlerFunction = (context: T, ...args: any[]) => any; + +// @public +export type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; + +// @public +export type Headers = { + [header in KnownHeaders]?: string | string[] | undefined; +} & { + [header: string]: string | string[] | undefined; +}; + +// @public +export interface HttpResponseOptions { + body?: HttpResponsePayload; + headers?: ResponseHeaders; +} + +// @public +export type HttpResponsePayload = undefined | string | Record | Buffer | Stream; + +// @public +export interface HttpServiceSetup { + basePath: IBasePath; + createCookieSessionStorageFactory: (cookieOptions: SessionStorageCookieOptions) => Promise>; + createRouter: () => IRouter; + isTlsEnabled: boolean; + registerAuth: (handler: AuthenticationHandler) => void; + registerOnPostAuth: (handler: OnPostAuthHandler) => void; + registerOnPreAuth: (handler: OnPreAuthHandler) => void; + registerOnPreResponse: (handler: OnPreResponseHandler) => void; + registerRouteHandlerContext: (contextName: T, provider: RequestHandlerContextProvider) => RequestHandlerContextContainer; +} + +// @public (undocumented) +export interface HttpServiceStart { + isListening: (port: number) => boolean; +} + +// @public +export type IBasePath = Pick; + +// @public +export type IClusterClient = Pick; + +// @public +export interface IContextContainer> { + createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; + registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; +} + +// @public +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; + +// @public +export interface IKibanaResponse { + // (undocumented) + readonly options: HttpResponseOptions; + // (undocumented) + readonly payload?: T; + // (undocumented) + readonly status: number; +} + +// @public +export interface IKibanaSocket { + readonly authorizationError?: Error; + readonly authorized?: boolean; + // (undocumented) + getPeerCertificate(detailed: true): DetailedPeerCertificate | null; + // (undocumented) + getPeerCertificate(detailed: false): PeerCertificate | null; + getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate | null; +} + +// @public (undocumented) +export interface IndexSettingsDeprecationInfo { + // (undocumented) + [indexName: string]: DeprecationInfo[]; +} + +// @public +export interface IRouter { + delete: RouteRegistrar<'delete'>; + get: RouteRegistrar<'get'>; + // Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts + // + // @internal + getRoutes: () => RouterRoute[]; + handleLegacyErrors:

(handler: RequestHandler) => RequestHandler; + patch: RouteRegistrar<'patch'>; + post: RouteRegistrar<'post'>; + put: RouteRegistrar<'put'>; + routerPath: string; +} + +// @public +export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; + +// @public +export type ISavedObjectsRepository = Pick; + +// @public +export type IScopedClusterClient = Pick; + +// @public +export interface IUiSettingsClient { + get: (key: string) => Promise; + getAll: () => Promise>; + getRegistered: () => Readonly>; + getUserProvided: () => Promise>>; + isOverridden: (key: string) => boolean; + remove: (key: string) => Promise; + removeMany: (keys: string[]) => Promise; + set: (key: string, value: any) => Promise; + setMany: (changes: Record) => Promise; +} + +// @public +export class KibanaRequest { + // @internal (undocumented) + protected readonly [requestSymbol]: Request; + constructor(request: Request, params: Params, query: Query, body: Body, withoutSecretHeaders: boolean); + // (undocumented) + readonly body: Body; + // @internal + static from

| Type>(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders?: boolean): KibanaRequest; + readonly headers: Headers; + // (undocumented) + readonly params: Params; + // (undocumented) + readonly query: Query; + readonly route: RecursiveReadonly>; + // (undocumented) + readonly socket: IKibanaSocket; + readonly url: Url; + } + +// @public +export interface KibanaRequestRoute { + // (undocumented) + method: Method; + // (undocumented) + options: KibanaRequestRouteOptions; + // (undocumented) + path: string; +} + +// @public +export type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; + +// @public +export type KibanaResponseFactory = typeof kibanaResponseFactory; + +// @public +export const kibanaResponseFactory: { + custom: | Buffer | Stream | { + message: string | Error; + attributes?: Record | undefined; + } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; + badRequest: (options?: ErrorHttpResponseOptions) => KibanaResponse; + unauthorized: (options?: ErrorHttpResponseOptions) => KibanaResponse; + forbidden: (options?: ErrorHttpResponseOptions) => KibanaResponse; + notFound: (options?: ErrorHttpResponseOptions) => KibanaResponse; + conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; + internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; + customError: (options: CustomHttpResponseOptions) => KibanaResponse; + redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; + ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + noContent: (options?: HttpResponseOptions) => KibanaResponse; +}; + +// Warning: (ae-forgotten-export) The symbol "KnownKeys" needs to be exported by the entry point index.d.ts +// +// @public +export type KnownHeaders = KnownKeys; + +// @public @deprecated (undocumented) +export interface LegacyRequest extends Request { +} + +// @public @deprecated (undocumented) +export interface LegacyServiceSetupDeps { + // Warning: (ae-forgotten-export) The symbol "InternalCoreSetup" needs to be exported by the entry point index.d.ts + // + // (undocumented) + core: InternalCoreSetup & { + plugins: PluginsServiceSetup; + }; + // (undocumented) + plugins: Record; +} + +// @public @deprecated (undocumented) +export interface LegacyServiceStartDeps { + // Warning: (ae-forgotten-export) The symbol "InternalCoreStart" needs to be exported by the entry point index.d.ts + // + // (undocumented) + core: InternalCoreStart & { + plugins: PluginsServiceStart; + }; + // (undocumented) + plugins: Record; +} + +// Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts +// +// @public +export type LifecycleResponseFactory = typeof lifecycleResponseFactory; + +// @public +export interface Logger { + debug(message: string, meta?: LogMeta): void; + error(errorOrMessage: string | Error, meta?: LogMeta): void; + fatal(errorOrMessage: string | Error, meta?: LogMeta): void; + info(message: string, meta?: LogMeta): void; + // @internal (undocumented) + log(record: LogRecord): void; + trace(message: string, meta?: LogMeta): void; + warn(errorOrMessage: string | Error, meta?: LogMeta): void; +} + +// @public +export interface LoggerFactory { + get(...contextParts: string[]): Logger; +} + +// @internal +export class LogLevel { + // (undocumented) + static readonly All: LogLevel; + // (undocumented) + static readonly Debug: LogLevel; + // (undocumented) + static readonly Error: LogLevel; + // (undocumented) + static readonly Fatal: LogLevel; + static fromId(level: LogLevelId): LogLevel; + // Warning: (ae-forgotten-export) The symbol "LogLevelId" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly id: LogLevelId; + // (undocumented) + static readonly Info: LogLevel; + // (undocumented) + static readonly Off: LogLevel; + supports(level: LogLevel): boolean; + // (undocumented) + static readonly Trace: LogLevel; + // (undocumented) + readonly value: number; + // (undocumented) + static readonly Warn: LogLevel; +} + +// @public +export interface LogMeta { + // (undocumented) + [key: string]: any; +} + +// @internal +export interface LogRecord { + // (undocumented) + context: string; + // (undocumented) + error?: Error; + // (undocumented) + level: LogLevel; + // (undocumented) + message: string; + // (undocumented) + meta?: { + [name: string]: any; + }; + // (undocumented) + timestamp: Date; +} + +// @public (undocumented) +export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; + +// @public (undocumented) +export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; + +// @public +export type MutatingOperationRefreshSetting = boolean | 'wait_for'; + +// Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts +// +// @public +export type OnPostAuthHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPostAuthToolkit) => OnPostAuthResult | KibanaResponse | Promise; + +// @public +export interface OnPostAuthToolkit { + next: () => OnPostAuthResult; +} + +// Warning: (ae-forgotten-export) The symbol "OnPreAuthResult" needs to be exported by the entry point index.d.ts +// +// @public +export type OnPreAuthHandler = (request: KibanaRequest, response: LifecycleResponseFactory, toolkit: OnPreAuthToolkit) => OnPreAuthResult | KibanaResponse | Promise; + +// @public +export interface OnPreAuthToolkit { + next: () => OnPreAuthResult; + rewriteUrl: (url: string) => OnPreAuthResult; +} + +// @public +export interface OnPreResponseExtensions { + headers?: ResponseHeaders; +} + +// Warning: (ae-forgotten-export) The symbol "OnPreResponseResult" needs to be exported by the entry point index.d.ts +// +// @public +export type OnPreResponseHandler = (request: KibanaRequest, preResponse: OnPreResponseInfo, toolkit: OnPreResponseToolkit) => OnPreResponseResult | Promise; + +// @public +export interface OnPreResponseInfo { + // (undocumented) + statusCode: number; +} + +// @public +export interface OnPreResponseToolkit { + next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult; +} + +// @public (undocumented) +export interface PackageInfo { + // (undocumented) + branch: string; + // (undocumented) + buildNum: number; + // (undocumented) + buildSha: string; + // (undocumented) + dist: boolean; + // (undocumented) + version: string; +} + +// @public +export interface Plugin { + // (undocumented) + setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; + // (undocumented) + start(core: CoreStart, plugins: TPluginsStart): TStart | Promise; + // (undocumented) + stop?(): void; +} + +// @public +export interface PluginConfigDescriptor { + exposeToBrowser?: { + [P in keyof T]?: boolean; + }; + schema: PluginConfigSchema; +} + +// @public +export type PluginConfigSchema = Type; + +// @public +export type PluginInitializer = (core: PluginInitializerContext) => Plugin; + +// @public +export interface PluginInitializerContext { + // (undocumented) + config: { + legacy: { + globalConfig$: Observable; + }; + create: () => Observable; + createIfExists: () => Observable; + }; + // (undocumented) + env: { + mode: EnvironmentMode; + packageInfo: Readonly; + }; + // (undocumented) + logger: LoggerFactory; + // (undocumented) + opaqueId: PluginOpaqueId; +} + +// @public +export interface PluginManifest { + readonly configPath: ConfigPath; + readonly id: PluginName; + readonly kibanaVersion: string; + readonly optionalPlugins: readonly PluginName[]; + readonly requiredPlugins: readonly PluginName[]; + readonly server: boolean; + readonly ui: boolean; + readonly version: string; +} + +// @public +export type PluginName = string; + +// @public (undocumented) +export type PluginOpaqueId = symbol; + +// @public (undocumented) +export interface PluginsServiceSetup { + // (undocumented) + contracts: Map; + // (undocumented) + uiPlugins: { + internal: Map; + public: Map; + browserConfigs: Map>; + }; +} + +// @public (undocumented) +export interface PluginsServiceStart { + // (undocumented) + contracts: Map; +} + +// Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ + [K in keyof T]: RecursiveReadonly; +}> : T; + +// @public +export type RedirectResponseOptions = HttpResponseOptions & { + headers: { + location: string; + }; +}; + +// @public +export type RequestHandler

| Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; + +// @public +export interface RequestHandlerContext { + // (undocumented) + core: { + savedObjects: { + client: SavedObjectsClientContract; + }; + elasticsearch: { + dataClient: IScopedClusterClient; + adminClient: IScopedClusterClient; + }; + uiSettings: { + client: IUiSettingsClient; + }; + }; +} + +// @public +export type RequestHandlerContextContainer = IContextContainer>; + +// @public +export type RequestHandlerContextProvider = IContextProvider, TContextName>; + +// @public +export type ResponseError = string | Error | { + message: string | Error; + attributes?: ResponseErrorAttributes; +}; + +// @public +export type ResponseErrorAttributes = Record; + +// @public +export type ResponseHeaders = { + [header in KnownHeaders]?: string | string[]; +} & { + [header: string]: string | string[]; +}; + +// @public +export interface RouteConfig

| Type, Method extends RouteMethod> { + options?: RouteConfigOptions; + path: string; + validate: RouteSchemas | false; +} + +// @public +export interface RouteConfigOptions { + authRequired?: boolean; + body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; + tags?: readonly string[]; +} + +// @public +export interface RouteConfigOptionsBody { + accepts?: RouteContentType | RouteContentType[] | string | string[]; + maxBytes?: number; + output?: typeof validBodyOutput[number]; + parse?: boolean | 'gunzip'; +} + +// @public +export type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; + +// @public +export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; + +// @public +export type RouteRegistrar =

| Type>(route: RouteConfig, handler: RequestHandler) => void; + +// @public +export interface RouteSchemas

| Type> { + // (undocumented) + body?: B; + // (undocumented) + params?: P; + // (undocumented) + query?: Q; +} + +// @public (undocumented) +export interface SavedObject { + attributes: T; + // (undocumented) + error?: { + message: string; + statusCode: number; + }; + id: string; + migrationVersion?: SavedObjectsMigrationVersion; + references: SavedObjectReference[]; + type: string; + updated_at?: string; + version?: string; +} + +// @public +export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; + +// @public +export interface SavedObjectAttributes { + // (undocumented) + [key: string]: SavedObjectAttribute; +} + +// @public +export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; + +// @public +export interface SavedObjectReference { + // (undocumented) + id: string; + // (undocumented) + name: string; + // (undocumented) + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBaseOptions { + namespace?: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkCreateObject { + // (undocumented) + attributes: T; + // (undocumented) + id?: string; + migrationVersion?: SavedObjectsMigrationVersion; + // (undocumented) + references?: SavedObjectReference[]; + // (undocumented) + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkGetObject { + fields?: string[]; + // (undocumented) + id: string; + // (undocumented) + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkResponse { + // (undocumented) + saved_objects: Array>; +} + +// @public (undocumented) +export interface SavedObjectsBulkResponse { + // (undocumented) + saved_objects: Array>; +} + +// @public (undocumented) +export interface SavedObjectsBulkUpdateObject extends Pick { + attributes: Partial; + id: string; + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export interface SavedObjectsBulkUpdateResponse { + // (undocumented) + saved_objects: Array>; +} + +// @public (undocumented) +export class SavedObjectsClient { + // @internal + constructor(repository: ISavedObjectsRepository); + bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; + bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; + create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + // (undocumented) + errors: typeof SavedObjectsErrorHelpers; + // (undocumented) + static errors: typeof SavedObjectsErrorHelpers; + find(options: SavedObjectsFindOptions): Promise>; + get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; + update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; +} + +// @public +export type SavedObjectsClientContract = Pick; + +// @public +export type SavedObjectsClientFactory = ({ request, }: { + request: Request; +}) => SavedObjectsClientContract; + +// @public +export interface SavedObjectsClientProviderOptions { + // (undocumented) + excludedWrappers?: string[]; +} + +// @public +export type SavedObjectsClientWrapperFactory = (options: SavedObjectsClientWrapperOptions) => SavedObjectsClientContract; + +// @public +export interface SavedObjectsClientWrapperOptions { + // (undocumented) + client: SavedObjectsClientContract; + // (undocumented) + request: Request; +} + +// @public (undocumented) +export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { + id?: string; + migrationVersion?: SavedObjectsMigrationVersion; + overwrite?: boolean; + // (undocumented) + references?: SavedObjectReference[]; + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export class SavedObjectsErrorHelpers { + // (undocumented) + static createBadRequestError(reason?: string): DecoratedError; + // (undocumented) + static createEsAutoCreateIndexError(): DecoratedError; + // (undocumented) + static createGenericNotFoundError(type?: string | null, id?: string | null): DecoratedError; + // (undocumented) + static createInvalidVersionError(versionInput?: string): DecoratedError; + // (undocumented) + static createUnsupportedTypeError(type: string): DecoratedError; + // (undocumented) + static decorateBadRequestError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateConflictError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateEsUnavailableError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateForbiddenError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateGeneralError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateNotAuthorizedError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static decorateRequestEntityTooLargeError(error: Error, reason?: string): DecoratedError; + // (undocumented) + static isBadRequestError(error: Error | DecoratedError): boolean; + // (undocumented) + static isConflictError(error: Error | DecoratedError): boolean; + // (undocumented) + static isEsAutoCreateIndexError(error: Error | DecoratedError): boolean; + // (undocumented) + static isEsUnavailableError(error: Error | DecoratedError): boolean; + // (undocumented) + static isForbiddenError(error: Error | DecoratedError): boolean; + // (undocumented) + static isInvalidVersionError(error: Error | DecoratedError): boolean; + // (undocumented) + static isNotAuthorizedError(error: Error | DecoratedError): boolean; + // (undocumented) + static isNotFoundError(error: Error | DecoratedError): boolean; + // (undocumented) + static isRequestEntityTooLargeError(error: Error | DecoratedError): boolean; + // Warning: (ae-forgotten-export) The symbol "DecoratedError" needs to be exported by the entry point index.d.ts + // + // (undocumented) + static isSavedObjectsClientError(error: any): error is DecoratedError; +} + +// @public +export interface SavedObjectsExportOptions { + excludeExportDetails?: boolean; + exportSizeLimit: number; + includeReferencesDeep?: boolean; + namespace?: string; + objects?: Array<{ + id: string; + type: string; + }>; + savedObjectsClient: SavedObjectsClientContract; + search?: string; + types?: string[]; +} + +// @public +export interface SavedObjectsExportResultDetails { + exportedCount: number; + missingRefCount: number; + missingReferences: Array<{ + id: string; + type: string; + }>; +} + +// @public (undocumented) +export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { + // (undocumented) + defaultSearchOperator?: 'AND' | 'OR'; + fields?: string[]; + // (undocumented) + filter?: string; + // (undocumented) + hasReference?: { + type: string; + id: string; + }; + // (undocumented) + page?: number; + // (undocumented) + perPage?: number; + search?: string; + searchFields?: string[]; + // (undocumented) + sortField?: string; + // (undocumented) + sortOrder?: string; + // (undocumented) + type: string | string[]; +} + +// @public +export interface SavedObjectsFindResponse { + // (undocumented) + page: number; + // (undocumented) + per_page: number; + // (undocumented) + saved_objects: Array>; + // (undocumented) + total: number; +} + +// @public +export interface SavedObjectsImportConflictError { + // (undocumented) + type: 'conflict'; +} + +// @public +export interface SavedObjectsImportError { + // (undocumented) + error: SavedObjectsImportConflictError | SavedObjectsImportUnsupportedTypeError | SavedObjectsImportMissingReferencesError | SavedObjectsImportUnknownError; + // (undocumented) + id: string; + // (undocumented) + title?: string; + // (undocumented) + type: string; +} + +// @public +export interface SavedObjectsImportMissingReferencesError { + // (undocumented) + blocking: Array<{ + type: string; + id: string; + }>; + // (undocumented) + references: Array<{ + type: string; + id: string; + }>; + // (undocumented) + type: 'missing_references'; +} + +// @public +export interface SavedObjectsImportOptions { + // (undocumented) + namespace?: string; + // (undocumented) + objectLimit: number; + // (undocumented) + overwrite: boolean; + // (undocumented) + readStream: Readable; + // (undocumented) + savedObjectsClient: SavedObjectsClientContract; + // (undocumented) + supportedTypes: string[]; +} + +// @public +export interface SavedObjectsImportResponse { + // (undocumented) + errors?: SavedObjectsImportError[]; + // (undocumented) + success: boolean; + // (undocumented) + successCount: number; +} + +// @public +export interface SavedObjectsImportRetry { + // (undocumented) + id: string; + // (undocumented) + overwrite: boolean; + // (undocumented) + replaceReferences: Array<{ + type: string; + from: string; + to: string; + }>; + // (undocumented) + type: string; +} + +// @public +export interface SavedObjectsImportUnknownError { + // (undocumented) + message: string; + // (undocumented) + statusCode: number; + // (undocumented) + type: 'unknown'; +} + +// @public +export interface SavedObjectsImportUnsupportedTypeError { + // (undocumented) + type: 'unsupported_type'; +} + +// @public (undocumented) +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { + // (undocumented) + migrationVersion?: SavedObjectsMigrationVersion; + refresh?: MutatingOperationRefreshSetting; +} + +// @internal @deprecated (undocumented) +export interface SavedObjectsLegacyService { + // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts + // + // (undocumented) + addScopedSavedObjectsClientWrapperFactory: SavedObjectsClientProvider['addClientWrapperFactory']; + // (undocumented) + getSavedObjectsRepository(...rest: any[]): any; + // (undocumented) + getScopedSavedObjectsClient: SavedObjectsClientProvider['getClient']; + // (undocumented) + importExport: { + objectLimit: number; + importSavedObjects(options: SavedObjectsImportOptions): Promise; + resolveImportErrors(options: SavedObjectsResolveImportErrorsOptions): Promise; + getSortedObjectsForExport(options: SavedObjectsExportOptions): Promise; + }; + // (undocumented) + SavedObjectsClient: typeof SavedObjectsClient; + // (undocumented) + schema: SavedObjectsSchema; + // (undocumented) + setScopedSavedObjectsClientFactory: SavedObjectsClientProvider['setClientFactory']; + // (undocumented) + types: string[]; +} + +// @public (undocumented) +export interface SavedObjectsMigrationLogger { + // (undocumented) + debug: (msg: string) => void; + // (undocumented) + info: (msg: string) => void; + // (undocumented) + warning: (msg: string) => void; +} + +// @public +export interface SavedObjectsMigrationVersion { + // (undocumented) + [pluginName: string]: string; +} + +// Warning: (ae-missing-release-tag) "RawDoc" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface SavedObjectsRawDoc { + // (undocumented) + _id: string; + // (undocumented) + _primary_term?: number; + // (undocumented) + _seq_no?: number; + // (undocumented) + _source: any; + // (undocumented) + _type?: string; +} + +// @public (undocumented) +export class SavedObjectsRepository { + bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; + bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; + create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "LegacyConfig" needs to be exported by the entry point index.d.ts + // + // @internal + static createRepository(migrator: KibanaMigrator, schema: SavedObjectsSchema, config: LegacyConfig, indexName: string, callCluster: APICaller, extraTypes?: string[], injectedConstructor?: any): any; + delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; + // (undocumented) + find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; + get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; + incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>; + update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; + } + +// @public +export interface SavedObjectsResolveImportErrorsOptions { + // (undocumented) + namespace?: string; + // (undocumented) + objectLimit: number; + // (undocumented) + readStream: Readable; + // (undocumented) + retries: SavedObjectsImportRetry[]; + // (undocumented) + savedObjectsClient: SavedObjectsClientContract; + // (undocumented) + supportedTypes: string[]; +} + +// @internal (undocumented) +export class SavedObjectsSchema { + // Warning: (ae-forgotten-export) The symbol "SavedObjectsSchemaDefinition" needs to be exported by the entry point index.d.ts + constructor(schemaDefinition?: SavedObjectsSchemaDefinition); + // (undocumented) + getConvertToAliasScript(type: string): string | undefined; + // (undocumented) + getIndexForType(config: LegacyConfig, type: string): string | undefined; + // (undocumented) + isHiddenType(type: string): boolean; + // (undocumented) + isNamespaceAgnostic(type: string): boolean; +} + +// @internal (undocumented) +export class SavedObjectsSerializer { + constructor(schema: SavedObjectsSchema); + generateRawId(namespace: string | undefined, type: string, id?: string): string; + isRawSavedObject(rawDoc: SavedObjectsRawDoc): any; + // Warning: (ae-forgotten-export) The symbol "SanitizedSavedObjectDoc" needs to be exported by the entry point index.d.ts + rawToSavedObject(doc: SavedObjectsRawDoc): SanitizedSavedObjectDoc; + savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): SavedObjectsRawDoc; + } + +// @public +export interface SavedObjectsServiceSetup { + addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; +} + +// @public +export interface SavedObjectsServiceStart { + getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; +} + +// @public (undocumented) +export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { + references?: SavedObjectReference[]; + refresh?: MutatingOperationRefreshSetting; + version?: string; +} + +// @public (undocumented) +export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> { + // (undocumented) + attributes: Partial; + // (undocumented) + references: SavedObjectReference[] | undefined; +} + +// @public +export class ScopedClusterClient implements IScopedClusterClient { + constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Headers | undefined); + callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; + callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; + } + +// @public +export interface SessionCookieValidationResult { + isValid: boolean; + path?: string; +} + +// @public +export interface SessionStorage { + clear(): void; + get(): Promise; + set(sessionValue: T): void; +} + +// @public +export interface SessionStorageCookieOptions { + encryptionKey: string; + isSecure: boolean; + name: string; + validate: (sessionValue: T | T[]) => SessionCookieValidationResult; +} + +// @public +export interface SessionStorageFactory { + // (undocumented) + asScoped: (request: KibanaRequest) => SessionStorage; +} + +// @public (undocumented) +export type SharedGlobalConfig = RecursiveReadonly_2<{ + kibana: Pick; + elasticsearch: Pick; + path: Pick; +}>; + +// @public +export interface UiSettingsParams { + category?: string[]; + description?: string; + name?: string; + optionLabels?: Record; + options?: string[]; + readonly?: boolean; + requiresPageReload?: boolean; + type?: UiSettingsType; + value?: SavedObjectAttribute; +} + +// @public (undocumented) +export interface UiSettingsServiceSetup { + register(settings: Record): void; +} + +// @public +export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; + +// @public +export interface UserProvidedValues { + // (undocumented) + isOverridden?: boolean; + // (undocumented) + userValue?: T; +} + +// @public +export const validBodyOutput: readonly ["data", "stream"]; + + +// Warnings were encountered during analysis: +// +// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:213:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:213:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:214:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:215:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts + +``` diff --git a/src/legacy/core_plugins/telemetry/common/constants.ts b/src/legacy/core_plugins/telemetry/common/constants.ts index 7b0c62276f2902..7e366676a8565b 100644 --- a/src/legacy/core_plugins/telemetry/common/constants.ts +++ b/src/legacy/core_plugins/telemetry/common/constants.ts @@ -70,3 +70,8 @@ export const TELEMETRY_STATS_TYPE = 'telemetry'; * @type {string} */ export const UI_METRIC_USAGE_TYPE = 'ui_metric'; + +/** + * Link to Advanced Settings. + */ +export const PATH_TO_ADVANCED_SETTINGS = 'kibana#/management/kibana/settings'; diff --git a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap index 9c26909dc68f18..193205cd394e24 100644 --- a/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap +++ b/src/legacy/core_plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap @@ -10,7 +10,7 @@ exports[`OptInDetailsComponent renders as expected 1`] = ` values={ Object { "disableLink": any; @@ -57,7 +59,10 @@ export class OptedInBanner extends React.PureComponent { ), disableLink: ( - + (this.type.params, this, aggs); } isFilterable() { @@ -425,13 +424,15 @@ export class AggConfig { } this.__type = type; + let availableFields = []; + + const fieldParam = this.type && this.type.params.find((p: any) => p.type === 'field'); + + if (fieldParam) { + // @ts-ignore + availableFields = fieldParam.getAvailableFields(this.getIndexPattern().fields); + } - const fieldParam = - this.type && (this.type.params.find((p: any) => p.type === 'field') as FieldParamType); - // @ts-ignore - const availableFields = fieldParam - ? fieldParam.getAvailableFields(this.getIndexPattern().fields) - : []; // clear out the previous params except for a few special ones this.setParams({ // split row/columns is "outside" of the agg, so don't reset it diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts index b4ea0ec8bc465c..b5a7474c99b0e7 100644 --- a/src/legacy/ui/public/agg_types/agg_configs.ts +++ b/src/legacy/ui/public/agg_types/agg_configs.ts @@ -126,7 +126,10 @@ export class AggConfigs { return aggConfigs; } - createAggConfig(params: AggConfig | AggConfigOptions, { addToAggConfigs = true } = {}) { + createAggConfig( + params: AggConfig | AggConfigOptions, + { addToAggConfigs = true } = {} + ) { let aggConfig; if (params instanceof AggConfig) { aggConfig = params; @@ -137,7 +140,7 @@ export class AggConfigs { if (addToAggConfigs) { this.aggs.push(aggConfig); } - return aggConfig; + return aggConfig as T; } /** diff --git a/src/legacy/ui/public/agg_types/agg_params.test.ts b/src/legacy/ui/public/agg_types/agg_params.test.ts index 28d852c7f2567d..25e62e06d52d70 100644 --- a/src/legacy/ui/public/agg_types/agg_params.test.ts +++ b/src/legacy/ui/public/agg_types/agg_params.test.ts @@ -17,17 +17,18 @@ * under the License. */ -import { AggParam, initParams } from './agg_params'; +import { initParams } from './agg_params'; import { BaseParamType } from './param_types/base'; import { FieldParamType } from './param_types/field'; import { OptionedParamType } from './param_types/optioned'; +import { AggParamType } from '../agg_types/param_types/agg'; jest.mock('ui/new_platform'); describe('AggParams class', () => { describe('constructor args', () => { it('accepts an array of param defs', () => { - const params = [{ name: 'one' }, { name: 'two' }] as AggParam[]; + const params = [{ name: 'one' }, { name: 'two' }] as AggParamType[]; const aggParams = initParams(params); expect(aggParams).toHaveLength(params.length); @@ -37,7 +38,7 @@ describe('AggParams class', () => { describe('AggParam creation', () => { it('Uses the FieldParamType class for params with the name "field"', () => { - const params = [{ name: 'field', type: 'field' }] as AggParam[]; + const params = [{ name: 'field', type: 'field' }] as AggParamType[]; const aggParams = initParams(params); expect(aggParams).toHaveLength(params.length); @@ -50,7 +51,7 @@ describe('AggParams class', () => { name: 'order', type: 'optioned', }, - ]; + ] as AggParamType[]; const aggParams = initParams(params); expect(aggParams).toHaveLength(params.length); @@ -71,7 +72,7 @@ describe('AggParams class', () => { name: 'waist', displayName: 'waist', }, - ] as AggParam[]; + ] as AggParamType[]; const aggParams = initParams(params); diff --git a/src/legacy/ui/public/agg_types/agg_params.ts b/src/legacy/ui/public/agg_types/agg_params.ts index 8925bf9e3d46c6..262a57f4a5aa30 100644 --- a/src/legacy/ui/public/agg_types/agg_params.ts +++ b/src/legacy/ui/public/agg_types/agg_params.ts @@ -44,17 +44,10 @@ export interface AggParamOption { enabled?(agg: AggConfig): boolean; } -export interface AggParamConfig { - type: string; -} - -export const initParams = < - TAggParam extends AggParam = AggParam, - TAggParamConfig extends AggParamConfig = AggParamConfig ->( - params: TAggParamConfig[] +export const initParams = ( + params: TAggParam[] ): TAggParam[] => - params.map((config: TAggParamConfig) => { + params.map((config: TAggParam) => { const Class = paramTypeMap[config.type] || paramTypeMap._default; return new Class(config); @@ -74,20 +67,25 @@ export const initParams = < * output object which is used to create the agg dsl for the search request. All other properties * are dependent on the AggParam#write methods which should be studied for each AggType. */ -export const writeParams = ( - params: AggParam[], - aggConfig: AggConfig, +export const writeParams = < + TAggConfig extends AggConfig = AggConfig, + TAggParam extends AggParamType = AggParamType +>( + params: Array> = [], + aggConfig: TAggConfig, aggs?: AggConfigs, locals?: Record ) => { const output = { params: {} as Record }; locals = locals || {}; - params.forEach(function(param) { + params.forEach(param => { if (param.write) { param.write(aggConfig, output, aggs, locals); } else { - output.params[param.name] = aggConfig.params[param.name]; + if (param && param.name) { + output.params[param.name] = aggConfig.params[param.name]; + } } }); diff --git a/src/legacy/ui/public/agg_types/agg_type.ts b/src/legacy/ui/public/agg_types/agg_type.ts index 5216affb3e52dc..ff4c6875ec6c06 100644 --- a/src/legacy/ui/public/agg_types/agg_type.ts +++ b/src/legacy/ui/public/agg_types/agg_type.ts @@ -20,19 +20,19 @@ import { constant, noop, identity } from 'lodash'; import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; -import { AggParam, initParams } from './agg_params'; +import { initParams } from './agg_params'; import { AggConfig } from '../vis'; import { AggConfigs } from './agg_configs'; import { SearchSource } from '../courier'; import { Adapters } from '../inspector'; import { BaseParamType } from './param_types/base'; - +import { AggParamType } from '../agg_types/param_types/agg'; import { KBN_FIELD_TYPES, FieldFormat } from '../../../../plugins/data/public'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, - TParam extends AggParam = AggParam + TParam extends AggParamType = AggParamType > { name: string; title: string; @@ -46,7 +46,7 @@ export interface AggTypeConfig< getRequestAggs?: ((aggConfig: TAggConfig) => TAggConfig[]) | (() => TAggConfig[] | void); getResponseAggs?: ((aggConfig: TAggConfig) => TAggConfig[]) | (() => TAggConfig[] | void); customLabels?: boolean; - decorateAggConfig?: () => Record; + decorateAggConfig?: () => any; postFlightRequest?: ( resp: any, aggConfigs: AggConfigs, @@ -67,7 +67,10 @@ const getFormat = (agg: AggConfig) => { return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); }; -export class AggType { +export class AggType< + TAggConfig extends AggConfig = AggConfig, + TParam extends AggParamType = AggParamType +> { /** * the unique, unchanging, name that we have assigned this aggType * @@ -162,7 +165,7 @@ export class AggType Record; + decorateAggConfig: () => any; /** * A function that needs to be called after the main request has been made * and should return an updated response @@ -196,7 +199,7 @@ export class AggType any; paramByName = (name: string) => { - return this.params.find((p: AggParam) => p.name === name); + return this.params.find((p: TParam) => p.name === name); }; /** diff --git a/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts b/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts index c151f9101d0901..ed332ea420bcca 100644 --- a/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts +++ b/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts @@ -17,29 +17,33 @@ * under the License. */ -import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../../vis'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { AggType, AggTypeConfig } from '../agg_type'; +import { AggParamType } from '../param_types/agg'; -export type IBucketAggConfig = AggConfig; +export interface IBucketAggConfig extends AggConfig { + type: InstanceType; +} -export interface BucketAggParam extends AggParamType { +export interface BucketAggParam + extends AggParamType { scriptable?: boolean; filterFieldTypes?: KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*'; } -export interface BucketAggTypeConfig - extends AggTypeConfig { +const bucketType = 'buckets'; + +interface BucketAggTypeConfig + extends AggTypeConfig> { getKey?: (bucket: any, key: any, agg: AggConfig) => any; } -const bucketType = 'buckets'; - -export class BucketAggType< - TBucketAggConfig extends IBucketAggConfig = IBucketAggConfig -> extends AggType { - getKey: (bucket: any, key: any, agg: IBucketAggConfig) => any; +export class BucketAggType extends AggType< + TBucketAggConfig, + BucketAggParam +> { + getKey: (bucket: any, key: any, agg: TBucketAggConfig) => any; type = bucketType; constructor(config: BucketAggTypeConfig) { diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts index 0399e8d3823200..ddb4102563a7c0 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts @@ -22,6 +22,7 @@ import { createFilterDateRange } from './date_range'; import { DateFormat } from '../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; +import { IBucketAggConfig } from '../_bucket_agg_type'; jest.mock('ui/new_platform'); @@ -61,7 +62,7 @@ describe('AggConfig Filters', () => { const aggConfigs = getAggConfigs(); const from = new Date('1 Feb 2015'); const to = new Date('7 Feb 2015'); - const filter = createFilterDateRange(aggConfigs.aggs[0], { + const filter = createFilterDateRange(aggConfigs.aggs[0] as IBucketAggConfig, { from: from.valueOf(), to: to.valueOf(), }); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts index 125532fe070ba8..34cf996826865f 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts @@ -18,6 +18,7 @@ */ import { createFilterFilters } from './filters'; import { AggConfigs } from '../../agg_configs'; +import { IBucketAggConfig } from '../_bucket_agg_type'; jest.mock('ui/new_platform'); @@ -56,7 +57,7 @@ describe('AggConfig Filters', () => { }; it('should return a filters filter', () => { const aggConfigs = getAggConfigs(); - const filter = createFilterFilters(aggConfigs.aggs[0], 'type:nginx'); + const filter = createFilterFilters(aggConfigs.aggs[0] as IBucketAggConfig, 'type:nginx'); expect(filter!.query.bool.must[0].query_string.query).toBe('type:nginx'); expect(filter!.meta).toHaveProperty('index', '1234'); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts index ac8e55f096fb41..d07cf84aef4d9b 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts @@ -19,6 +19,7 @@ import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; +import { IBucketAggConfig } from '../_bucket_agg_type'; import { BytesFormat } from '../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); @@ -59,7 +60,7 @@ describe('AggConfig Filters', () => { it('should return an range filter for histogram', () => { const aggConfigs = getAggConfigs(); - const filter = createFilterHistogram(aggConfigs.aggs[0], '2048'); + const filter = createFilterHistogram(aggConfigs.aggs[0] as IBucketAggConfig, '2048'); expect(filter).toHaveProperty('meta'); expect(filter.meta).toHaveProperty('index', '1234'); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts index 569735a60298de..bf6b437f17cf22 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts @@ -21,6 +21,7 @@ import { createFilterIpRange } from './ip_range'; import { AggConfigs } from '../../agg_configs'; import { IpFormat } from '../../../../../../plugins/data/public'; import { BUCKET_TYPES } from '../bucket_agg_types'; +import { IBucketAggConfig } from '../_bucket_agg_type'; jest.mock('ui/new_platform'); @@ -59,7 +60,7 @@ describe('AggConfig Filters', () => { }, ]); - const filter = createFilterIpRange(aggConfigs.aggs[0], { + const filter = createFilterIpRange(aggConfigs.aggs[0] as IBucketAggConfig, { type: 'range', from: '0.0.0.0', to: '1.1.1.1', @@ -88,7 +89,7 @@ describe('AggConfig Filters', () => { }, ]); - const filter = createFilterIpRange(aggConfigs.aggs[0], { + const filter = createFilterIpRange(aggConfigs.aggs[0] as IBucketAggConfig, { type: 'mask', mask: '67.129.65.201/27', }); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts index 803f6d97ae42d3..a513b8c7827398 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts @@ -17,7 +17,7 @@ * under the License. */ -import { CidrMask } from '../../../utils/cidr_mask'; +import { CidrMask } from '../lib/cidr_mask'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { IpRangeKey } from '../ip_range'; import { esFilters } from '../../../../../../plugins/data/public'; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts index e7344f16ba0b16..dc02b773edc42e 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts @@ -21,6 +21,7 @@ import { createFilterRange } from './range'; import { BytesFormat } from '../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; +import { IBucketAggConfig } from '../_bucket_agg_type'; jest.mock('ui/new_platform'); @@ -60,7 +61,10 @@ describe('AggConfig Filters', () => { it('should return a range filter for range agg', () => { const aggConfigs = getAggConfigs(); - const filter = createFilterRange(aggConfigs.aggs[0], { gte: 1024, lt: 2048.0 }); + const filter = createFilterRange(aggConfigs.aggs[0] as IBucketAggConfig, { + gte: 1024, + lt: 2048.0, + }); expect(filter).toHaveProperty('range'); expect(filter).toHaveProperty('meta'); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts b/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts index 42f8349d5a2a3d..86c0aa24f529a6 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts @@ -20,6 +20,7 @@ import { createFilterTerms } from './terms'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; +import { IBucketAggConfig } from '../_bucket_agg_type'; import { esFilters } from '../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); @@ -49,7 +50,11 @@ describe('AggConfig Filters', () => { { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); - const filter = createFilterTerms(aggConfigs.aggs[0], 'apache', {}) as esFilters.Filter; + const filter = createFilterTerms( + aggConfigs.aggs[0] as IBucketAggConfig, + 'apache', + {} + ) as esFilters.Filter; expect(filter).toHaveProperty('query'); expect(filter.query).toHaveProperty('match_phrase'); @@ -64,27 +69,35 @@ describe('AggConfig Filters', () => { { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); - const filterFalse = createFilterTerms(aggConfigs.aggs[0], '', {}) as esFilters.Filter; + const filterFalse = createFilterTerms( + aggConfigs.aggs[0] as IBucketAggConfig, + '', + {} + ) as esFilters.Filter; expect(filterFalse).toHaveProperty('query'); expect(filterFalse.query).toHaveProperty('match_phrase'); expect(filterFalse.query.match_phrase).toHaveProperty('field'); expect(filterFalse.query.match_phrase.field).toBeFalsy(); - const filterTrue = createFilterTerms(aggConfigs.aggs[0], '1', {}) as esFilters.Filter; + const filterTrue = createFilterTerms( + aggConfigs.aggs[0] as IBucketAggConfig, + '1', + {} + ) as esFilters.Filter; expect(filterTrue).toHaveProperty('query'); expect(filterTrue.query).toHaveProperty('match_phrase'); expect(filterTrue.query.match_phrase).toHaveProperty('field'); expect(filterTrue.query.match_phrase.field).toBeTruthy(); }); - // + it('should generate correct __missing__ filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); const filter = createFilterTerms( - aggConfigs.aggs[0], + aggConfigs.aggs[0] as IBucketAggConfig, '__missing__', {} ) as esFilters.ExistsFilter; @@ -95,13 +108,13 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('index', '1234'); expect(filter.meta).toHaveProperty('negate', true); }); - // + it('should generate correct __other__ filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); - const [filter] = createFilterTerms(aggConfigs.aggs[0], '__other__', { + const [filter] = createFilterTerms(aggConfigs.aggs[0] as IBucketAggConfig, '__other__', { terms: ['apache'], }) as esFilters.Filter[]; diff --git a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts b/src/legacy/ui/public/agg_types/buckets/date_histogram.ts index 6a87b2e88ac4cc..45122a24c81844 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/date_histogram.ts @@ -20,9 +20,10 @@ import _ from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; -import { BUCKET_TYPES } from 'ui/agg_types/buckets/bucket_agg_types'; + import { npStart } from 'ui/new_platform'; -import { BucketAggParam, BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; import { TimeIntervalParamEditor } from '../../vis/editors/default/controls/time_interval'; @@ -31,7 +32,6 @@ import { DropPartialsParamEditor } from '../../vis/editors/default/controls/drop import { ScaleMetricsParamEditor } from '../../vis/editors/default/controls/scale_metrics'; import { dateHistogramInterval } from '../../../../core_plugins/data/public'; import { writeParams } from '../agg_params'; -import { AggConfigs } from '../agg_configs'; import { isMetricAggType } from '../metrics/metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; @@ -77,7 +77,12 @@ export const dateHistogramBucketAgg = new BucketAggType = writeParams(this.params as BucketAggParam[], agg); + let output: Record = {}; + + if (this.params) { + output = writeParams(this.params, agg); + } + const field = agg.getFieldDisplayName(); return i18n.translate('common.ui.aggTypes.buckets.dateHistogramLabel', { defaultMessage: '{fieldName} per {intervalDescription}', @@ -102,7 +107,7 @@ export const dateHistogramBucketAgg = new BucketAggType, aggs: AggConfigs) { + write(agg, output, aggs) { setBounds(agg, true); agg.buckets.setInterval(getInterval(agg)); const { useNormalizedEsInterval, scaleMetricValues } = agg.params; @@ -200,7 +205,7 @@ export const dateHistogramBucketAgg = new BucketAggType) { + write(agg, output) { // If a time_zone has been set explicitly always prefer this. let tz = agg.params.time_zone; if (!tz && agg.params.field) { @@ -230,7 +235,7 @@ export const dateHistogramBucketAgg = new BucketAggType) { + write(agg, output) { const val = agg.params.extended_bounds; if (val.min != null || val.max != null) { @@ -263,6 +268,6 @@ export const dateHistogramBucketAgg = new BucketAggType undefined, - write: (agg: IBucketAggConfig, output: Record) => { + write: (agg, output) => { const field = agg.getParam('field'); let tz = agg.getParam('time_zone'); @@ -114,3 +112,16 @@ export const dateRangeBucketAgg = new BucketAggType({ }, ], }); + +export const convertDateRangeToString = ( + { from, to }: DateRangeKey, + format: (val: any) => string +) => { + if (!from) { + return 'Before ' + format(to); + } else if (!to) { + return 'After ' + format(from); + } else { + return format(from) + ' to ' + format(to); + } +}; diff --git a/src/legacy/ui/public/agg_types/buckets/filters.ts b/src/legacy/ui/public/agg_types/buckets/filters.ts index caebf2d7d974ee..6e7f4e27b9e90d 100644 --- a/src/legacy/ui/public/agg_types/buckets/filters.ts +++ b/src/legacy/ui/public/agg_types/buckets/filters.ts @@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { FiltersParamEditor, FilterValue } from '../../vis/editors/default/controls/filters'; import { createFilterFilters } from './create_filter/filters'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType } from './_bucket_agg_type'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { getQueryLog, esQuery } from '../../../../../plugins/data/public'; @@ -48,7 +48,7 @@ export const filtersBucketAgg = new BucketAggType({ name: 'filters', editorComponent: FiltersParamEditor, default: [{ input: { query: '', language: config.get('search:queryLanguage') }, label: '' }], - write(aggConfig: IBucketAggConfig, output: Record) { + write(aggConfig, output) { const inFilters: FilterValue[] = aggConfig.params.filters; if (!_.size(inFilters)) return; diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts index 0acbaf4aa02a21..8e39a464b9adf0 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts +++ b/src/legacy/ui/public/agg_types/buckets/geo_hash.ts @@ -28,8 +28,7 @@ import { PrecisionParamEditor } from '../../vis/editors/default/controls/precisi import { AggGroupNames } from '../../vis/editors/default/agg_groups'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; -// @ts-ignore -import { geoContains, scaleBounds } from '../../utils/geo_utils'; +import { geoContains, scaleBounds, GeoBoundingBox } from './lib/geo_utils'; import { BUCKET_TYPES } from './bucket_agg_types'; const config = chrome.getUiSettingsClient(); @@ -70,15 +69,15 @@ function getPrecision(val: string) { return precision; } -const isOutsideCollar = (bounds: unknown, collar: MapCollar) => +const isOutsideCollar = (bounds: GeoBoundingBox, collar: MapCollar) => bounds && collar && !geoContains(collar, bounds); const geohashGridTitle = i18n.translate('common.ui.aggTypes.buckets.geohashGridTitle', { defaultMessage: 'Geohash', }); -interface MapCollar { - zoom: unknown; +interface MapCollar extends GeoBoundingBox { + zoom?: unknown; } export interface IBucketGeoHashGridAggConfig extends IBucketAggConfig { @@ -142,17 +141,19 @@ export const geoHashBucketAgg = new BucketAggType({ }, ], getRequestAggs(agg) { - const aggs: IBucketAggConfig[] = []; + const aggs = []; const params = agg.params; if (params.isFilteredByCollar && agg.getField()) { const { mapBounds, mapZoom } = params; if (mapBounds) { - let mapCollar; + let mapCollar: MapCollar; + if ( - !agg.lastMapCollar || - agg.lastMapCollar.zoom !== mapZoom || - isOutsideCollar(mapBounds, agg.lastMapCollar) + mapBounds && + (!agg.lastMapCollar || + agg.lastMapCollar.zoom !== mapZoom || + isOutsideCollar(mapBounds, agg.lastMapCollar)) ) { mapCollar = scaleBounds(mapBounds); mapCollar.zoom = mapZoom; diff --git a/src/legacy/ui/public/agg_types/buckets/geo_tile.ts b/src/legacy/ui/public/agg_types/buckets/geo_tile.ts index 3afb35a0356900..ef71e3947566a7 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_tile.ts +++ b/src/legacy/ui/public/agg_types/buckets/geo_tile.ts @@ -19,12 +19,13 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; -import { METRIC_TYPES } from 'ui/agg_types/metrics/metric_agg_types'; -import { AggConfigOptions } from 'ui/agg_types/agg_config'; +import { AggConfigOptions } from '../agg_config'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { IBucketAggConfig } from './_bucket_agg_type'; +import { METRIC_TYPES } from '../metrics/metric_agg_types'; const geotileGridTitle = i18n.translate('common.ui.aggTypes.buckets.geotileGridTitle', { defaultMessage: 'Geotile', @@ -67,6 +68,6 @@ export const geoTileBucketAgg = new BucketAggType({ aggs.push(agg.aggConfigs.createAggConfig(aggConfig, { addToAggConfigs: false })); } - return aggs; + return aggs as IBucketAggConfig[]; }, }); diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.ts b/src/legacy/ui/public/agg_types/buckets/histogram.ts index 7bd3d565003bee..d287cbddb7834c 100644 --- a/src/legacy/ui/public/agg_types/buckets/histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/histogram.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; import { npStart } from 'ui/new_platform'; -import { BucketAggType, IBucketAggConfig, BucketAggParam } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; import { NumberIntervalParamEditor } from '../../vis/editors/default/controls/number_interval'; import { MinDocCountParamEditor } from '../../vis/editors/default/controls/min_doc_count'; @@ -130,7 +130,7 @@ export const histogramBucketAgg = new BucketAggType({ ); }); }, - write(aggConfig: IBucketHistogramAggConfig, output: Record) { + write(aggConfig, output) { let interval = parseFloat(aggConfig.params.interval); if (interval <= 0) { interval = 1; @@ -171,12 +171,12 @@ export const histogramBucketAgg = new BucketAggType({ output.params.interval = interval; }, - } as BucketAggParam, + }, { name: 'min_doc_count', default: false, editorComponent: MinDocCountParamEditor, - write(aggConfig: IBucketAggConfig, output: Record) { + write(aggConfig, output) { if (aggConfig.params.min_doc_count) { output.params.min_doc_count = 0; } else { @@ -197,7 +197,7 @@ export const histogramBucketAgg = new BucketAggType({ max: '', }, editorComponent: ExtendedBoundsParamEditor, - write(aggConfig: IBucketAggConfig, output: Record) { + write(aggConfig, output) { const { min, max } = aggConfig.params.extended_bounds; if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.ts b/src/legacy/ui/public/agg_types/buckets/ip_range.ts index 35155a482734ce..609cd8adb5c39e 100644 --- a/src/legacy/ui/public/agg_types/buckets/ip_range.ts +++ b/src/legacy/ui/public/agg_types/buckets/ip_range.ts @@ -20,10 +20,9 @@ import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType } from './_bucket_agg_type'; import { IpRangeTypeParamEditor } from '../../vis/editors/default/controls/ip_range_type'; import { IpRangesParamEditor } from '../../vis/editors/default/controls/ip_ranges'; -import { ipRange } from '../../utils/ip_range'; import { BUCKET_TYPES } from './bucket_agg_types'; // @ts-ignore @@ -59,7 +58,7 @@ export const ipRangeBucketAgg = new BucketAggType({ fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.IP) ); const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { - return ipRange.toString(range, formatter); + return convertIPRangeToString(range, formatter); }); return new IpRangeFormat(); }, @@ -93,7 +92,7 @@ export const ipRangeBucketAgg = new BucketAggType({ mask: [{ mask: '0.0.0.0/1' }, { mask: '128.0.0.0/2' }], }, editorComponent: IpRangesParamEditor, - write(aggConfig: IBucketAggConfig, output: Record) { + write(aggConfig, output) { const ipRangeType = aggConfig.params.ipRangeType; let ranges = aggConfig.params.ranges[ipRangeType]; @@ -106,3 +105,13 @@ export const ipRangeBucketAgg = new BucketAggType({ }, ], }); + +export const convertIPRangeToString = (range: IpRangeKey, format: (val: any) => string) => { + if (range.type === 'mask') { + return format(range.mask); + } + const from = range.from ? format(range.from) : '-Infinity'; + const to = range.to ? format(range.to) : 'Infinity'; + + return `${from} to ${to}`; +}; diff --git a/src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.test.ts b/src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.test.ts new file mode 100644 index 00000000000000..01dd3ddf1b8742 --- /dev/null +++ b/src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { CidrMask } from './cidr_mask'; + +describe('CidrMask', () => { + test('should throw errors with invalid CIDR masks', () => { + expect( + () => + // @ts-ignore + new CidrMask() + ).toThrowError(); + + expect(() => new CidrMask('')).toThrowError(); + expect(() => new CidrMask('hello, world')).toThrowError(); + expect(() => new CidrMask('0.0.0.0')).toThrowError(); + expect(() => new CidrMask('0.0.0.0/0')).toThrowError(); + expect(() => new CidrMask('0.0.0.0/33')).toThrowError(); + expect(() => new CidrMask('256.0.0.0/32')).toThrowError(); + expect(() => new CidrMask('0.0.0.0/32/32')).toThrowError(); + expect(() => new CidrMask('1.2.3/1')).toThrowError(); + expect(() => new CidrMask('0.0.0.0/123d')).toThrowError(); + }); + + test('should correctly grab IP address and prefix length', () => { + let mask = new CidrMask('0.0.0.0/1'); + expect(mask.initialAddress.toString()).toBe('0.0.0.0'); + expect(mask.prefixLength).toBe(1); + + mask = new CidrMask('128.0.0.1/31'); + expect(mask.initialAddress.toString()).toBe('128.0.0.1'); + expect(mask.prefixLength).toBe(31); + }); + + test('should calculate a range of IP addresses', () => { + let mask = new CidrMask('0.0.0.0/1'); + let range = mask.getRange(); + expect(range.from.toString()).toBe('0.0.0.0'); + expect(range.to.toString()).toBe('127.255.255.255'); + + mask = new CidrMask('1.2.3.4/2'); + range = mask.getRange(); + expect(range.from.toString()).toBe('0.0.0.0'); + expect(range.to.toString()).toBe('63.255.255.255'); + + mask = new CidrMask('67.129.65.201/27'); + range = mask.getRange(); + expect(range.from.toString()).toBe('67.129.65.192'); + expect(range.to.toString()).toBe('67.129.65.223'); + }); + + test('toString()', () => { + let mask = new CidrMask('.../1'); + expect(mask.toString()).toBe('0.0.0.0/1'); + + mask = new CidrMask('128.0.0.1/31'); + expect(mask.toString()).toBe('128.0.0.1/31'); + }); +}); diff --git a/src/legacy/ui/public/utils/cidr_mask.ts b/src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.ts similarity index 96% rename from src/legacy/ui/public/utils/cidr_mask.ts rename to src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.ts index 249c60dedfebf6..aadbbc8c822768 100644 --- a/src/legacy/ui/public/utils/cidr_mask.ts +++ b/src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.ts @@ -17,7 +17,8 @@ * under the License. */ -import { Ipv4Address } from '../../../../plugins/kibana_utils/public'; +import { Ipv4Address } from '../../../../../../plugins/kibana_utils/public'; + const NUM_BITS = 32; function throwError(mask: string) { diff --git a/src/legacy/ui/public/utils/geo_utils.js b/src/legacy/ui/public/agg_types/buckets/lib/geo_utils.ts similarity index 55% rename from src/legacy/ui/public/utils/geo_utils.js rename to src/legacy/ui/public/agg_types/buckets/lib/geo_utils.ts index 44b7670d16c111..639b6d1fbb03e3 100644 --- a/src/legacy/ui/public/utils/geo_utils.js +++ b/src/legacy/ui/public/agg_types/buckets/lib/geo_utils.ts @@ -19,46 +19,57 @@ import _ from 'lodash'; -export function geoContains(collar, bounds) { - //test if bounds top_left is outside collar - if(bounds.top_left.lat > collar.top_left.lat || bounds.top_left.lon < collar.top_left.lon) { +interface GeoBoundingBoxCoordinate { + lat: number; + lon: number; +} + +export interface GeoBoundingBox { + top_left: GeoBoundingBoxCoordinate; + bottom_right: GeoBoundingBoxCoordinate; +} + +export function geoContains(collar: GeoBoundingBox, bounds: GeoBoundingBox) { + // test if bounds top_left is outside collar + if (bounds.top_left.lat > collar.top_left.lat || bounds.top_left.lon < collar.top_left.lon) { return false; } - //test if bounds bottom_right is outside collar - if(bounds.bottom_right.lat < collar.bottom_right.lat || bounds.bottom_right.lon > collar.bottom_right.lon) { + // test if bounds bottom_right is outside collar + if ( + bounds.bottom_right.lat < collar.bottom_right.lat || + bounds.bottom_right.lon > collar.bottom_right.lon + ) { return false; } - //both corners are inside collar so collar contains bounds + // both corners are inside collar so collar contains bounds return true; } -export function scaleBounds(bounds) { - if (!bounds) return; - - const scale = .5; // scale bounds by 50% +export function scaleBounds(bounds: GeoBoundingBox): GeoBoundingBox { + const scale = 0.5; // scale bounds by 50% const topLeft = bounds.top_left; const bottomRight = bounds.bottom_right; let latDiff = _.round(Math.abs(topLeft.lat - bottomRight.lat), 5); const lonDiff = _.round(Math.abs(bottomRight.lon - topLeft.lon), 5); - //map height can be zero when vis is first created - if(latDiff === 0) latDiff = lonDiff; + // map height can be zero when vis is first created + if (latDiff === 0) latDiff = lonDiff; const latDelta = latDiff * scale; let topLeftLat = _.round(topLeft.lat, 5) + latDelta; - if(topLeftLat > 90) topLeftLat = 90; + if (topLeftLat > 90) topLeftLat = 90; let bottomRightLat = _.round(bottomRight.lat, 5) - latDelta; - if(bottomRightLat < -90) bottomRightLat = -90; + if (bottomRightLat < -90) bottomRightLat = -90; const lonDelta = lonDiff * scale; let topLeftLon = _.round(topLeft.lon, 5) - lonDelta; - if(topLeftLon < -180) topLeftLon = -180; + if (topLeftLon < -180) topLeftLon = -180; let bottomRightLon = _.round(bottomRight.lon, 5) + lonDelta; - if(bottomRightLon > 180) bottomRightLon = 180; + if (bottomRightLon > 180) bottomRightLon = 180; return { - 'top_left': { lat: topLeftLat, lon: topLeftLon }, - 'bottom_right': { lat: bottomRightLat, lon: bottomRightLon } + top_left: { lat: topLeftLat, lon: topLeftLon }, + bottom_right: { lat: bottomRightLat, lon: bottomRightLon }, }; } diff --git a/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts b/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts index e4527ff87f48cc..77e84e044de55a 100644 --- a/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts +++ b/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts @@ -19,9 +19,10 @@ import { isString, isObject } from 'lodash'; import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type'; +import { AggConfig } from '../agg_config'; export const isType = (type: string) => { - return (agg: IBucketAggConfig): boolean => { + return (agg: AggConfig): boolean => { const field = agg.params.field; return field && field.type === type; @@ -31,7 +32,7 @@ export const isType = (type: string) => { export const isStringType = isType('string'); export const migrateIncludeExcludeFormat = { - serialize(this: BucketAggParam, value: any, agg: IBucketAggConfig) { + serialize(this: BucketAggParam, value: any, agg: IBucketAggConfig) { if (this.shouldShow && !this.shouldShow(agg)) return; if (!value || isString(value)) return value; else return value.pattern; @@ -49,4 +50,4 @@ export const migrateIncludeExcludeFormat = { output.params[this.name] = value; } }, -}; +} as Partial>; diff --git a/src/legacy/ui/public/agg_types/buckets/range.ts b/src/legacy/ui/public/agg_types/buckets/range.ts index 89529442b24a67..24757a607e0052 100644 --- a/src/legacy/ui/public/agg_types/buckets/range.ts +++ b/src/legacy/ui/public/agg_types/buckets/range.ts @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; -import { IBucketAggConfig } from './_bucket_agg_type'; import { BucketAggType } from './_bucket_agg_type'; import { FieldFormat, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; import { RangeKey } from './range_key'; @@ -102,7 +101,7 @@ export const rangeBucketAgg = new BucketAggType({ { from: 1000, to: 2000 }, ], editorComponent: RangesEditor, - write(aggConfig: IBucketAggConfig, output: Record) { + write(aggConfig, output) { output.params.ranges = aggConfig.params.ranges; output.params.keyed = true; }, diff --git a/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts b/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts index 454f1bf70a790b..8db9226e41eec7 100644 --- a/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts +++ b/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts @@ -20,6 +20,7 @@ import { AggConfigs } from '../index'; import { BUCKET_TYPES } from './bucket_agg_types'; import { significantTermsBucketAgg } from './significant_terms'; +import { IBucketAggConfig } from './_bucket_agg_type'; jest.mock('ui/new_platform'); @@ -71,7 +72,7 @@ describe('Significant Terms Agg', () => { name: 'FIELD', }, }); - const label = significantTermsBucketAgg.makeLabel(aggConfigs.aggs[0]); + const label = significantTermsBucketAgg.makeLabel(aggConfigs.aggs[0] as IBucketAggConfig); expect(label).toBe('Top SIZE unusual terms in FIELD'); }); diff --git a/src/legacy/ui/public/agg_types/buckets/significant_terms.ts b/src/legacy/ui/public/agg_types/buckets/significant_terms.ts index 65c73e5f9b7dd3..128fd9e83e6fd9 100644 --- a/src/legacy/ui/public/agg_types/buckets/significant_terms.ts +++ b/src/legacy/ui/public/agg_types/buckets/significant_terms.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { SizeParamEditor } from '../../vis/editors/default/controls/size'; -import { BucketAggType, BucketAggParam } from './_bucket_agg_type'; +import { BucketAggType } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -63,7 +63,7 @@ export const significantTermsBucketAgg = new BucketAggType({ advanced: true, shouldShow: isStringType, ...migrateIncludeExcludeFormat, - } as BucketAggParam, + }, { name: 'include', displayName: i18n.translate('common.ui.aggTypes.buckets.significantTerms.includeLabel', { @@ -73,6 +73,6 @@ export const significantTermsBucketAgg = new BucketAggType({ advanced: true, shouldShow: isStringType, ...migrateIncludeExcludeFormat, - } as BucketAggParam, + }, ], }); diff --git a/src/legacy/ui/public/agg_types/buckets/terms.ts b/src/legacy/ui/public/agg_types/buckets/terms.ts index 6ce0b9ce38ad34..e38f7ca4cc038e 100644 --- a/src/legacy/ui/public/agg_types/buckets/terms.ts +++ b/src/legacy/ui/public/agg_types/buckets/terms.ts @@ -21,9 +21,8 @@ import chrome from 'ui/chrome'; import { noop } from 'lodash'; import { i18n } from '@kbn/i18n'; import { SearchSource, getRequestInspectorStats, getResponseInspectorStats } from '../../courier'; -import { BucketAggType, BucketAggParam } from './_bucket_agg_type'; +import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { AggConfigOptions } from '../agg_config'; import { IBucketAggConfig } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { wrapWithInlineComp } from './inline_comp_wrapper'; @@ -158,18 +157,21 @@ export const termsBucketAgg = new BucketAggType({ type: 'agg', default: null, editorComponent: OrderAggParamEditor, - makeAgg(termsAgg: IBucketAggConfig, state: AggConfigOptions) { + makeAgg(termsAgg, state) { state = state || {}; state.schema = orderAggSchema; - const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { addToAggConfigs: false }); + const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { + addToAggConfigs: false, + }); orderAgg.id = termsAgg.id + '-orderAgg'; + return orderAgg; }, - write(agg: IBucketAggConfig, output: Record, aggs: AggConfigs) { + write(agg, output, aggs) { const dir = agg.params.order.value; const order: Record = (output.params.order = {}); - let orderAgg = agg.params.orderAgg || aggs.getResponseAggById(agg.params.orderBy); + let orderAgg = agg.params.orderAgg || aggs!.getResponseAggById(agg.params.orderBy); // TODO: This works around an Elasticsearch bug the always casts terms agg scripts to strings // thus causing issues with filtering. This probably causes other issues since float might not @@ -194,7 +196,8 @@ export const termsBucketAgg = new BucketAggType({ } const orderAggId = orderAgg.id; - if (orderAgg.parentId) { + + if (orderAgg.parentId && aggs) { orderAgg = aggs.byId(orderAgg.parentId); } @@ -243,9 +246,9 @@ export const termsBucketAgg = new BucketAggType({ displayName: i18n.translate('common.ui.aggTypes.otherBucket.labelForOtherBucketLabel', { defaultMessage: 'Label for other bucket', }), - shouldShow: (agg: IBucketAggConfig) => agg.getParam('otherBucket'), + shouldShow: agg => agg.getParam('otherBucket'), write: noop, - } as BucketAggParam, + }, { name: 'missingBucket', default: false, @@ -263,7 +266,7 @@ export const termsBucketAgg = new BucketAggType({ displayName: i18n.translate('common.ui.aggTypes.otherBucket.labelForMissingValuesLabel', { defaultMessage: 'Label for missing values', }), - shouldShow: (agg: IBucketAggConfig) => agg.getParam('missingBucket'), + shouldShow: agg => agg.getParam('missingBucket'), write: noop, }, { @@ -286,5 +289,5 @@ export const termsBucketAgg = new BucketAggType({ shouldShow: isStringType, ...migrateIncludeExcludeFormat, }, - ] as BucketAggParam[], + ], }); diff --git a/src/legacy/ui/public/agg_types/metrics/lib/nested_agg_helpers.ts b/src/legacy/ui/public/agg_types/metrics/lib/nested_agg_helpers.ts index dac36ac8a89ca7..a7bfb7b82b97f6 100644 --- a/src/legacy/ui/public/agg_types/metrics/lib/nested_agg_helpers.ts +++ b/src/legacy/ui/public/agg_types/metrics/lib/nested_agg_helpers.ts @@ -44,7 +44,7 @@ export const forwardModifyAggConfigOnSearchRequestStart = (paramName: string) => const nestedAggConfig = aggConfig.getParam(paramName); if (nestedAggConfig && nestedAggConfig.type && nestedAggConfig.type.params) { - nestedAggConfig.type.params.forEach((param: MetricAggParam) => { + nestedAggConfig.type.params.forEach((param: MetricAggParam) => { // Check if this parameter of the nested aggConfig has a modifyAggConfigOnSearchRequestStart // function, that needs to be called. if (param.modifyAggConfigOnSearchRequestStart) { diff --git a/src/legacy/ui/public/utils/__tests__/ordinal_suffix.js b/src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.test.ts similarity index 76% rename from src/legacy/ui/public/utils/__tests__/ordinal_suffix.js rename to src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.test.ts index dae12d41cfb5b9..18ee6b4de32044 100644 --- a/src/legacy/ui/public/utils/__tests__/ordinal_suffix.js +++ b/src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.test.ts @@ -17,11 +17,10 @@ * under the License. */ -import _ from 'lodash'; -import { ordinalSuffix } from '../ordinal_suffix'; -import expect from '@kbn/expect'; +import { forOwn } from 'lodash'; +import { ordinalSuffix } from './ordinal_suffix'; -describe('ordinal suffix util', function () { +describe('ordinal suffix util', () => { const checks = { 1: 'st', 2: 'nd', @@ -52,19 +51,19 @@ describe('ordinal suffix util', function () { 27: 'th', 28: 'th', 29: 'th', - 30: 'th' + 30: 'th', }; - _.forOwn(checks, function (expected, num) { + forOwn(checks, (expected, num: any) => { const int = parseInt(num, 10); const float = int + Math.random(); - it('knowns ' + int, function () { - expect(ordinalSuffix(num)).to.be(num + '' + expected); + it('knowns ' + int, () => { + expect(ordinalSuffix(num)).toBe(num + '' + expected); }); - it('knows ' + float, function () { - expect(ordinalSuffix(num)).to.be(num + '' + expected); + it('knows ' + float, () => { + expect(ordinalSuffix(num)).toBe(num + '' + expected); }); }); }); diff --git a/src/legacy/ui/public/utils/ordinal_suffix.js b/src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.ts similarity index 93% rename from src/legacy/ui/public/utils/ordinal_suffix.js rename to src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.ts index 64fb29c8ae534e..21903995ebb2ff 100644 --- a/src/legacy/ui/public/utils/ordinal_suffix.js +++ b/src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.ts @@ -18,11 +18,11 @@ */ // adopted from http://stackoverflow.com/questions/3109978/php-display-number-with-ordinal-suffix -export function ordinalSuffix(num) { +export function ordinalSuffix(num: any): string { return num + '' + suffix(num); } -function suffix(num) { +function suffix(num: any): string { const int = Math.floor(parseFloat(num)); const hunth = int % 100; diff --git a/src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.ts b/src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.ts index e4726c62428cba..d177a62649d134 100644 --- a/src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.ts +++ b/src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.ts @@ -22,7 +22,7 @@ import { noop } from 'lodash'; import { MetricAggParamEditor } from '../../../vis/editors/default/controls/metric_agg'; import { SubAggParamEditor } from '../../../vis/editors/default/controls/sub_agg'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; -import { IMetricAggConfig } from '../metric_agg_type'; +import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; import { parentPipelineAggWriter } from './parent_pipeline_agg_writer'; // @ts-ignore @@ -74,7 +74,7 @@ export const parentPipelineAggHelper = { name: 'customMetric', editorComponent: SubAggParamEditor, type: 'agg', - makeAgg(termsAgg: IMetricAggConfig, state: any) { + makeAgg(termsAgg, state: any) { state = state || { type: 'count' }; state.schema = metricAggSchema; @@ -93,7 +93,7 @@ export const parentPipelineAggHelper = { name: 'buckets_path', write: noop, }, - ]; + ] as Array>; }, getFormat(agg: IMetricAggConfig) { diff --git a/src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.ts b/src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.ts index 1df771727f6cc8..e75ebf366a27e6 100644 --- a/src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.ts +++ b/src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.ts @@ -22,7 +22,7 @@ import { siblingPipelineAggWriter } from './sibling_pipeline_agg_writer'; import { SubMetricParamEditor } from '../../../vis/editors/default/controls/sub_metric'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; -import { IMetricAggConfig } from '../metric_agg_type'; +import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; // @ts-ignore import { Schemas } from '../../../vis/editors/default/schemas'; @@ -110,7 +110,7 @@ const siblingPipelineAggHelper = { ), write: siblingPipelineAggWriter, }, - ]; + ] as Array>; }, getFormat(agg: IMetricAggConfig) { diff --git a/src/legacy/ui/public/agg_types/metrics/median.ts b/src/legacy/ui/public/agg_types/metrics/median.ts index 8797bed5105c52..5792d4a7c2ba3b 100644 --- a/src/legacy/ui/public/agg_types/metrics/median.ts +++ b/src/legacy/ui/public/agg_types/metrics/median.ts @@ -17,7 +17,7 @@ * under the License. */ import { i18n } from '@kbn/i18n'; -import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; +import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; // @ts-ignore @@ -49,7 +49,7 @@ export const medianMetricAgg = new MetricAggType({ default: [50], }, { - write(agg: IMetricAggConfig, output: Record) { + write(agg, output) { output.params.keyed = false; }, }, diff --git a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts b/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts index c24dda180ea94d..29499c5be84b84 100644 --- a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts +++ b/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts @@ -25,24 +25,28 @@ import { AggConfig } from '../agg_config'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; -export type IMetricAggConfig = AggConfig; - -export interface MetricAggTypeConfig - extends AggTypeConfig { - isScalable?: () => boolean; - subtype?: string; +export interface IMetricAggConfig extends AggConfig { + type: InstanceType; } -export interface MetricAggParam extends AggParamType { +export interface MetricAggParam + extends AggParamType { filterFieldTypes?: KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*'; onlyAggregatable?: boolean; } const metricType = 'metrics'; -export class MetricAggType< - TMetricAggConfig extends IMetricAggConfig = IMetricAggConfig -> extends AggType { +interface MetricAggTypeConfig + extends AggTypeConfig> { + isScalable?: () => boolean; + subtype?: string; +} + +export class MetricAggType extends AggType< + TMetricAggConfig, + MetricAggParam +> { subtype: string; isScalable: () => boolean; type = metricType; diff --git a/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts b/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts index 7c7a2a68cd7c55..0adf41a0420a0a 100644 --- a/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts +++ b/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts @@ -111,7 +111,7 @@ describe('parent pipeline aggs', function() { // Grab the aggConfig off the vis (we don't actually use the vis for anything else) metricAgg = metric.provider; - aggConfig = aggConfigs.aggs[1]; + aggConfig = aggConfigs.aggs[1] as IMetricAggConfig; aggDsl = aggConfig.toDsl(aggConfigs); }; diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts index ead5122278b5a2..1a1d5bf04309fd 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; import { PercentileRanksEditor } from '../../vis/editors/default/controls/percentile_ranks'; -import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; +import { MetricAggType } from './metric_agg_type'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; @@ -72,7 +72,7 @@ export const percentileRanksMetricAgg = new MetricAggType) { + write(agg, output) { output.params.keyed = false; }, }, diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.ts b/src/legacy/ui/public/agg_types/metrics/percentiles.ts index 1a3606d6779516..9b8205425b4a46 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentiles.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentiles.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; +import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; @@ -28,7 +28,7 @@ import { getPercentileValue } from './percentiles_get_value'; import { PercentilesEditor } from '../../vis/editors/default/controls/percentiles'; // @ts-ignore -import { ordinalSuffix } from '../../utils/ordinal_suffix'; +import { ordinalSuffix } from './lib/ordinal_suffix'; export type IPercentileAggConfig = IResponseAggConfig; @@ -67,7 +67,7 @@ export const percentilesMetricAgg = new MetricAggType({ default: [1, 5, 25, 50, 75, 95, 99], }, { - write(agg: IMetricAggConfig, output: Record) { + write(agg, output) { output.params.keyed = false; }, }, diff --git a/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts b/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts index e038936de07d20..60165790da5455 100644 --- a/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts +++ b/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts @@ -107,7 +107,7 @@ describe('sibling pipeline aggs', () => { // Grab the aggConfig off the vis (we don't actually use the vis for anything else) metricAgg = metric.provider; - aggConfig = aggConfigs.aggs[1]; + aggConfig = aggConfigs.aggs[1] as IMetricAggConfig; aggDsl = aggConfig.toDsl(aggConfigs); }; diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts b/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts index 051174c388c1b8..3e861c052d3671 100644 --- a/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts +++ b/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts @@ -85,7 +85,7 @@ describe('Top hit metric', () => { ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) - aggConfig = aggConfigs.aggs[0]; + aggConfig = aggConfigs.aggs[0] as IMetricAggConfig; aggDsl = aggConfig.toDsl(aggConfigs); }; diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.ts b/src/legacy/ui/public/agg_types/metrics/top_hit.ts index 49c4a7951fab91..4b07c997f11e0a 100644 --- a/src/legacy/ui/public/agg_types/metrics/top_hit.ts +++ b/src/legacy/ui/public/agg_types/metrics/top_hit.ts @@ -38,7 +38,7 @@ const isNumericFieldSelected = (agg: IMetricAggConfig) => { return field && field.type && field.type === KBN_FIELD_TYPES.NUMBER; }; -aggTypeFieldFilters.addFilter((field, aggConfig: IMetricAggConfig) => { +aggTypeFieldFilters.addFilter((field, aggConfig) => { if ( aggConfig.type.name !== METRIC_TYPES.TOP_HITS || _.get(aggConfig.schema, 'aggSettings.top_hits.allowStrings', false) @@ -82,7 +82,7 @@ export const topHitMetricAgg = new MetricAggType({ editorComponent: TopFieldParamEditor, onlyAggregatable: false, filterFieldTypes: '*', - write(agg: IMetricAggConfig, output: Record) { + write(agg, output) { const field = agg.getParam('field'); output.params = {}; @@ -196,7 +196,7 @@ export const topHitMetricAgg = new MetricAggType({ value: 'asc', }, ], - write(agg: IMetricAggConfig, output: Record) { + write(agg, output) { const sortField = agg.params.sortField; const sortOrder = agg.params.sortOrder; diff --git a/src/legacy/ui/public/agg_types/param_types/agg.ts b/src/legacy/ui/public/agg_types/param_types/agg.ts index 71b2d41bdb773f..0a83805c8c44cc 100644 --- a/src/legacy/ui/public/agg_types/param_types/agg.ts +++ b/src/legacy/ui/public/agg_types/param_types/agg.ts @@ -20,26 +20,28 @@ import { AggConfig } from '../agg_config'; import { BaseParamType } from './base'; -export class AggParamType extends BaseParamType { - makeAgg: (agg: AggConfig, state?: any) => AggConfig; +export class AggParamType extends BaseParamType< + TAggConfig +> { + makeAgg: (agg: TAggConfig, state?: any) => TAggConfig; constructor(config: Record) { super(config); if (!config.write) { - this.write = (aggConfig: AggConfig, output: Record) => { + this.write = (aggConfig: TAggConfig, output: Record) => { if (aggConfig.params[this.name] && aggConfig.params[this.name].length) { output.params[this.name] = aggConfig.params[this.name]; } }; } if (!config.serialize) { - this.serialize = (agg: AggConfig) => { + this.serialize = (agg: TAggConfig) => { return agg.toJSON(); }; } if (!config.deserialize) { - this.deserialize = (state: unknown, agg?: AggConfig): AggConfig => { + this.deserialize = (state: unknown, agg?: TAggConfig): TAggConfig => { if (!agg) { throw new Error('aggConfig was not provided to AggParamType deserialize function'); } diff --git a/src/legacy/ui/public/agg_types/param_types/base.ts b/src/legacy/ui/public/agg_types/param_types/base.ts index 61ef73fb62e8a3..bc8fd30e6324e3 100644 --- a/src/legacy/ui/public/agg_types/param_types/base.ts +++ b/src/legacy/ui/public/agg_types/param_types/base.ts @@ -17,12 +17,11 @@ * under the License. */ -import { AggParam } from '../'; import { AggConfigs } from '../agg_configs'; import { AggConfig } from '../../vis'; import { SearchSourceContract, FetchOptions } from '../../courier/types'; -export class BaseParamType implements AggParam { +export class BaseParamType { name: string; type: string; displayName: string; @@ -31,18 +30,18 @@ export class BaseParamType implements AggParam { editorComponent: any = null; default: any; write: ( - aggConfig: AggConfig, + aggConfig: TAggConfig, output: Record, aggConfigs?: AggConfigs, locals?: Record ) => void; - serialize: (value: any, aggConfig?: AggConfig) => any; - deserialize: (value: any, aggConfig?: AggConfig) => any; + serialize: (value: any, aggConfig?: TAggConfig) => any; + deserialize: (value: any, aggConfig?: TAggConfig) => any; options: any[]; valueType?: any; - onChange?(agg: AggConfig): void; - shouldShow?(agg: AggConfig): boolean; + onChange?(agg: TAggConfig): void; + shouldShow?(agg: TAggConfig): boolean; /** * A function that will be called before an aggConfig is serialized and sent to ES. @@ -54,7 +53,7 @@ export class BaseParamType implements AggParam { * @returns {Promise|undefined} */ modifyAggConfigOnSearchRequestStart: ( - aggConfig: AggConfig, + aggConfig: TAggConfig, searchSource?: SearchSourceContract, options?: FetchOptions ) => void; @@ -70,7 +69,7 @@ export class BaseParamType implements AggParam { this.default = config.default; this.editorComponent = config.editorComponent; - const defaultWrite = (aggConfig: AggConfig, output: Record) => { + const defaultWrite = (aggConfig: TAggConfig, output: Record) => { if (aggConfig.params[this.name]) { output.params[this.name] = aggConfig.params[this.name] || this.default; } diff --git a/src/legacy/ui/public/utils/__tests__/cidr_mask.ts b/src/legacy/ui/public/utils/__tests__/cidr_mask.ts deleted file mode 100644 index 5277344448bd85..00000000000000 --- a/src/legacy/ui/public/utils/__tests__/cidr_mask.ts +++ /dev/null @@ -1,84 +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 expect from '@kbn/expect'; -import { CidrMask } from '../cidr_mask'; - -describe('CidrMask', () => { - it('should throw errors with invalid CIDR masks', () => { - expect( - () => - // @ts-ignore - new CidrMask() - ).to.throwError(); - - expect(() => new CidrMask('')).to.throwError(); - - expect(() => new CidrMask('hello, world')).to.throwError(); - - expect(() => new CidrMask('0.0.0.0')).to.throwError(); - - expect(() => new CidrMask('0.0.0.0/0')).to.throwError(); - - expect(() => new CidrMask('0.0.0.0/33')).to.throwError(); - - expect(() => new CidrMask('256.0.0.0/32')).to.throwError(); - - expect(() => new CidrMask('0.0.0.0/32/32')).to.throwError(); - - expect(() => new CidrMask('1.2.3/1')).to.throwError(); - - expect(() => new CidrMask('0.0.0.0/123d')).to.throwError(); - }); - - it('should correctly grab IP address and prefix length', () => { - let mask = new CidrMask('0.0.0.0/1'); - expect(mask.initialAddress.toString()).to.be('0.0.0.0'); - expect(mask.prefixLength).to.be(1); - - mask = new CidrMask('128.0.0.1/31'); - expect(mask.initialAddress.toString()).to.be('128.0.0.1'); - expect(mask.prefixLength).to.be(31); - }); - - it('should calculate a range of IP addresses', () => { - let mask = new CidrMask('0.0.0.0/1'); - let range = mask.getRange(); - expect(range.from.toString()).to.be('0.0.0.0'); - expect(range.to.toString()).to.be('127.255.255.255'); - - mask = new CidrMask('1.2.3.4/2'); - range = mask.getRange(); - expect(range.from.toString()).to.be('0.0.0.0'); - expect(range.to.toString()).to.be('63.255.255.255'); - - mask = new CidrMask('67.129.65.201/27'); - range = mask.getRange(); - expect(range.from.toString()).to.be('67.129.65.192'); - expect(range.to.toString()).to.be('67.129.65.223'); - }); - - it('toString()', () => { - let mask = new CidrMask('.../1'); - expect(mask.toString()).to.be('0.0.0.0/1'); - - mask = new CidrMask('128.0.0.1/31'); - expect(mask.toString()).to.be('128.0.0.1/31'); - }); -}); diff --git a/src/legacy/ui/public/utils/date_range.ts b/src/legacy/ui/public/utils/date_range.ts deleted file mode 100644 index ca44183b8d68be..00000000000000 --- a/src/legacy/ui/public/utils/date_range.ts +++ /dev/null @@ -1,32 +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 { DateRangeKey } from '../agg_types/buckets/date_range'; - -export const dateRange = { - toString({ from, to }: DateRangeKey, format: (val: any) => string) { - if (!from) { - return 'Before ' + format(to); - } else if (!to) { - return 'After ' + format(from); - } else { - return format(from) + ' to ' + format(to); - } - }, -}; diff --git a/src/legacy/ui/public/utils/ip_range.ts b/src/legacy/ui/public/utils/ip_range.ts deleted file mode 100644 index 45ce21709d68c2..00000000000000 --- a/src/legacy/ui/public/utils/ip_range.ts +++ /dev/null @@ -1,31 +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 { IpRangeKey } from '../agg_types/buckets/ip_range'; - -export const ipRange = { - toString(range: IpRangeKey, format: (val: any) => string) { - if (range.type === 'mask') { - return format(range.mask); - } - const from = range.from ? format(range.from) : '-Infinity'; - const to = range.to ? format(range.to) : 'Infinity'; - return `${from} to ${to}`; - }, -}; diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/mask_list.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/mask_list.tsx index 7d964204ff90ce..b48f07512332ed 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/mask_list.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/components/mask_list.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { EuiFieldText, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { CidrMask } from '../../../../../utils/cidr_mask'; +import { CidrMask } from '../../../../../agg_types/buckets/lib/cidr_mask'; import { InputList, InputListConfig, InputObject, InputModel, InputItem } from './input_list'; const EMPTY_STRING = ''; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index 377e2cd97b72e8..d754c1d3955955 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -26,10 +26,8 @@ import { SerializedFieldFormat } from 'src/plugins/expressions/public'; import { IFieldFormatId, FieldFormat } from '../../../../../../plugins/data/public'; import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns'; -import { dateRange } from '../../../utils/date_range'; -import { ipRange } from '../../../utils/ip_range'; -import { DateRangeKey } from '../../../agg_types/buckets/date_range'; -import { IpRangeKey } from '../../../agg_types/buckets/ip_range'; +import { DateRangeKey, convertDateRangeToString } from '../../../agg_types/buckets/date_range'; +import { IpRangeKey, convertIPRangeToString } from '../../../agg_types/buckets/ip_range'; interface TermsFieldFormatParams { otherBucketLabel: string; @@ -120,14 +118,14 @@ export const getFormat: FormatFactory = mapping => { const nestedFormatter = mapping.params as SerializedFieldFormat; const DateRangeFormat = FieldFormat.from((range: DateRangeKey) => { const format = getFieldFormat(nestedFormatter.id, nestedFormatter.params); - return dateRange.toString(range, format.convert.bind(format)); + return convertDateRangeToString(range, format.convert.bind(format)); }); return new DateRangeFormat(); } else if (id === 'ip_range') { const nestedFormatter = mapping.params as SerializedFieldFormat; const IpRangeFormat = FieldFormat.from((range: IpRangeKey) => { const format = getFieldFormat(nestedFormatter.id, nestedFormatter.params); - return ipRange.toString(range, format.convert.bind(format)); + return convertIPRangeToString(range, format.convert.bind(format)); }); return new IpRangeFormat(); } else if (isTermsFieldFormat(mapping) && mapping.params) { diff --git a/src/plugins/data/server/search/create_api.ts b/src/plugins/data/server/search/create_api.ts index 2a874869526d73..e1613103ac3998 100644 --- a/src/plugins/data/server/search/create_api.ts +++ b/src/plugins/data/server/search/create_api.ts @@ -38,7 +38,7 @@ export function createApi({ } // Give providers access to other search strategies by injecting this function const strategy = await strategyProvider(caller, api.search); - return strategy.search(request); + return strategy.search(request, options); }, }; return api; diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index 7b725a47aa13bd..99ccb4dcbebabf 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { coreMock } from '../../../../../core/server/mocks'; +import { coreMock, pluginInitializerContextConfigMock } from '../../../../../core/server/mocks'; import { esSearchStrategyProvider } from './es_search_strategy'; describe('ES search strategy', () => { @@ -31,6 +31,7 @@ describe('ES search strategy', () => { }, }); const mockSearch = jest.fn(); + const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; beforeEach(() => { mockApiCaller.mockClear(); @@ -41,6 +42,7 @@ describe('ES search strategy', () => { const esSearch = esSearchStrategyProvider( { core: mockCoreSetup, + config$: mockConfig$, }, mockApiCaller, mockSearch @@ -49,11 +51,12 @@ describe('ES search strategy', () => { expect(typeof esSearch.search).toBe('function'); }); - it('logs the response if `debug` is set to `true`', () => { + it('logs the response if `debug` is set to `true`', async () => { const spy = jest.spyOn(console, 'log'); const esSearch = esSearchStrategyProvider( { core: mockCoreSetup, + config$: mockConfig$, }, mockApiCaller, mockSearch @@ -61,43 +64,46 @@ describe('ES search strategy', () => { expect(spy).not.toBeCalled(); - esSearch.search({ params: {}, debug: true }); + await esSearch.search({ params: {}, debug: true }); expect(spy).toBeCalled(); }); - it('calls the API caller with the params with defaults', () => { + it('calls the API caller with the params with defaults', async () => { const params = { index: 'logstash-*' }; const esSearch = esSearchStrategyProvider( { core: mockCoreSetup, + config$: mockConfig$, }, mockApiCaller, mockSearch ); - esSearch.search({ params }); + await esSearch.search({ params }); expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toBe('search'); expect(mockApiCaller.mock.calls[0][1]).toEqual({ ...params, + timeout: '0ms', ignoreUnavailable: true, restTotalHitsAsInt: true, }); }); - it('calls the API caller with overridden defaults', () => { - const params = { index: 'logstash-*', ignoreUnavailable: false }; + it('calls the API caller with overridden defaults', async () => { + const params = { index: 'logstash-*', ignoreUnavailable: false, timeout: '1000ms' }; const esSearch = esSearchStrategyProvider( { core: mockCoreSetup, + config$: mockConfig$, }, mockApiCaller, mockSearch ); - esSearch.search({ params }); + await esSearch.search({ params }); expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toBe('search'); @@ -112,6 +118,7 @@ describe('ES search strategy', () => { const esSearch = esSearchStrategyProvider( { core: mockCoreSetup, + config$: mockConfig$, }, mockApiCaller, mockSearch diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index c5fc1d9d3a11c2..20bc964effc02c 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { first } from 'rxjs/operators'; import { APICaller } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { ES_SEARCH_STRATEGY } from '../../../common/search'; @@ -28,7 +29,9 @@ export const esSearchStrategyProvider: TSearchStrategyProvider => { return { search: async (request, options) => { + const config = await context.config$.pipe(first()).toPromise(); const params = { + timeout: `${config.elasticsearch.shardTimeout.asMilliseconds()}ms`, ignoreUnavailable: true, // Don't fail if the index/indices don't exist restTotalHitsAsInt: true, // Get the number of hits as an int rather than a range ...request.params, diff --git a/src/plugins/data/server/search/i_search_context.ts b/src/plugins/data/server/search/i_search_context.ts index 5f2df5d8e819ec..9d9de055d994fd 100644 --- a/src/plugins/data/server/search/i_search_context.ts +++ b/src/plugins/data/server/search/i_search_context.ts @@ -16,8 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { CoreSetup } from '../../../../core/server'; + +import { Observable } from 'rxjs'; +import { CoreSetup, SharedGlobalConfig } from '../../../../core/server'; export interface ISearchContext { core: CoreSetup; + config$: Observable; } diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 3409a72326121f..8ca314ad7bfd8f 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -83,6 +83,11 @@ export class SearchService implements Plugin { }; api.registerSearchStrategyContext(this.initializerContext.opaqueId, 'core', () => core); + api.registerSearchStrategyContext( + this.initializerContext.opaqueId, + 'config$', + () => this.initializerContext.config.legacy.globalConfig$ + ); // ES search capabilities are written in a way that it could easily be a separate plugin, // however these two plugins are tightly coupled due to the default search strategy using diff --git a/src/plugins/eui_utils/public/eui_utils.ts b/src/plugins/eui_utils/public/eui_utils.ts index 12249bf9eca90b..d9c10c34dd4a8b 100644 --- a/src/plugins/eui_utils/public/eui_utils.ts +++ b/src/plugins/eui_utils/public/eui_utils.ts @@ -42,7 +42,7 @@ export class EuiUtils { useEffect(() => { const s = getChartsTheme$().subscribe(update); return () => s.unsubscribe(); - }, [false]); + }, []); return value; }; diff --git a/test/common/services/security/user.ts b/test/common/services/security/user.ts index e1c9b3fb998add..ae02127043234c 100644 --- a/test/common/services/security/user.ts +++ b/test/common/services/security/user.ts @@ -38,7 +38,7 @@ export class User { public async create(username: string, user: any) { this.log.debug(`creating user ${username}`); const { data, status, statusText } = await this.axios.post( - `/api/security/v1/users/${username}`, + `/internal/security/users/${username}`, { username, ...user, @@ -55,7 +55,7 @@ export class User { public async delete(username: string) { this.log.debug(`deleting user ${username}`); const { data, status, statusText } = await this.axios.delete( - `/api/security/v1/users/${username}` + `/internal/security/users/${username}` ); if (status !== 204) { throw new Error( diff --git a/x-pack/legacy/plugins/apm/public/components/app/Home/Home.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/Home/Home.test.tsx index 272e2561b7fd7d..711290942cea16 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Home/Home.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Home/Home.test.tsx @@ -7,15 +7,15 @@ import { shallow } from 'enzyme'; import React from 'react'; import { Home } from '../Home'; -import { MockPluginContextWrapper } from '../../../utils/testHelpers'; +import { MockApmPluginContextWrapper } from '../../../utils/testHelpers'; describe('Home component', () => { it('should render services', () => { expect( shallow( - + - + ) ).toMatchSnapshot(); }); @@ -23,9 +23,9 @@ describe('Home component', () => { it('should render traces', () => { expect( shallow( - + - + ) ).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap index 664a71c934a4e5..7809734dbf2adb 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap @@ -4,10 +4,32 @@ exports[`Home component should render services 1`] = ` @@ -21,10 +43,32 @@ exports[`Home component should render traces 1`] = ` diff --git a/x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx index 2b3f1368f6fa0a..4c98618d7de8a4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx @@ -25,7 +25,7 @@ import { EuiTabLink } from '../../shared/EuiTabLink'; import { SettingsLink } from '../../shared/Links/apm/SettingsLink'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; import { ServiceMap } from '../ServiceMap'; -import { usePlugins } from '../../../new-platform/plugin'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; function getHomeTabs({ serviceMapEnabled = false @@ -82,9 +82,8 @@ interface Props { } export function Home({ tab }: Props) { - const { apm } = usePlugins(); - const { serviceMapEnabled } = apm.config; - const homeTabs = getHomeTabs({ serviceMapEnabled }); + const { config } = useApmPluginContext(); + const homeTabs = getHomeTabs(config); const selectedTab = homeTabs.find( homeTab => homeTab.name === tab ) as $ElementType; diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js b/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.test.tsx similarity index 50% rename from x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js rename to x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.test.tsx index c2009cd0ae3a15..97f8c941911b68 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js +++ b/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.test.tsx @@ -7,67 +7,79 @@ import { mount } from 'enzyme'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { UpdateBreadcrumbs } from '../UpdateBreadcrumbs'; -import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; -import { getRoutes } from '../route_config'; - -const coreMock = { - chrome: { - setBreadcrumbs: jest.fn() - } -}; - -jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); +import { UpdateBreadcrumbs } from './UpdateBreadcrumbs'; +import { getRoutes } from './route_config'; +import { + MockApmPluginContextWrapper, + mockApmPluginContextValue +} from '../../../utils/testHelpers'; +import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; const routes = getRoutes({ serviceMapEnabled: true }); +const setBreadcrumbs = jest.fn(); -function expectBreadcrumbToMatchSnapshot(route, params = '') { +function expectBreadcrumbToMatchSnapshot(route: string, params = '') { mount( - - - + + + + + ); - expect(coreMock.chrome.setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(coreMock.chrome.setBreadcrumbs.mock.calls[0][0]).toMatchSnapshot(); + expect(setBreadcrumbs).toHaveBeenCalledTimes(1); + expect(setBreadcrumbs.mock.calls[0][0]).toMatchSnapshot(); } describe('UpdateBreadcrumbs', () => { - let realDoc; + let realDoc: Document; beforeEach(() => { - realDoc = global.document; - global.document = { + realDoc = window.document; + (window.document as any) = { title: 'Kibana' }; - coreMock.chrome.setBreadcrumbs.mockReset(); + setBreadcrumbs.mockReset(); }); afterEach(() => { - global.document = realDoc; + (window.document as any) = realDoc; }); it('Homepage', () => { expectBreadcrumbToMatchSnapshot('/'); - expect(global.document.title).toMatchInlineSnapshot(`"APM"`); + expect(window.document.title).toMatchInlineSnapshot(`"APM"`); }); it('/services/:serviceName/errors/:groupId', () => { expectBreadcrumbToMatchSnapshot('/services/opbeans-node/errors/myGroupId'); - expect(global.document.title).toMatchInlineSnapshot( + expect(window.document.title).toMatchInlineSnapshot( `"myGroupId | Errors | opbeans-node | Services | APM"` ); }); it('/services/:serviceName/errors', () => { expectBreadcrumbToMatchSnapshot('/services/opbeans-node/errors'); - expect(global.document.title).toMatchInlineSnapshot( + expect(window.document.title).toMatchInlineSnapshot( `"Errors | opbeans-node | Services | APM"` ); }); it('/services/:serviceName/transactions', () => { expectBreadcrumbToMatchSnapshot('/services/opbeans-node/transactions'); - expect(global.document.title).toMatchInlineSnapshot( + expect(window.document.title).toMatchInlineSnapshot( `"Transactions | opbeans-node | Services | APM"` ); }); @@ -77,7 +89,7 @@ describe('UpdateBreadcrumbs', () => { '/services/opbeans-node/transactions/view', 'transactionName=my-transaction-name' ); - expect(global.document.title).toMatchInlineSnapshot( + expect(window.document.title).toMatchInlineSnapshot( `"my-transaction-name | Transactions | opbeans-node | Services | APM"` ); }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx b/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx index 6568c9151bfd92..8960af0f21fd29 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx @@ -6,19 +6,19 @@ import { Location } from 'history'; import React from 'react'; -import { LegacyCoreStart } from 'src/core/public'; -import { useKibanaCore } from '../../../../../observability/public'; +import { AppMountContext } from 'src/core/public'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { Breadcrumb, ProvideBreadcrumbs, BreadcrumbRoute } from './ProvideBreadcrumbs'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; interface Props { location: Location; breadcrumbs: Breadcrumb[]; - core: LegacyCoreStart; + core: AppMountContext['core']; } function getTitleFromBreadCrumbs(breadcrumbs: Breadcrumb[]) { @@ -57,7 +57,8 @@ interface UpdateBreadcrumbsProps { } export function UpdateBreadcrumbs({ routes }: UpdateBreadcrumbsProps) { - const core = useKibanaCore(); + const { core } = useApmPluginContext(); + return ( { - static contextType = KibanaCoreContext; + static contextType = ApmPluginContext; public state: State = { isCreatingJob: false @@ -37,7 +37,7 @@ export class MachineLearningFlyout extends Component { }) => { this.setState({ isCreatingJob: true }); try { - const { http } = this.context; + const { http } = this.context.core; const { serviceName } = this.props.urlParams; if (!serviceName) { throw new Error('Service name is required to create this ML job'); @@ -91,7 +91,7 @@ export class MachineLearningFlyout extends Component { }: { transactionType: string; }) => { - const core = this.context; + const { core } = this.context; const { urlParams } = this.props; const { serviceName } = urlParams; @@ -119,7 +119,7 @@ export class MachineLearningFlyout extends Component { } } )}{' '} - + { } )} - +

) }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/view.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/view.tsx index c3d1c8ba1f5b17..31fc4db8f1a2f0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/view.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/view.tsx @@ -22,7 +22,6 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState, useEffect } from 'react'; import { isEmpty } from 'lodash'; -import { useKibanaCore } from '../../../../../../../observability/public'; import { FETCH_STATUS, useFetcher } from '../../../../../hooks/useFetcher'; import { getHasMLJob } from '../../../../../services/rest/ml'; import { MLJobLink } from '../../../../shared/Links/MachineLearningLinks/MLJobLink'; @@ -30,6 +29,7 @@ import { MLLink } from '../../../../shared/Links/MachineLearningLinks/MLLink'; import { TransactionSelect } from './TransactionSelect'; import { IUrlParams } from '../../../../../context/UrlParamsContext/types'; import { useServiceTransactionTypes } from '../../../../../hooks/useServiceTransactionTypes'; +import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; interface Props { isCreatingJob: boolean; @@ -51,7 +51,7 @@ export function MachineLearningFlyoutView({ string | undefined >(undefined); - const { http } = useKibanaCore(); + const { http } = useApmPluginContext().core; const { data: hasMLJob = false, status } = useFetcher(() => { if (serviceName && selectedTransactionType) { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx index 3625fb430ff297..85254bee12e134 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx @@ -31,12 +31,11 @@ import moment from 'moment-timezone'; import React, { Component } from 'react'; import styled from 'styled-components'; import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { KibanaCoreContext } from '../../../../../../observability/public'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { KibanaLink } from '../../../shared/Links/KibanaLink'; import { createErrorGroupWatch, Schedule } from './createErrorGroupWatch'; import { ElasticDocsLink } from '../../../shared/Links/ElasticDocsLink'; -import { PluginsContext } from '../../../../new-platform/plugin'; +import { ApmPluginContext } from '../../../../context/ApmPluginContext'; type ScheduleKey = keyof Schedule; @@ -77,8 +76,8 @@ export class WatcherFlyout extends Component< WatcherFlyoutProps, WatcherFlyoutState > { - static contextType = KibanaCoreContext; - context!: React.ContextType; + static contextType = ApmPluginContext; + context!: React.ContextType; public state: WatcherFlyoutState = { schedule: 'daily', threshold: 10, @@ -156,7 +155,7 @@ export class WatcherFlyout extends Component< indexPatternTitle: string; }) => () => { const { serviceName } = this.props.urlParams; - const core = this.context; + const { core } = this.context; if (!serviceName) { return; @@ -213,7 +212,7 @@ export class WatcherFlyout extends Component< }; public addErrorToast = () => { - const core = this.context; + const { core } = this.context; core.notifications.toasts.addWarning({ title: i18n.translate( @@ -237,7 +236,7 @@ export class WatcherFlyout extends Component< }; public addSuccessToast = (id: string) => { - const core = this.context; + const { core } = this.context; core.notifications.toasts.addSuccess({ title: i18n.translate( @@ -258,7 +257,7 @@ export class WatcherFlyout extends Component< } } )}{' '} - + @@ -269,7 +268,7 @@ export class WatcherFlyout extends Component< } )} - +

) }); @@ -614,11 +613,11 @@ export class WatcherFlyout extends Component< - - {({ apm }) => { + + {({ config }) => { return ( ); }} - + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index 8903900a625c1b..4158bb877e4595 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -13,11 +13,11 @@ import { import { i18n } from '@kbn/i18n'; import { memoize } from 'lodash'; import React, { Fragment } from 'react'; -import { KibanaCoreContext } from '../../../../../../observability/public'; import { IUrlParams } from '../../../../context/UrlParamsContext/types'; import { LicenseContext } from '../../../../context/LicenseContext'; import { MachineLearningFlyout } from './MachineLearningFlyout'; import { WatcherFlyout } from './WatcherFlyout'; +import { ApmPluginContext } from '../../../../context/ApmPluginContext'; interface Props { urlParams: IUrlParams; @@ -29,8 +29,8 @@ interface State { type FlyoutName = null | 'ML' | 'Watcher'; export class ServiceIntegrations extends React.Component { - static contextType = KibanaCoreContext; - context!: React.ContextType; + static contextType = ApmPluginContext; + context!: React.ContextType; public state: State = { isPopoverOpen: false, activeFlyout: null }; @@ -67,7 +67,7 @@ export class ServiceIntegrations extends React.Component { }; public getWatcherPanelItems = () => { - const core = this.context; + const { core } = this.context; return [ { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx index 21a39e19657a1d..0ec9e90a316599 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceNodeMetrics/index.test.tsx @@ -7,11 +7,18 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ServiceNodeMetrics } from '.'; +import { MockApmPluginContextWrapper } from '../../../utils/testHelpers'; describe('ServiceNodeMetrics', () => { describe('render', () => { it('renders', () => { - expect(() => shallow()).not.toThrowError(); + expect(() => + shallow( + + + + ) + ).not.toThrowError(); }); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx index 9f48880090369d..e5406e59004fb7 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx @@ -4,42 +4,58 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { ReactChild, FunctionComponent } from 'react'; import { render, wait, waitForElement } from '@testing-library/react'; import { ServiceOverview } from '..'; import * as urlParamsHooks from '../../../../hooks/useUrlParams'; -import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; import * as useLocalUIFilters from '../../../../hooks/useLocalUIFilters'; import { FETCH_STATUS } from '../../../../hooks/useFetcher'; import { SessionStorageMock } from '../../../../services/__test__/SessionStorageMock'; +import { + MockApmPluginContextWrapper, + mockApmPluginContextValue +} from '../../../../utils/testHelpers'; +import { ApmPluginContextValue } from '../../../../context/ApmPluginContext'; + +function wrapper({ children }: { children: ReactChild }) { + return ( + + {children} + + ); +} function renderServiceOverview() { - return render(); + return render(, { wrapper } as { + wrapper: FunctionComponent<{}>; + }); } -const coreMock = ({ - http: { - basePath: { - prepend: (path: string) => `/basepath${path}` - }, - get: jest.fn() - }, - notifications: { - toasts: { - addWarning: () => {} - } - } -} as unknown) as LegacyCoreStart & { - http: { get: jest.Mock }; -}; +const addWarning = jest.fn(); +const httpGet = jest.fn(); describe('Service Overview -> View', () => { beforeEach(() => { // @ts-ignore global.sessionStorage = new SessionStorageMock(); - spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock); // mock urlParams spyOn(urlParamsHooks, 'useUrlParams').and.returnValue({ urlParams: { @@ -62,7 +78,7 @@ describe('Service Overview -> View', () => { it('should render services, when list is not empty', async () => { // mock rest requests - coreMock.http.get.mockResolvedValueOnce({ + httpGet.mockResolvedValueOnce({ hasLegacyData: false, hasHistoricalData: true, items: [ @@ -88,14 +104,14 @@ describe('Service Overview -> View', () => { const { container, getByText } = renderServiceOverview(); // wait for requests to be made - await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1)); + await wait(() => expect(httpGet).toHaveBeenCalledTimes(1)); await waitForElement(() => getByText('My Python Service')); expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot(); }); it('should render getting started message, when list is empty and no historical data is found', async () => { - coreMock.http.get.mockResolvedValueOnce({ + httpGet.mockResolvedValueOnce({ hasLegacyData: false, hasHistoricalData: false, items: [] @@ -104,7 +120,7 @@ describe('Service Overview -> View', () => { const { container, getByText } = renderServiceOverview(); // wait for requests to be made - await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1)); + await wait(() => expect(httpGet).toHaveBeenCalledTimes(1)); // wait for elements to be rendered await waitForElement(() => @@ -117,7 +133,7 @@ describe('Service Overview -> View', () => { }); it('should render empty message, when list is empty and historical data is found', async () => { - coreMock.http.get.mockResolvedValueOnce({ + httpGet.mockResolvedValueOnce({ hasLegacyData: false, hasHistoricalData: true, items: [] @@ -126,7 +142,7 @@ describe('Service Overview -> View', () => { const { container, getByText } = renderServiceOverview(); // wait for requests to be made - await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1)); + await wait(() => expect(httpGet).toHaveBeenCalledTimes(1)); await waitForElement(() => getByText('No services found')); expect(container.querySelectorAll('.euiTableRow')).toMatchSnapshot(); @@ -134,13 +150,7 @@ describe('Service Overview -> View', () => { describe('when legacy data is found', () => { it('renders an upgrade migration notification', async () => { - // create spies - const addWarning = jest.spyOn( - coreMock.notifications.toasts, - 'addWarning' - ); - - coreMock.http.get.mockResolvedValueOnce({ + httpGet.mockResolvedValueOnce({ hasLegacyData: true, hasHistoricalData: true, items: [] @@ -149,7 +159,7 @@ describe('Service Overview -> View', () => { renderServiceOverview(); // wait for requests to be made - await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1)); + await wait(() => expect(httpGet).toHaveBeenCalledTimes(1)); expect(addWarning).toHaveBeenLastCalledWith( expect.objectContaining({ @@ -161,12 +171,7 @@ describe('Service Overview -> View', () => { describe('when legacy data is not found', () => { it('does not render an upgrade migration notification', async () => { - // create spies - const addWarning = jest.spyOn( - coreMock.notifications.toasts, - 'addWarning' - ); - coreMock.http.get.mockResolvedValueOnce({ + httpGet.mockResolvedValueOnce({ hasLegacyData: false, hasHistoricalData: true, items: [] @@ -175,7 +180,7 @@ describe('Service Overview -> View', () => { renderServiceOverview(); // wait for requests to be made - await wait(() => expect(coreMock.http.get).toHaveBeenCalledTimes(1)); + await wait(() => expect(httpGet).toHaveBeenCalledTimes(1)); expect(addWarning).not.toHaveBeenCalled(); }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx index 0702e092a714f5..05ccc691ecdba8 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/index.tsx @@ -15,9 +15,9 @@ import { NoServicesMessage } from './NoServicesMessage'; import { ServiceList } from './ServiceList'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { useTrackPageview } from '../../../../../infra/public'; -import { useKibanaCore } from '../../../../../observability/public'; import { PROJECTION } from '../../../../common/projections/typings'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; const initalData = { items: [], @@ -28,7 +28,7 @@ const initalData = { let hasDisplayedToast = false; export function ServiceOverview() { - const core = useKibanaCore(); + const { core } = useApmPluginContext(); const { urlParams: { start, end }, uiFilters diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/DeleteButton.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/DeleteButton.tsx index 8f5d4f4f600d3b..b5a59aea3286d2 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/DeleteButton.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/DeleteButton.tsx @@ -11,8 +11,8 @@ import { i18n } from '@kbn/i18n'; import { useCallApmApi } from '../../../../../hooks/useCallApmApi'; import { Config } from '../index'; import { getOptionLabel } from '../../../../../../common/agent_configuration_constants'; -import { useKibanaCore } from '../../../../../../../observability/public'; import { APMClient } from '../../../../../services/rest/createCallApmApi'; +import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; interface Props { onDeleted: () => void; @@ -21,10 +21,7 @@ interface Props { export function DeleteButton({ onDeleted, selectedConfig }: Props) { const [isDeleting, setIsDeleting] = useState(false); - const { - notifications: { toasts } - } = useKibanaCore(); - + const { toasts } = useApmPluginContext().core.notifications; const callApmApi = useCallApmApi(); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx index 853ce26d324fbc..e1cb07be3d3786 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AddEditFlyout/index.tsx @@ -33,7 +33,7 @@ import { useFetcher } from '../../../../../hooks/useFetcher'; import { isRumAgentName } from '../../../../../../common/agent_name'; import { ALL_OPTION_VALUE } from '../../../../../../common/agent_configuration_constants'; import { saveConfig } from './saveConfig'; -import { useKibanaCore } from '../../../../../../../observability/public'; +import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; const defaultSettings = { TRANSACTION_SAMPLE_RATE: '1.0', @@ -54,9 +54,7 @@ export function AddEditFlyout({ onDeleted, selectedConfig }: Props) { - const { - notifications: { toasts } - } = useKibanaCore(); + const { toasts } = useApmPluginContext().core.notifications; const [isSaving, setIsSaving] = useState(false); const callApmApiFromHook = useCallApmApi(); diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index 7ced0b6fdd5662..ba68e1726d2b41 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -23,7 +23,7 @@ import { useFetcher } from '../../../../hooks/useFetcher'; import { useCallApmApi } from '../../../../hooks/useCallApmApi'; import { APMClient } from '../../../../services/rest/createCallApmApi'; import { clearCache } from '../../../../services/rest/callApi'; -import { useKibanaCore } from '../../../../../../observability/public'; +import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; const APM_INDEX_LABELS = [ { @@ -86,9 +86,7 @@ async function saveApmIndices({ } export function ApmIndices() { - const { - notifications: { toasts } - } = useKibanaCore(); + const { toasts } = useApmPluginContext().core.notifications; const [apmIndices, setApmIndices] = useState>({}); const [isSaving, setIsSaving] = useState(false); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx index b77524fca050dc..d1254e1e1e2ad9 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TraceLink/__test__/TraceLink.test.tsx @@ -9,26 +9,29 @@ import { shallow } from 'enzyme'; import * as urlParamsHooks from '../../../../hooks/useUrlParams'; import * as hooks from '../../../../hooks/useFetcher'; import { TraceLink } from '../'; +import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers'; +import * as routeConfig from '../../Main/route_config'; +import { BreadcrumbRoute } from '../../Main/ProvideBreadcrumbs'; -jest.mock('../../Main/route_config/index.tsx', () => ({ - routes: [ - { - path: '/services/:serviceName/transactions/view', - name: 'transaction_name' - }, - { - path: '/traces', - name: 'traces' - } - ] -})); +const renderOptions = { wrapper: MockApmPluginContextWrapper }; + +jest.spyOn(routeConfig, 'getRoutes').mockReturnValue([ + { + path: '/services/:serviceName/transactions/view', + name: 'transaction_name' + }, + { + path: '/traces', + name: 'traces' + } +] as BreadcrumbRoute[]); describe('TraceLink', () => { afterAll(() => { jest.clearAllMocks(); }); it('renders transition page', () => { - const component = render(); + const component = render(, renderOptions); expect(component.getByText('Fetching trace...')).toBeDefined(); }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx index 91e0ae11a652e2..a6f0d26a32e78b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx @@ -22,16 +22,11 @@ import * as useFetcherHook from '../../../../hooks/useFetcher'; import { fromQuery } from '../../../shared/Links/url_helpers'; import { Router } from 'react-router-dom'; import { UrlParamsProvider } from '../../../../context/UrlParamsContext'; -import { KibanaCoreContext } from '../../../../../../observability/public'; -import { LegacyCoreStart } from 'kibana/public'; +import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers'; jest.spyOn(history, 'push'); jest.spyOn(history, 'replace'); -const coreMock = ({ - notifications: { toasts: { addWarning: () => {} } } -} as unknown) as LegacyCoreStart; - function setup({ urlParams, serviceTransactionTypes @@ -55,13 +50,13 @@ function setup({ jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); return render( - + - + ); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx index f016052df56a2b..de356b5812e9a4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/index.tsx @@ -15,7 +15,6 @@ import { import { Location } from 'history'; import { first } from 'lodash'; import React, { useMemo } from 'react'; -import { useKibanaCore } from '../../../../../observability/public'; import { useTransactionList } from '../../../hooks/useTransactionList'; import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; import { IUrlParams } from '../../../context/UrlParamsContext/types'; @@ -35,6 +34,7 @@ import { PROJECTION } from '../../../../common/projections/typings'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; import { TransactionTypeFilter } from '../../shared/LocalUIFilters/TransactionTypeFilter'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; function getRedirectLocation({ urlParams, @@ -86,7 +86,7 @@ export function TransactionOverview() { status: transactionListStatus } = useTransactionList(urlParams); - const { http } = useKibanaCore(); + const { http } = useApmPluginContext().core; const { data: hasMLJob = false } = useFetcher(() => { if (serviceName && transactionType) { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index 32fbe46ac560c4..67bff86c8ccdfc 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -15,7 +15,7 @@ import { getBoolFilter } from './get_bool_filter'; import { useLocation } from '../../../hooks/useLocation'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { history } from '../../../utils/history'; -import { usePlugins } from '../../../new-platform/plugin'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; import { AutocompleteProvider, @@ -71,7 +71,7 @@ export function KueryBar() { }); const { urlParams } = useUrlParams(); const location = useLocation(); - const { data } = usePlugins(); + const { data } = useApmPluginContext().plugins; const autocompleteProvider = data.autocomplete.getProvider('kuery'); let currentRequestCheck; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx index 5d25dc7de4e131..51d8b43dac0eaa 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx @@ -10,8 +10,8 @@ import url from 'url'; import rison, { RisonValue } from 'rison-node'; import { useLocation } from '../../../../hooks/useLocation'; import { getTimepickerRisonData } from '../rison_helpers'; -import { useKibanaCore } from '../../../../../../observability/public'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; +import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; interface Props { query: { @@ -31,7 +31,7 @@ interface Props { } export function DiscoverLink({ query = {}, ...rest }: Props) { - const core = useKibanaCore(); + const { core } = useApmPluginContext(); const location = useLocation(); const risonQuery = { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx index 839efbbdf60f1d..2f49e476ef610b 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/DiscoverLinks/__test__/DiscoverLinks.integration.test.tsx @@ -13,31 +13,8 @@ import { getRenderedHref } from '../../../../../utils/testHelpers'; import { DiscoverErrorLink } from '../DiscoverErrorLink'; import { DiscoverSpanLink } from '../DiscoverSpanLink'; import { DiscoverTransactionLink } from '../DiscoverTransactionLink'; -import * as kibanaCore from '../../../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; describe('DiscoverLinks', () => { - beforeAll(() => { - jest.spyOn(console, 'error').mockImplementation(() => null); - - const coreMock = ({ - http: { - notifications: { - toasts: {} - }, - basePath: { - prepend: (path: string) => `/basepath${path}` - } - } - } as unknown) as LegacyCoreStart; - - spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock); - }); - - afterAll(() => { - jest.restoreAllMocks(); - }); - it('produces the correct URL for a transaction', async () => { const transaction = { transaction: { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx index 1888e1d04c2cb4..efae8982c22359 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; -import { usePlugins } from '../../../new-platform/plugin'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; // union type constisting of valid guide sections that we link to type DocsSection = '/apm/get-started' | '/x-pack' | '/apm/server'; @@ -17,8 +17,7 @@ interface Props extends EuiLinkAnchorProps { } export function ElasticDocsLink({ section, path, ...rest }: Props) { - const { apm } = usePlugins(); - const { stackVersion } = apm; - const href = `https://www.elastic.co/guide/en${section}/${stackVersion}${path}`; + const { version } = useApmPluginContext().packageInfo; + const href = `https://www.elastic.co/guide/en${section}/${version}${path}`; return ; } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx index 4f96f529c471c5..42022a37414953 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.test.tsx @@ -8,18 +8,6 @@ import { Location } from 'history'; import React from 'react'; import { getRenderedHref } from '../../../utils/testHelpers'; import { InfraLink } from './InfraLink'; -import * as kibanaCore from '../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; - -const coreMock = ({ - http: { - basePath: { - prepend: (path: string) => `/basepath${path}` - } - } -} as unknown) as LegacyCoreStart; - -jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); test('InfraLink produces the correct URL', async () => { const href = await getRenderedHref( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.tsx index 192fafadba4c01..8ff5e3010d6cc1 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/InfraLink.tsx @@ -9,7 +9,7 @@ import { compact } from 'lodash'; import React from 'react'; import url from 'url'; import { fromQuery } from './url_helpers'; -import { useKibanaCore } from '../../../../../observability/public'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; interface InfraQueryParams { time?: number; @@ -24,7 +24,7 @@ interface Props extends EuiLinkAnchorProps { } export function InfraLink({ path, query = {}, ...rest }: Props) { - const core = useKibanaCore(); + const { core } = useApmPluginContext(); const nextSearch = fromQuery(query); const href = url.format({ pathname: core.http.basePath.prepend('/app/infra'), diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx index 521d62205311d2..fad534e11f645e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.test.tsx @@ -8,26 +8,8 @@ import { Location } from 'history'; import React from 'react'; import { getRenderedHref } from '../../../utils/testHelpers'; import { KibanaLink } from './KibanaLink'; -import * as kibanaCore from '../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; describe('KibanaLink', () => { - beforeEach(() => { - const coreMock = ({ - http: { - basePath: { - prepend: (path: string) => `/basepath${path}` - } - } - } as unknown) as LegacyCoreStart; - - jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - it('produces the correct URL', async () => { const href = await getRenderedHref(() => , { search: '?rangeFrom=now-5h&rangeTo=now-2h' diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.tsx index de62d5e46070a0..37e2c06d2f701d 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/KibanaLink.tsx @@ -7,7 +7,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import React from 'react'; import url from 'url'; -import { useKibanaCore } from '../../../../../observability/public'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; interface Props extends EuiLinkAnchorProps { path?: string; @@ -15,7 +15,7 @@ interface Props extends EuiLinkAnchorProps { } export function KibanaLink({ path, ...rest }: Props) { - const core = useKibanaCore(); + const { core } = useApmPluginContext(); const href = url.format({ pathname: core.http.basePath.prepend('/app/kibana'), hash: path diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx index aaf27e75ce93b8..75a247a1aae40b 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.test.tsx @@ -8,25 +8,8 @@ import { Location } from 'history'; import React from 'react'; import { getRenderedHref } from '../../../../utils/testHelpers'; import { MLJobLink } from './MLJobLink'; -import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; describe('MLJobLink', () => { - beforeEach(() => { - const coreMock = ({ - http: { - basePath: { - prepend: (path: string) => `/basepath${path}` - } - } - } as unknown) as LegacyCoreStart; - - spyOn(kibanaCore, 'useKibanaCore').and.returnValue(coreMock); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); it('should produce the correct URL', async () => { const href = await getRenderedHref( () => ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx index 0db30e136b6ec6..d38d61fede37a7 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx @@ -8,26 +8,6 @@ import { Location } from 'history'; import React from 'react'; import { getRenderedHref } from '../../../../utils/testHelpers'; import { MLLink } from './MLLink'; -import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; - -const coreMock = ({ - http: { - basePath: { - prepend: (path: string) => `/basepath${path}` - } - } -} as unknown) as LegacyCoreStart; - -jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); - -beforeAll(() => { - jest.spyOn(console, 'error').mockImplementation(() => null); -}); - -afterAll(() => { - jest.restoreAllMocks(); -}); test('MLLink produces the correct URL', async () => { const href = await getRenderedHref( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx index 0fe80b729f0104..3671a0089fd6e2 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx @@ -10,7 +10,7 @@ import url from 'url'; import rison, { RisonValue } from 'rison-node'; import { useLocation } from '../../../../hooks/useLocation'; import { getTimepickerRisonData, TimepickerRisonData } from '../rison_helpers'; -import { useKibanaCore } from '../../../../../../observability/public'; +import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; interface MlRisonData { ml?: { @@ -25,7 +25,7 @@ interface Props { } export function MLLink({ children, path = '', query = {} }: Props) { - const core = useKibanaCore(); + const { core } = useApmPluginContext(); const location = useLocation(); const risonQuery: MlRisonData & TimepickerRisonData = getTimepickerRisonData( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx index c3913e43cbd62b..5bbc194e35992e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx @@ -11,11 +11,11 @@ import { APMError } from '../../../../../../typings/es_schemas/ui/APMError'; import { expectTextsInDocument, expectTextsNotInDocument, - MockPluginContextWrapper + MockApmPluginContextWrapper } from '../../../../../utils/testHelpers'; const renderOptions = { - wrapper: MockPluginContextWrapper + wrapper: MockApmPluginContextWrapper }; function getError() { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx index 438fe57218cc9b..4b6355034f16ae 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx @@ -11,11 +11,11 @@ import { Span } from '../../../../../../typings/es_schemas/ui/Span'; import { expectTextsInDocument, expectTextsNotInDocument, - MockPluginContextWrapper + MockApmPluginContextWrapper } from '../../../../../utils/testHelpers'; const renderOptions = { - wrapper: MockPluginContextWrapper + wrapper: MockApmPluginContextWrapper }; describe('SpanMetadata', () => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx index 1ea20ecd645629..1fcb093fa03544 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -11,11 +11,11 @@ import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction import { expectTextsInDocument, expectTextsNotInDocument, - MockPluginContextWrapper + MockApmPluginContextWrapper } from '../../../../../utils/testHelpers'; const renderOptions = { - wrapper: MockPluginContextWrapper + wrapper: MockApmPluginContextWrapper }; function getTransaction() { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx index bed25fcc640127..979b9118a7534e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx @@ -9,12 +9,12 @@ import { render } from '@testing-library/react'; import { MetadataTable } from '..'; import { expectTextsInDocument, - MockPluginContextWrapper + MockApmPluginContextWrapper } from '../../../../utils/testHelpers'; import { SectionsWithRows } from '../helper'; const renderOptions = { - wrapper: MockPluginContextWrapper + wrapper: MockApmPluginContextWrapper }; describe('MetadataTable', () => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 4a3b77b699c5f9..040d29aaa56dd4 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -23,7 +23,7 @@ import { DiscoverTransactionLink } from '../Links/DiscoverLinks/DiscoverTransact import { InfraLink } from '../Links/InfraLink'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { fromQuery } from '../Links/url_helpers'; -import { useKibanaCore } from '../../../../../observability/public'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; function getInfraMetricsQuery(transaction: Transaction) { const plus5 = new Date(transaction['@timestamp']); @@ -65,7 +65,7 @@ export const TransactionActionMenu: FunctionComponent = ( ) => { const { transaction } = props; - const core = useKibanaCore(); + const { core } = useApmPluginContext(); const [isOpen, setIsOpen] = useState(false); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index 4bb018c760f1f6..2bfa5cf1274fae 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -9,12 +9,12 @@ import { render, fireEvent } from '@testing-library/react'; import { TransactionActionMenu } from '../TransactionActionMenu'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import * as Transactions from './mockData'; -import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'src/core/public'; +import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers'; const renderTransaction = async (transaction: Record) => { const rendered = render( - + , + { wrapper: MockApmPluginContextWrapper } ); fireEvent.click(rendered.getByText('Actions')); @@ -23,22 +23,6 @@ const renderTransaction = async (transaction: Record) => { }; describe('TransactionActionMenu component', () => { - beforeEach(() => { - const coreMock = ({ - http: { - basePath: { - prepend: (path: string) => `/basepath${path}` - } - } - } as unknown) as LegacyCoreStart; - - jest.spyOn(kibanaCore, 'useKibanaCore').mockReturnValue(coreMock); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - it('should always render the discover link', async () => { const { queryByText } = await renderTransaction( Transactions.transactionWithMinimalData diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx index e95f733fb4bc85..6d3e29ec099850 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/TransactionCharts/BrowserLineChart.test.tsx @@ -7,11 +7,18 @@ import React from 'react'; import { shallow } from 'enzyme'; import { BrowserLineChart } from './BrowserLineChart'; +import { MockApmPluginContextWrapper } from '../../../../utils/testHelpers'; describe('BrowserLineChart', () => { describe('render', () => { it('renders', () => { - expect(() => shallow()).not.toThrowError(); + expect(() => + shallow( + + + + ) + ).not.toThrowError(); }); }); }); diff --git a/x-pack/legacy/plugins/apm/public/context/ApmPluginContext.tsx b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext.tsx new file mode 100644 index 00000000000000..86efd9b31974e8 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/context/ApmPluginContext.tsx @@ -0,0 +1,18 @@ +/* + * 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 { createContext } from 'react'; +import { AppMountContext, PackageInfo } from 'kibana/public'; +import { ApmPluginSetupDeps, ConfigSchema } from '../new-platform/plugin'; + +export interface ApmPluginContextValue { + config: ConfigSchema; + core: AppMountContext['core']; + packageInfo: PackageInfo; + plugins: ApmPluginSetupDeps; +} + +export const ApmPluginContext = createContext({} as ApmPluginContextValue); diff --git a/x-pack/legacy/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/legacy/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx index 1c340f4b4f3c77..36e780f50c3ae3 100644 --- a/x-pack/legacy/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx +++ b/x-pack/legacy/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx @@ -6,10 +6,10 @@ import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useKibanaCore } from '../../../../observability/public'; +import { useApmPluginContext } from '../../hooks/useApmPluginContext'; export function InvalidLicenseNotification() { - const core = useKibanaCore(); + const { core } = useApmPluginContext(); const manageLicenseURL = core.http.basePath.prepend( '/app/kibana#/management/elasticsearch/license_management' ); diff --git a/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx b/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx index 38a402fd72ed20..4bb246a2a745ba 100644 --- a/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx +++ b/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher'; import { loadLicense, LicenseApiResponse } from '../../services/rest/xpack'; import { InvalidLicenseNotification } from './InvalidLicenseNotification'; -import { useKibanaCore } from '../../../../observability/public'; +import { useApmPluginContext } from '../../hooks/useApmPluginContext'; const initialLicense: LicenseApiResponse = { features: {}, @@ -18,7 +18,7 @@ const initialLicense: LicenseApiResponse = { export const LicenseContext = React.createContext(initialLicense); export const LicenseProvider: React.FC = ({ children }) => { - const { http } = useKibanaCore(); + const { http } = useApmPluginContext().core; const { data = initialLicense, status } = useFetcher( () => loadLicense(http), [http] diff --git a/x-pack/legacy/plugins/security/common/constants.ts b/x-pack/legacy/plugins/apm/public/hooks/useApmPluginContext.ts similarity index 57% rename from x-pack/legacy/plugins/security/common/constants.ts rename to x-pack/legacy/plugins/apm/public/hooks/useApmPluginContext.ts index 08e49ad995550b..80a04edbca8584 100644 --- a/x-pack/legacy/plugins/security/common/constants.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useApmPluginContext.ts @@ -4,4 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export const INTERNAL_API_BASE_PATH = '/internal/security'; +import { useContext } from 'react'; +import { ApmPluginContext } from '../context/ApmPluginContext'; + +export function useApmPluginContext() { + return useContext(ApmPluginContext); +} diff --git a/x-pack/legacy/plugins/apm/public/hooks/useCallApi.ts b/x-pack/legacy/plugins/apm/public/hooks/useCallApi.ts index 6b12fd04d09161..415e6172ae81e4 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useCallApi.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useCallApi.ts @@ -5,11 +5,11 @@ */ import { useMemo } from 'react'; -import { useKibanaCore } from '../../../observability/public'; import { callApi, FetchOptions } from '../services/rest/callApi'; +import { useApmPluginContext } from './useApmPluginContext'; export function useCallApi() { - const { http } = useKibanaCore(); + const { http } = useApmPluginContext().core; return useMemo(() => { return (options: FetchOptions) => callApi(http, options); diff --git a/x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts b/x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts index b201b396e05c3a..b28b295d8189e4 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useCallApmApi.ts @@ -5,11 +5,11 @@ */ import { useMemo } from 'react'; -import { useKibanaCore } from '../../../observability/public'; import { createCallApmApi } from '../services/rest/createCallApmApi'; +import { useApmPluginContext } from './useApmPluginContext'; export function useCallApmApi() { - const { http } = useKibanaCore(); + const { http } = useApmPluginContext().core; return useMemo(() => { return createCallApmApi(http); diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx index 743cf4e01e5559..8d8716e6e5cd78 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx @@ -4,25 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { render, wait } from '@testing-library/react'; -import { delay } from '../utils/testHelpers'; +import React from 'react'; +import { delay, MockApmPluginContextWrapper } from '../utils/testHelpers'; import { useFetcher } from './useFetcher'; -import { KibanaCoreContext } from '../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'kibana/public'; - -// Wrap the hook with a provider so it can useKibanaCore -const wrapper = ({ children }: { children?: React.ReactNode }) => ( - {} } } - } as unknown) as LegacyCoreStart - } - > - {children} - -); + +const wrapper = MockApmPluginContextWrapper; async function asyncFn(name: string, ms: number) { await delay(ms); diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.test.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.test.tsx index 92246499c62291..e3ef1d44c8b031 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.test.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.test.tsx @@ -5,24 +5,11 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { delay } from '../utils/testHelpers'; +import { delay, MockApmPluginContextWrapper } from '../utils/testHelpers'; import { useFetcher } from './useFetcher'; -import { KibanaCoreContext } from '../../../observability/public/context/kibana_core'; -import { LegacyCoreStart } from 'kibana/public'; -import React from 'react'; - -// Wrap the hook with a provider so it can useKibanaCore -const wrapper = ({ children }: { children?: React.ReactNode }) => ( - {} } } - } as unknown) as LegacyCoreStart - } - > - {children} - -); + +// Wrap the hook with a provider so it can useApmPluginContext +const wrapper = MockApmPluginContextWrapper; describe('useFetcher', () => { describe('when resolving after 500ms', () => { diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx index 2d60273c1896a8..ac8f40a29d93a8 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.tsx @@ -10,9 +10,9 @@ import { IHttpFetchError } from 'src/core/public'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; import { LoadingIndicatorContext } from '../context/LoadingIndicatorContext'; import { useComponentId } from './useComponentId'; -import { useKibanaCore } from '../../../observability/public'; import { APMClient } from '../services/rest/createCallApmApi'; import { useCallApmApi } from './useCallApmApi'; +import { useApmPluginContext } from './useApmPluginContext'; export enum FETCH_STATUS { LOADING = 'loading', @@ -42,7 +42,7 @@ export function useFetcher( preservePreviousData?: boolean; } = {} ): Result> & { refetch: () => void } { - const { notifications } = useKibanaCore(); + const { notifications } = useApmPluginContext().core; const { preservePreviousData = true } = options; const id = useComponentId(); diff --git a/x-pack/legacy/plugins/apm/public/index.tsx b/x-pack/legacy/plugins/apm/public/index.tsx index db14e1c520020e..59b2fedaafba6c 100644 --- a/x-pack/legacy/plugins/apm/public/index.tsx +++ b/x-pack/legacy/plugins/apm/public/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { npStart } from 'ui/new_platform'; +import { npSetup, npStart } from 'ui/new_platform'; import 'react-vis/dist/style.css'; import { PluginInitializerContext } from 'kibana/public'; import 'ui/autoload/all'; @@ -14,8 +14,6 @@ import { REACT_APP_ROOT_ID } from './new-platform/plugin'; import './style/global_overrides.css'; import template from './templates/index.html'; -const { core, plugins } = npStart; - // This will be moved to core.application.register when the new platform // migration is complete. // @ts-ignore @@ -32,5 +30,7 @@ const checkForRoot = () => { }); }; checkForRoot().then(() => { - plugin({} as PluginInitializerContext).start(core, plugins); + const pluginInstance = plugin({} as PluginInitializerContext); + pluginInstance.setup(npSetup.core, npSetup.plugins); + pluginInstance.start(npStart.core, npStart.plugins); }); diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index 8277707e538ac0..38dc82f01b386d 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -4,20 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useContext, createContext } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom'; import { Router, Route, Switch } from 'react-router-dom'; import styled from 'styled-components'; +import { metadata } from 'ui/metadata'; import { HomePublicPluginSetup } from '../../../../../../src/plugins/home/public'; import { CoreStart, - LegacyCoreStart, Plugin, CoreSetup, - PluginInitializerContext + PluginInitializerContext, + PackageInfo } from '../../../../../../src/core/public'; -import { DataPublicPluginStart } from '../../../../../../src/plugins/data/public'; -import { KibanaCoreContextProvider } from '../../../observability/public'; +import { DataPublicPluginSetup } from '../../../../../../src/plugins/data/public'; import { history } from '../utils/history'; import { LocationProvider } from '../context/LocationContext'; import { UrlParamsProvider } from '../context/UrlParamsContext'; @@ -35,7 +35,7 @@ import { featureCatalogueEntry } from './featureCatalogueEntry'; import { getConfigFromInjectedMetadata } from './getConfigFromInjectedMetadata'; import { toggleAppLinkInNav } from './toggleAppLinkInNav'; import { BreadcrumbRoute } from '../components/app/Main/ProvideBreadcrumbs'; -import { stackVersionFromLegacyMetadata } from './stackVersionFromLegacyMetadata'; +import { ApmPluginContext } from '../context/ApmPluginContext'; export const REACT_APP_ROOT_ID = 'react-apm-root'; @@ -63,13 +63,10 @@ export type ApmPluginSetup = void; export type ApmPluginStart = void; export interface ApmPluginSetupDeps { + data: DataPublicPluginSetup; home: HomePublicPluginSetup; } -export interface ApmPluginStartDeps { - data: DataPublicPluginStart; -} - export interface ConfigSchema { indexPatternTitle: string; serviceMapEnabled: boolean; @@ -78,27 +75,14 @@ export interface ConfigSchema { }; } -// These are to be used until we switch over all our context handling to -// kibana_react -export const PluginsContext = createContext< - ApmPluginStartDeps & { apm: { config: ConfigSchema; stackVersion: string } } ->( - {} as ApmPluginStartDeps & { - apm: { config: ConfigSchema; stackVersion: string }; - } -); -export function usePlugins() { - return useContext(PluginsContext); -} - export class ApmPlugin - implements - Plugin< - ApmPluginSetup, - ApmPluginStart, - ApmPluginSetupDeps, - ApmPluginStartDeps - > { + implements Plugin { + // When we switch over from the old platform to new platform the plugins will + // be coming from setup instead of start, since that's where we do + // `core.application.register`. During the transitions we put plugins on an + // instance property so we can use it in start. + setupPlugins: ApmPluginSetupDeps = {} as ApmPluginSetupDeps; + constructor( // @ts-ignore Not using initializerContext now, but will be once NP // migration is complete. @@ -108,10 +92,12 @@ export class ApmPlugin // Take the DOM element as the constructor, so we can mount the app. public setup(_core: CoreSetup, plugins: ApmPluginSetupDeps) { plugins.home.featureCatalogue.register(featureCatalogueEntry); + this.setupPlugins = plugins; } - public start(core: CoreStart, plugins: ApmPluginStartDeps) { + public start(core: CoreStart) { const i18nCore = core.i18n; + const plugins = this.setupPlugins; // Once we're actually an NP plugin we'll get the config from the // initializerContext like: @@ -124,12 +110,10 @@ export class ApmPlugin // Once we're actually an NP plugin we'll get the package info from the // initializerContext like: // - // const stackVersion = this.initializerContext.env.packageInfo.branch + // const packageInfo = this.initializerContext.env.packageInfo // // Until then we use a shim to get it from legacy metadata: - const stackVersion = stackVersionFromLegacyMetadata; - - const pluginsForContext = { ...plugins, apm: { config, stackVersion } }; + const packageInfo = metadata as PackageInfo; const routes = getRoutes(config); @@ -138,26 +122,31 @@ export class ApmPlugin setReadonlyBadge(core); toggleAppLinkInNav(core, config); + const apmPluginContextValue = { + config, + core, + packageInfo, + plugins + }; + ReactDOM.render( - - - - - - - - - - - - - - - - - - - , + + + + + + + + + + + + + + + + + , document.getElementById(REACT_APP_ROOT_ID) ); diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 9e3c486715a1fa..4e0a0209926bf8 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -11,7 +11,7 @@ import enzymeToJson from 'enzyme-to-json'; import { Location } from 'history'; import moment from 'moment'; import { Moment } from 'moment-timezone'; -import React, { FunctionComponent, ReactNode } from 'react'; +import React, { ReactNode } from 'react'; import { render, waitForElement } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { MemoryRouter } from 'react-router-dom'; @@ -20,10 +20,10 @@ import { LocationProvider } from '../context/LocationContext'; import { PromiseReturnType } from '../../typings/common'; import { ESFilter } from '../../typings/elasticsearch'; import { - PluginsContext, - ConfigSchema, - ApmPluginStartDeps -} from '../new-platform/plugin'; + ApmPluginContext, + ApmPluginContextValue +} from '../context/ApmPluginContext'; +import { ConfigSchema } from '../new-platform/plugin'; export function toJson(wrapper: ReactWrapper) { return enzymeToJson(wrapper, { @@ -51,11 +51,13 @@ export function mockMoment() { // Useful for getting the rendered href from any kind of link component export async function getRenderedHref(Component: React.FC, location: Location) { const el = render( - - - - - + + + + + + + ); await waitForElement(() => el.container.querySelector('a')); @@ -181,22 +183,52 @@ export async function inspectSearchParams( export type SearchParamsMock = PromiseReturnType; -export const MockPluginContextWrapper: FunctionComponent<{}> = ({ - children +const mockCore = { + chrome: { + setBreadcrumbs: () => {} + }, + http: { + basePath: { + prepend: (path: string) => `/basepath${path}` + } + }, + notifications: { + toasts: { + addWarning: () => {} + } + } +}; + +const mockConfig: ConfigSchema = { + indexPatternTitle: 'apm-*', + serviceMapEnabled: false, + ui: { + enabled: false + } +}; + +export const mockApmPluginContextValue = { + config: mockConfig, + core: mockCore, + packageInfo: { version: '0' }, + plugins: {} +}; + +export function MockApmPluginContextWrapper({ + children, + value = {} as ApmPluginContextValue }: { children?: ReactNode; -}) => { + value?: ApmPluginContextValue; +}) { return ( - {children} - + ); -}; +} diff --git a/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts b/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts index 9591dfdecbfef0..66f2a8d1ac79f2 100644 --- a/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts +++ b/x-pack/legacy/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts @@ -183,7 +183,7 @@ async function createOrUpdateUser(newUser: User) { async function createUser(newUser: User) { const user = await callKibana({ method: 'POST', - url: `/api/security/v1/users/${newUser.username}`, + url: `/internal/security/users/${newUser.username}`, data: { ...newUser, enabled: true, @@ -209,7 +209,7 @@ async function updateUser(existingUser: User, newUser: User) { // assign role to user await callKibana({ method: 'POST', - url: `/api/security/v1/users/${username}`, + url: `/internal/security/users/${username}`, data: { ...existingUser, roles: allRoles } }); @@ -219,7 +219,7 @@ async function updateUser(existingUser: User, newUser: User) { async function getUser(username: string) { try { return await callKibana({ - url: `/api/security/v1/users/${username}` + url: `/internal/security/users/${username}` }); } catch (e) { const err = e as AxiosError; diff --git a/x-pack/legacy/plugins/beats_management/public/components/layouts/no_data.tsx b/x-pack/legacy/plugins/beats_management/public/components/layouts/no_data.tsx index e525ea4be46e0d..8d2edf9c29e9ee 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/layouts/no_data.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/layouts/no_data.tsx @@ -6,29 +6,25 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPageContent } from '@elastic/eui'; import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; -interface LayoutProps { +interface LayoutProps extends RouteComponentProps { + children: React.ReactNode; title: string | React.ReactNode; actionSection?: React.ReactNode; - modalClosePath?: string; } -export const NoDataLayout: React.FC = withRouter( - ({ actionSection, title, modalClosePath, children, history }) => { - return ( - - - - {title}} - body={children} - actions={actionSection} - /> - - - - ); - } -) as any; +export const NoDataLayout = withRouter(({ actionSection, title, children }: LayoutProps) => ( + + + + {title}} + body={children} + actions={actionSection} + /> + + + +)); diff --git a/x-pack/legacy/plugins/beats_management/public/components/navigation/connected_link.tsx b/x-pack/legacy/plugins/beats_management/public/components/navigation/connected_link.tsx index 30d12c9ce10dee..947e22ee290895 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/navigation/connected_link.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/navigation/connected_link.tsx @@ -6,22 +6,24 @@ import React from 'react'; import { EuiLink } from '@elastic/eui'; -import { Link, withRouter } from 'react-router-dom'; +import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; -export function ConnectedLinkComponent({ +interface ConnectedLinkComponent extends RouteComponentProps { + location: any; + path: string; + disabled: boolean; + query: any; + [key: string]: any; +} + +export const ConnectedLinkComponent = ({ location, path, query, disabled, children, ...props -}: { - location: any; - path: string; - disabled: boolean; - query: any; - [key: string]: any; -}) { +}: ConnectedLinkComponent) => { if (disabled) { return ; } @@ -36,6 +38,6 @@ export function ConnectedLinkComponent({ className={`euiLink euiLink--primary ${props.className || ''}`} /> ); -} +}; -export const ConnectedLink = withRouter(ConnectedLinkComponent); +export const ConnectedLink = withRouter(ConnectedLinkComponent); diff --git a/x-pack/legacy/plugins/beats_management/public/containers/with_url_state.tsx b/x-pack/legacy/plugins/beats_management/public/containers/with_url_state.tsx index 29581508d2ad50..71e9163fe22e7d 100644 --- a/x-pack/legacy/plugins/beats_management/public/containers/with_url_state.tsx +++ b/x-pack/legacy/plugins/beats_management/public/containers/with_url_state.tsx @@ -6,7 +6,7 @@ import { parse, stringify } from 'querystring'; import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { FlatObject } from '../frontend_types'; import { RendererFunction } from '../utils/typed_react'; @@ -22,9 +22,7 @@ export interface URLStateProps { ) => void; urlState: URLState; } -interface ComponentProps { - history: any; - match: any; +interface ComponentProps extends RouteComponentProps { children: RendererFunction>; } @@ -66,8 +64,8 @@ export class WithURLStateComponent extends React.Compon } const search: string = stringify({ - ...(pastState as any), - ...(newState as any), + ...pastState, + ...newState, }); const newLocation = { @@ -86,16 +84,12 @@ export class WithURLStateComponent extends React.Compon }); }; } -export const WithURLState = withRouter(WithURLStateComponent); +export const WithURLState = withRouter(WithURLStateComponent); -export function withUrlState( - UnwrappedComponent: React.ComponentType -): React.FC { - return (origProps: OP) => { - return ( - - {(URLProps: URLStateProps) => } - - ); - }; +export function withUrlState(UnwrappedComponent: React.ComponentType) { + return (origProps: OP) => ( + + {(URLProps: URLStateProps) => } + + ); } diff --git a/x-pack/legacy/plugins/canvas/server/routes/custom_elements.ts b/x-pack/legacy/plugins/canvas/server/routes/custom_elements.ts deleted file mode 100644 index 3fe78befd2f507..00000000000000 --- a/x-pack/legacy/plugins/canvas/server/routes/custom_elements.ts +++ /dev/null @@ -1,192 +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 { omit } from 'lodash'; -import { SavedObjectsClientContract } from 'src/core/server'; - -import { API_ROUTE_CUSTOM_ELEMENT, CUSTOM_ELEMENT_TYPE } from '../../common/lib/constants'; -import { getId } from '../../public/lib/get_id'; -// @ts-ignore Untyped Local -import { formatResponse as formatRes } from '../lib/format_response'; -import { CustomElement } from '../../types'; - -import { CoreSetup } from '../shim'; - -// Exclude ID attribute for the type used for SavedObjectClient -type CustomElementAttributes = Pick> & { - '@timestamp': string; - '@created': string; -}; - -interface CustomElementRequestFacade { - getSavedObjectsClient: () => SavedObjectsClientContract; -} - -type CustomElementRequest = CustomElementRequestFacade & { - params: { - id: string; - }; - payload: CustomElement; -}; - -type FindCustomElementRequest = CustomElementRequestFacade & { - query: { - name: string; - page: number; - perPage: number; - }; -}; - -export function customElements( - route: CoreSetup['http']['route'], - elasticsearch: CoreSetup['elasticsearch'] -) { - // @ts-ignore: errors not on Cluster type - const { errors: esErrors } = elasticsearch.getCluster('data'); - - const routePrefix = API_ROUTE_CUSTOM_ELEMENT; - const formatResponse = formatRes(esErrors); - - const createCustomElement = (req: CustomElementRequest) => { - const savedObjectsClient = req.getSavedObjectsClient(); - - if (!req.payload) { - return Promise.reject(boom.badRequest('A custom element payload is required')); - } - - const now = new Date().toISOString(); - const { id, ...payload } = req.payload; - return savedObjectsClient.create( - CUSTOM_ELEMENT_TYPE, - { - ...payload, - '@timestamp': now, - '@created': now, - }, - { id: id || getId('custom-element') } - ); - }; - - const updateCustomElement = (req: CustomElementRequest, newPayload?: CustomElement) => { - const savedObjectsClient = req.getSavedObjectsClient(); - const { id } = req.params; - const payload = newPayload ? newPayload : req.payload; - - const now = new Date().toISOString(); - - return savedObjectsClient - .get(CUSTOM_ELEMENT_TYPE, id) - .then(element => { - // TODO: Using create with force over-write because of version conflict issues with update - return savedObjectsClient.create( - CUSTOM_ELEMENT_TYPE, - { - ...element.attributes, - ...omit(payload, 'id'), // never write the id property - '@timestamp': now, // always update the modified time - '@created': element.attributes['@created'], // ensure created is not modified - }, - { overwrite: true, id } - ); - }); - }; - - const deleteCustomElement = (req: CustomElementRequest) => { - const savedObjectsClient = req.getSavedObjectsClient(); - const { id } = req.params; - - return savedObjectsClient.delete(CUSTOM_ELEMENT_TYPE, id); - }; - - const findCustomElement = (req: FindCustomElementRequest) => { - const savedObjectsClient = req.getSavedObjectsClient(); - const { name, page, perPage } = req.query; - - return savedObjectsClient.find({ - type: CUSTOM_ELEMENT_TYPE, - sortField: '@timestamp', - sortOrder: 'desc', - search: name ? `${name}* | ${name}` : '*', - searchFields: ['name'], - fields: ['id', 'name', 'displayName', 'help', 'image', 'content', '@created', '@timestamp'], - page, - perPage, - }); - }; - - const getCustomElementById = (req: CustomElementRequest) => { - const savedObjectsClient = req.getSavedObjectsClient(); - const { id } = req.params; - return savedObjectsClient.get(CUSTOM_ELEMENT_TYPE, id); - }; - - // get custom element by id - route({ - method: 'GET', - path: `${routePrefix}/{id}`, - handler: (req: CustomElementRequest) => - getCustomElementById(req) - .then(obj => ({ id: obj.id, ...obj.attributes })) - .then(formatResponse) - .catch(formatResponse), - }); - - // create custom element - route({ - method: 'POST', - path: routePrefix, - // @ts-ignore config option missing on route method type - config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit - handler: (req: CustomElementRequest) => - createCustomElement(req) - .then(() => ({ ok: true })) - .catch(formatResponse), - }); - - // update custom element - route({ - method: 'PUT', - path: `${routePrefix}/{id}`, - // @ts-ignore config option missing on route method type - config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit - handler: (req: CustomElementRequest) => - updateCustomElement(req) - .then(() => ({ ok: true })) - .catch(formatResponse), - }); - - // delete custom element - route({ - method: 'DELETE', - path: `${routePrefix}/{id}`, - handler: (req: CustomElementRequest) => - deleteCustomElement(req) - .then(() => ({ ok: true })) - .catch(formatResponse), - }); - - // find custom elements - route({ - method: 'GET', - path: `${routePrefix}/find`, - handler: (req: FindCustomElementRequest) => - findCustomElement(req) - .then(formatResponse) - .then(resp => { - return { - total: resp.total, - customElements: resp.saved_objects.map(hit => ({ id: hit.id, ...hit.attributes })), - }; - }) - .catch(() => { - return { - total: 0, - customElements: [], - }; - }), - }); -} diff --git a/x-pack/legacy/plugins/canvas/server/routes/index.ts b/x-pack/legacy/plugins/canvas/server/routes/index.ts index 515d5b5e895edf..2f6b706fc7edbc 100644 --- a/x-pack/legacy/plugins/canvas/server/routes/index.ts +++ b/x-pack/legacy/plugins/canvas/server/routes/index.ts @@ -5,12 +5,10 @@ */ import { esFields } from './es_fields'; -import { customElements } from './custom_elements'; import { shareableWorkpads } from './shareables'; import { CoreSetup } from '../shim'; export function routes(setup: CoreSetup): void { - customElements(setup.http.route, setup.elasticsearch); esFields(setup.http.route, setup.elasticsearch); shareableWorkpads(setup.http.route); } diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js index 37d1305d667bf0..5e893b7d9208c7 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js @@ -6,7 +6,7 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { Route, Switch, Redirect } from 'react-router-dom'; +import { Route, Switch, Redirect, withRouter } from 'react-router-dom'; import { fatalError } from 'ui/notify'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -34,15 +34,13 @@ import { FollowerIndexEdit, } from './sections'; -export class App extends Component { - static contextTypes = { - router: PropTypes.shape({ - history: PropTypes.shape({ - push: PropTypes.func.isRequired, - createHref: PropTypes.func.isRequired - }).isRequired - }).isRequired - } +class AppComponent extends Component { + static propTypes = { + history: PropTypes.shape({ + push: PropTypes.func.isRequired, + createHref: PropTypes.func.isRequired, + }).isRequired, + }; constructor(...args) { super(...args); @@ -99,8 +97,13 @@ export class App extends Component { } registerRouter() { - const { router } = this.context; - routing.reactRouter = router; + const { history, location } = this.props; + routing.reactRouter = { + history, + route: { + location, + }, + }; } render() { @@ -196,3 +199,5 @@ export class App extends Component { ); } } + +export const App = withRouter(AppComponent); diff --git a/x-pack/legacy/plugins/fleet/public/components/layouts/no_data.tsx b/x-pack/legacy/plugins/fleet/public/components/layouts/no_data.tsx index e525ea4be46e0d..8d2edf9c29e9ee 100644 --- a/x-pack/legacy/plugins/fleet/public/components/layouts/no_data.tsx +++ b/x-pack/legacy/plugins/fleet/public/components/layouts/no_data.tsx @@ -6,29 +6,25 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPageContent } from '@elastic/eui'; import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; -interface LayoutProps { +interface LayoutProps extends RouteComponentProps { + children: React.ReactNode; title: string | React.ReactNode; actionSection?: React.ReactNode; - modalClosePath?: string; } -export const NoDataLayout: React.FC = withRouter( - ({ actionSection, title, modalClosePath, children, history }) => { - return ( - - - - {title}} - body={children} - actions={actionSection} - /> - - - - ); - } -) as any; +export const NoDataLayout = withRouter(({ actionSection, title, children }: LayoutProps) => ( + + + + {title}} + body={children} + actions={actionSection} + /> + + + +)); diff --git a/x-pack/legacy/plugins/fleet/public/components/navigation/connected_link.tsx b/x-pack/legacy/plugins/fleet/public/components/navigation/connected_link.tsx index 30d12c9ce10dee..947e22ee290895 100644 --- a/x-pack/legacy/plugins/fleet/public/components/navigation/connected_link.tsx +++ b/x-pack/legacy/plugins/fleet/public/components/navigation/connected_link.tsx @@ -6,22 +6,24 @@ import React from 'react'; import { EuiLink } from '@elastic/eui'; -import { Link, withRouter } from 'react-router-dom'; +import { Link, withRouter, RouteComponentProps } from 'react-router-dom'; -export function ConnectedLinkComponent({ +interface ConnectedLinkComponent extends RouteComponentProps { + location: any; + path: string; + disabled: boolean; + query: any; + [key: string]: any; +} + +export const ConnectedLinkComponent = ({ location, path, query, disabled, children, ...props -}: { - location: any; - path: string; - disabled: boolean; - query: any; - [key: string]: any; -}) { +}: ConnectedLinkComponent) => { if (disabled) { return ; } @@ -36,6 +38,6 @@ export function ConnectedLinkComponent({ className={`euiLink euiLink--primary ${props.className || ''}`} /> ); -} +}; -export const ConnectedLink = withRouter(ConnectedLinkComponent); +export const ConnectedLink = withRouter(ConnectedLinkComponent); diff --git a/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx b/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx index 32a4be66a07943..8ce834c365fe00 100644 --- a/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx +++ b/x-pack/legacy/plugins/fleet/public/hooks/with_url_state.tsx @@ -6,7 +6,7 @@ import { parse, stringify } from 'querystring'; import React from 'react'; -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { FlatObject, RendererFunction } from '../../common/types/helpers'; type StateCallback = (previousState: T) => T; @@ -21,9 +21,7 @@ export interface URLStateProps { ) => void; urlState: URLState; } -interface ComponentProps { - history: any; - match: any; +interface ComponentProps extends RouteComponentProps { children: RendererFunction>; } @@ -65,8 +63,8 @@ export class WithURLStateComponent extends React.Compon } const search: string = stringify({ - ...(pastState as any), - ...(newState as any), + ...pastState, + ...newState, }); const newLocation = { @@ -85,16 +83,12 @@ export class WithURLStateComponent extends React.Compon }); }; } -export const WithURLState = withRouter(WithURLStateComponent); +export const WithURLState = withRouter(WithURLStateComponent); -export function withUrlState( - UnwrappedComponent: React.ComponentType -): React.FC { - return (origProps: OP) => { - return ( - - {(URLProps: URLStateProps) => } - - ); - }; +export function withUrlState(UnwrappedComponent: React.ComponentType) { + return (origProps: OP) => ( + + {(URLProps: URLStateProps) => } + + ); } diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx index 0506cde60bb661..a800b6421c0276 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx @@ -19,7 +19,6 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); @@ -34,7 +33,6 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); @@ -47,7 +45,6 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx index 7a63406bb419a0..5fa80c8efee73f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx @@ -35,7 +35,6 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); @@ -48,7 +47,6 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); @@ -61,7 +59,6 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); @@ -76,7 +73,6 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); @@ -93,7 +89,6 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); @@ -108,7 +103,6 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap b/x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap index 4524f66c0642c8..e21f034161a878 100644 --- a/x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap @@ -2,20 +2,6 @@ exports[`TooltipSelector should render component 1`] = `
- -
- -
-
- diff --git a/x-pack/legacy/plugins/maps/public/components/global_filter_checkbox.js b/x-pack/legacy/plugins/maps/public/components/global_filter_checkbox.js index e841fa573c9a54..56406ee9653fe1 100644 --- a/x-pack/legacy/plugins/maps/public/components/global_filter_checkbox.js +++ b/x-pack/legacy/plugins/maps/public/components/global_filter_checkbox.js @@ -6,13 +6,8 @@ import React from 'react'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -const label = i18n.translate('xpack.maps.layerPanel.applyGlobalQueryCheckboxLabel', { - defaultMessage: `Apply global filter to source`, -}); - -export function GlobalFilterCheckbox({ applyGlobalQuery, customLabel, setApplyGlobalQuery }) { +export function GlobalFilterCheckbox({ applyGlobalQuery, label, setApplyGlobalQuery }) { const onApplyGlobalQueryChange = event => { setApplyGlobalQuery(event.target.checked); }; @@ -22,7 +17,7 @@ export function GlobalFilterCheckbox({ applyGlobalQuery, customLabel, setApplyGl display="columnCompressedSwitch" > - -
- -
-
- - {this._renderProperties()} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap index 4377fa47254835..101716d297b81e 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/__snapshots__/view.test.js.snap @@ -97,7 +97,9 @@ exports[`LayerPanel is rendered 1`] = ` > - +
+ mockSourceSettings +
diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js index 0086c5067ba123..941694c19ad561 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/filter_editor.js @@ -16,11 +16,14 @@ import { EuiTextColor, EuiTextAlign, EuiButtonEmpty, + EuiFormRow, + EuiSwitch, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { indexPatternService } from '../../../kibana_services'; +import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; import { start as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; const { SearchBar } = data.ui; @@ -79,6 +82,14 @@ export class FilterEditor extends Component { this._close(); }; + _onFilterByMapBoundsChange = event => { + this.props.updateSourceProp(this.props.layer.getId(), 'filterByMapBounds', event.target.checked); + }; + + _onApplyGlobalQueryChange = applyGlobalQuery => { + this.props.updateSourceProp(this.props.layer.getId(), 'applyGlobalQuery', applyGlobalQuery); + }; + _renderQueryPopover() { const layerQuery = this.props.layer.getQuery(); const { uiSettings } = npStart.core; @@ -169,13 +180,29 @@ export class FilterEditor extends Component { } render() { + let filterByBoundsSwitch; + if (this.props.layer.getSource().isFilterByMapBoundsConfigurable()) { + filterByBoundsSwitch = ( + + + + ); + } + return (
@@ -185,6 +212,18 @@ export class FilterEditor extends Component { {this._renderQuery()} {this._renderQueryPopover()} + + + + {filterByBoundsSwitch} + +
); } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/index.js index 4fc69690485fb9..127f2ca70ab935 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/filter_editor/index.js @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; import { FilterEditor } from './filter_editor'; import { getSelectedLayer } from '../../../selectors/map_selectors'; -import { setLayerQuery } from '../../../actions/map_actions'; +import { setLayerQuery, updateSourceProp } from '../../../actions/map_actions'; function mapStateToProps(state = {}) { return { @@ -19,7 +19,8 @@ function mapDispatchToProps(dispatch) { return { setLayerQuery: (layerId, query) => { dispatch(setLayerQuery(layerId, query)); - } + }, + updateSourceProp: (id, propName, value) => dispatch(updateSourceProp(id, propName, value)), }; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js index ebdeb22289a9af..85fdcc90278542 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/index.js @@ -8,7 +8,8 @@ import { connect } from 'react-redux'; import { LayerPanel } from './view'; import { getSelectedLayer } from '../../selectors/map_selectors'; import { - fitToLayerExtent + fitToLayerExtent, + updateSourceProp, } from '../../actions/map_actions'; function mapStateToProps(state = {}) { @@ -21,7 +22,8 @@ function mapDispatchToProps(dispatch) { return { fitToBounds: (layerId) => { dispatch(fitToLayerExtent(layerId)); - } + }, + updateSourceProp: (id, propName, value) => dispatch(updateSourceProp(id, propName, value)), }; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js index 68d4656880666c..b23764f1c7e338 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join.js @@ -217,7 +217,7 @@ export class Join extends Component { diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/__snapshots__/source_settings.test.js.snap b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/__snapshots__/source_settings.test.js.snap deleted file mode 100644 index 4d2cbcb012b411..00000000000000 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/__snapshots__/source_settings.test.js.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Should render source settings editor 1`] = ` - - - -
- -
-
- -
- mockSourceEditor -
-
- -
-`; - -exports[`should render nothing when source has no editor 1`] = `""`; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/index.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/index.js deleted file mode 100644 index 18cda96aeb1e88..00000000000000 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/index.js +++ /dev/null @@ -1,25 +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 { connect } from 'react-redux'; -import { SourceSettings } from './source_settings'; -import { getSelectedLayer } from '../../../selectors/map_selectors'; -import { updateSourceProp } from '../../../actions/map_actions'; - -function mapStateToProps(state = {}) { - return { - layer: getSelectedLayer(state) - }; -} - -function mapDispatchToProps(dispatch) { - return { - updateSourceProp: (id, propName, value) => dispatch(updateSourceProp(id, propName, value)), - }; -} - -const connectedSourceSettings = connect(mapStateToProps, mapDispatchToProps)(SourceSettings); -export { connectedSourceSettings as SourceSettings }; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/source_settings.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/source_settings.js deleted file mode 100644 index 9791931c3ee77c..00000000000000 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/source_settings.js +++ /dev/null @@ -1,44 +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 React, { Fragment } from 'react'; - -import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -export function SourceSettings({ layer, updateSourceProp }) { - const onSourceChange = ({ propName, value }) => { - updateSourceProp(layer.getId(), propName, value); - }; - - const sourceSettingsEditor = layer.renderSourceSettingsEditor({ onChange: onSourceChange }); - - if (!sourceSettingsEditor) { - return null; - } - - return ( - - - -
- -
-
- - - - {sourceSettingsEditor} -
- - -
- ); -} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/source_settings.test.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/source_settings.test.js deleted file mode 100644 index 090d30054ba814..00000000000000 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/source_settings/source_settings.test.js +++ /dev/null @@ -1,42 +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 React from 'react'; -import { shallow } from 'enzyme'; - -import { SourceSettings } from './source_settings'; - -test('Should render source settings editor', () => { - const mockLayer = { - renderSourceSettingsEditor: () => { - return (
mockSourceEditor
); - }, - }; - const component = shallow( - - ); - - expect(component) - .toMatchSnapshot(); -}); - -test('should render nothing when source has no editor', () => { - const mockLayer = { - renderSourceSettingsEditor: () => { - return null; - }, - }; - const component = shallow( - - ); - - expect(component) - .toMatchSnapshot(); -}); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.js index 78cb8aa827e35b..492c891d1db2d5 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.js @@ -11,7 +11,6 @@ import { JoinEditor } from './join_editor'; import { FlyoutFooter } from './flyout_footer'; import { LayerErrors } from './layer_errors'; import { LayerSettings } from './layer_settings'; -import { SourceSettings } from './source_settings'; import { StyleSettings } from './style_settings'; import { EuiButtonIcon, @@ -96,6 +95,10 @@ export class LayerPanel extends React.Component { } } + _onSourceChange = ({ propName, value }) => { + this.props.updateSourceProp(this.props.selectedLayer.getId(), propName, value); + }; + _renderFilterSection() { if (!this.props.selectedLayer.supportsElasticsearchFilters()) { return null; @@ -213,7 +216,7 @@ export class LayerPanel extends React.Component { - + {this.props.selectedLayer.renderSourceSettingsEditor({ onChange: this._onSourceChange })} {this._renderFilterSection()} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.test.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.test.js index fe891d92defbe8..8e97c58b695083 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.test.js @@ -40,12 +40,6 @@ jest.mock('./layer_settings', () => ({ } })); -jest.mock('./source_settings', () => ({ - SourceSettings: () => { - return (
mockSourceSettings
); - } -})); - import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; @@ -62,11 +56,13 @@ const mockLayer = { isJoinable: () => { return true; }, supportsElasticsearchFilters: () => { return false; }, getLayerTypeIconName: () => { return 'vector'; }, + renderSourceSettingsEditor: () => { return (
mockSourceSettings
); }, }; const defaultProps = { selectedLayer: mockLayer, fitToBounds: () => {}, + updateSourceProp: () => {}, }; describe('LayerPanel', () => { diff --git a/x-pack/legacy/plugins/maps/public/embeddable/README.md b/x-pack/legacy/plugins/maps/public/embeddable/README.md index c2952de82c223f..82f83f1bfcf4a2 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/README.md +++ b/x-pack/legacy/plugins/maps/public/embeddable/README.md @@ -79,3 +79,101 @@ const eventHandlers = { const mapEmbeddable = await factory.createFromState(state, input, parent, renderTooltipContent, eventHandlers); ``` + + +#### Passing in geospatial data +You can pass geospatial data into the Map embeddable by configuring the layerList parameter with a layer with `GEOJSON_FILE` source. +Geojson sources will not update unless you modify `__featureCollection` property by calling the `setLayerList` method. + +``` +const factory = new MapEmbeddableFactory(); +const state = { + layerList: [ + { + 'id': 'gaxya', + 'label': 'My geospatial data', + 'minZoom': 0, + 'maxZoom': 24, + 'alpha': 1, + 'sourceDescriptor': { + 'id': 'b7486', + 'type': 'GEOJSON_FILE', + '__featureCollection': { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], [10, 10], [10, 0], [0, 0] + ] + ] + }, + "properties": { + "name": "null island", + "another_prop": "something else interesting" + } + } + ] + } + }, + 'visible': true, + 'style': { + 'type': 'VECTOR', + 'properties': {} + }, + 'type': 'VECTOR' + } + ], + title: 'my map', +} +const input = { + hideFilterActions: true, + isLayerTOCOpen: false, + openTOCDetails: ['tfi3f', 'edh66'], + mapCenter: { lat: 0.0, lon: 0.0, zoom: 7 } +} +const mapEmbeddable = await factory.createFromState(state, input, parent); + +mapEmbeddable.setLayerList([ + { + 'id': 'gaxya', + 'label': 'My geospatial data', + 'minZoom': 0, + 'maxZoom': 24, + 'alpha': 1, + 'sourceDescriptor': { + 'id': 'b7486', + 'type': 'GEOJSON_FILE', + '__featureCollection': { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [35, 35], [45, 45], [45, 35], [35, 35] + ] + ] + }, + "properties": { + "name": "null island", + "another_prop": "something else interesting" + } + } + ] + } + }, + 'visible': true, + 'style': { + 'type': 'VECTOR', + 'properties': {} + }, + 'type': 'VECTOR' + } +]); +``` diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js index 18c4c78b969744..2c203ffc8fc632 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js @@ -165,6 +165,11 @@ export class MapEmbeddable extends Embeddable { }); } + async setLayerList(layerList) { + this._layerList = layerList; + return await this._store.dispatch(replaceLayerList(this._layerList)); + } + addFilters = filters => { npStart.plugins.uiActions.executeTriggerActions(APPLY_FILTER_TRIGGER, { embeddable: this, diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js index f901c8b93e8cd5..65704b4cd83ad5 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { TooltipSelector } from '../../../components/tooltip_selector'; import { getEMSClient } from '../../../meta'; +import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; export class UpdateSourceEditor extends Component { @@ -53,13 +55,29 @@ export class UpdateSourceEditor extends Component { }; render() { - return ( - + + + +
+ +
+
+ + + + +
+ + +
); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index de1b47ea28a910..fbc1703f860037 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -85,7 +85,6 @@ export class ESGeoGridSource extends AbstractESAggSource { metrics={this._descriptor.metrics} renderAs={this._descriptor.requestType} resolution={this._descriptor.resolution} - applyGlobalQuery={this._descriptor.applyGlobalQuery} /> ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js index cc1e53dc5cb3f8..8a41d477838640 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js @@ -12,8 +12,7 @@ import { indexPatternService } from '../../../kibana_services'; import { ResolutionEditor } from './resolution_editor'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiSpacer, EuiTitle } from '@elastic/eui'; -import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; +import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { isMetricCountable } from '../../util/is_metric_countable'; export class UpdateSourceEditor extends Component { @@ -63,11 +62,7 @@ export class UpdateSourceEditor extends Component { this.props.onChange({ propName: 'resolution', value: e }); }; - _onApplyGlobalQueryChange = applyGlobalQuery => { - this.props.onChange({ propName: 'applyGlobalQuery', value: applyGlobalQuery }); - }; - - _renderMetricsEditor() { + _renderMetricsPanel() { const metricsFilter = this.props.renderAs === RENDER_AS.HEATMAP ? metric => { @@ -77,13 +72,13 @@ export class UpdateSourceEditor extends Component { : null; const allowMultipleMetrics = this.props.renderAs !== RENDER_AS.HEATMAP; return ( -
- + +
- + -
+ ); } render() { return ( - - + {this._renderMetricsPanel()} + - {this._renderMetricsEditor()} + + +
+ +
+
+ + +
+ -
); } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js index 2df7dfc3e07641..f0b09f24800841 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component } from 'react'; +import React, { Component, Fragment } from 'react'; import { MetricsEditor } from '../../../components/metrics_editor'; import { indexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; -import { EuiTitle, EuiSpacer } from '@elastic/eui'; +import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; export class UpdateSourceEditor extends Component { state = { @@ -56,30 +55,25 @@ export class UpdateSourceEditor extends Component { this.props.onChange({ propName: 'metrics', value: metrics }); }; - _onApplyGlobalQueryChange = applyGlobalQuery => { - this.props.onChange({ propName: 'applyGlobalQuery', value: applyGlobalQuery }); - }; - render() { return ( - <> - -
- -
-
+ + + +
+ +
+
+ + +
- - - +
); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap index 1e064fdb0dd7d2..85c8d0b354a130 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap @@ -2,344 +2,386 @@ exports[`should enable sort order select when sort field provided 1`] = ` - + + +
+ +
+
+ -
- - + + + - - - - - + - - - - - + + - - - + + + + + + - - + + + +
`; exports[`should render top hits form when useTopHits is true 1`] = ` - + + +
+ +
+
+ -
- - + + + - - - - - + - - - - - - - - + + - - - - - - + + + + + + - - + + + + + + + + + +
`; exports[`should render update source editor 1`] = ` - + + +
+ +
+
+ -
- - + + + - - + - - - - - - - - + + - - - + + + + + + - - + + + +
`; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index 453a1851e47aa8..2a47cb2213be1b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -85,14 +85,12 @@ export class ESSearchSource extends AbstractESSource { source={this} indexPatternId={this._descriptor.indexPatternId} onChange={onChange} - filterByMapBounds={this._descriptor.filterByMapBounds} tooltipFields={this._tooltipFields} sortField={this._descriptor.sortField} sortOrder={this._descriptor.sortOrder} useTopHits={this._descriptor.useTopHits} topHitsSplitField={this._descriptor.topHitsSplitField} topHitsSize={this._descriptor.topHitsSize} - applyGlobalQuery={this._descriptor.applyGlobalQuery} /> ); } @@ -405,7 +403,7 @@ export class ESSearchSource extends AbstractESSource { searchSource.setField('size', 1); const query = { language: 'kuery', - query: `_id:"${docId}" and _index:${index}` + query: `_id:"${docId}" and _index:"${index}"` }; searchSource.setField('query', query); searchSource.setField('fields', this._getTooltipPropertyNames()); @@ -445,6 +443,10 @@ export class ESSearchSource extends AbstractESSource { return _.get(this._descriptor, 'filterByMapBounds', false); } + isFilterByMapBoundsConfigurable() { + return true; + } + async getLeftJoinFields() { const indexPattern = await this.getIndexPattern(); // Left fields are retrieved from _source. diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js index 1b4e999c29d0a1..b7c332b4c96cbf 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js @@ -9,13 +9,14 @@ import PropTypes from 'prop-types'; import { EuiFormRow, EuiSwitch, - EuiFlexGroup, - EuiFlexItem, EuiSelect, + EuiTitle, + EuiPanel, + EuiSpacer, + EuiHorizontalRule, } from '@elastic/eui'; import { SingleFieldSelect } from '../../../components/single_field_select'; import { TooltipSelector } from '../../../components/tooltip_selector'; -import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; import { indexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; @@ -23,12 +24,12 @@ import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; import { ValidatedRange } from '../../../components/validated_range'; import { SORT_ORDER } from '../../../../common/constants'; import { ESDocField } from '../../fields/es_doc_field'; +import { FormattedMessage } from '@kbn/i18n/react'; export class UpdateSourceEditor extends Component { static propTypes = { indexPatternId: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, - filterByMapBounds: PropTypes.bool.isRequired, tooltipFields: PropTypes.arrayOf(PropTypes.object).isRequired, sortField: PropTypes.string, sortOrder: PropTypes.string.isRequired, @@ -94,10 +95,6 @@ export class UpdateSourceEditor extends Component { this.props.onChange({ propName: 'tooltipProperties', value: propertyNames }); }; - _onFilterByMapBoundsChange = event => { - this.props.onChange({ propName: 'filterByMapBounds', value: event.target.checked }); - }; - onUseTopHitsChange = event => { this.props.onChange({ propName: 'useTopHits', value: event.target.checked }); }; @@ -118,13 +115,27 @@ export class UpdateSourceEditor extends Component { this.props.onChange({ propName: 'topHitsSize', value: size }); }; - _onApplyGlobalQueryChange = applyGlobalQuery => { - this.props.onChange({ propName: 'applyGlobalQuery', value: applyGlobalQuery }); - }; - renderTopHitsForm() { + const topHitsSwitch = ( + + + + ); + if (!this.props.useTopHits) { - return null; + return topHitsSwitch; } let sizeSlider; @@ -134,7 +145,7 @@ export class UpdateSourceEditor extends Component { label={i18n.translate('xpack.maps.source.esSearch.topHitsSizeLabel', { defaultMessage: 'Documents per entity', })} - display="rowCompressed" + display="columnCompressed" > + {topHitsSwitch} - - - + + +
+ +
+
+ + + + +
+ ); + } + + _renderSortPanel() { + return ( + + +
+ +
+
+ + - - - - - - - - + - - + + {this.renderTopHitsForm()} +
+ ); + } - - - + render() { + return ( + - + {this._renderTooltipsPanel()} + + + {this._renderSortPanel()} + ); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js index e255e2478a37df..a586bc9fb53b20 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js @@ -79,6 +79,10 @@ export class AbstractVectorSource extends AbstractSource { return false; } + isFilterByMapBoundsConfigurable() { + return false; + } + isBoundsAware() { return false; } diff --git a/x-pack/legacy/plugins/observability/README.md b/x-pack/legacy/plugins/observability/README.md deleted file mode 100644 index f7d8365fe6c802..00000000000000 --- a/x-pack/legacy/plugins/observability/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Observability Shared Resources - -This "faux" plugin serves as a place to statically share resources, helpers, and components across observability plugins. There is some discussion still happening about the best way to do this, but this is one suggested method that will work for now and has the benefit of adopting our pre-defined build and compile tooling out of the box. - -Files found here can be imported from any other x-pack plugin, with the caveat that these shared components should all be exposed from either `public/index` or `server/index` so that the platform can attempt to monitor breaking changes in this shared API. - -# for a file found at `x-pack/legacy/plugins/infra/public/components/Example.tsx` - -```ts -import { ExampleSharedComponent } from '../../../observability/public'; -``` - -### Plugin registration and config - -There is no plugin registration code or config in this folder because it's a "faux" plugin only being used to share code between other plugins. Plugins using this code do not need to register a dependency on this plugin unless this plugin ever exports functionality that relies on Kibana core itself (rather than being static DI components and utilities only, as it is now). - -### Directory structure - -Code meant to be shared by the UI should live in `public/` and be explicity exported from `public/index` while server helpers etc should live in `server/` and be explicitly exported from `server/index`. Code that needs to be shared across client and server should be exported from both places (not put in `common`, etc). diff --git a/x-pack/legacy/plugins/observability/public/context/kibana_core.tsx b/x-pack/legacy/plugins/observability/public/context/kibana_core.tsx deleted file mode 100644 index ab936ed689edfc..00000000000000 --- a/x-pack/legacy/plugins/observability/public/context/kibana_core.tsx +++ /dev/null @@ -1,25 +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 React, { createContext, useContext } from 'react'; -import { LegacyCoreStart } from '../../../../../../src/core/public'; - -interface AppMountContext { - core: LegacyCoreStart; -} - -// TODO: Replace CoreStart/CoreSetup with AppMountContext -// see: https://github.com/elastic/kibana/pull/41007 - -export const KibanaCoreContext = createContext({} as AppMountContext['core']); - -export const KibanaCoreContextProvider: React.FC<{ core: AppMountContext['core'] }> = props => ( - -); - -export function useKibanaCore() { - return useContext(KibanaCoreContext); -} diff --git a/x-pack/legacy/plugins/observability/public/index.tsx b/x-pack/legacy/plugins/observability/public/index.tsx deleted file mode 100644 index 49e5a6d787a553..00000000000000 --- a/x-pack/legacy/plugins/observability/public/index.tsx +++ /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 { KibanaCoreContext, KibanaCoreContextProvider, useKibanaCore } from './context/kibana_core'; -import { ExampleSharedComponent } from './components/example_shared_component'; - -export { ExampleSharedComponent, KibanaCoreContext, KibanaCoreContextProvider, useKibanaCore }; diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/app.js b/x-pack/legacy/plugins/remote_clusters/public/app/app.js index 6fea66ad03c9c4..483b2f5b97e273 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/app.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/app.js @@ -6,21 +6,19 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Switch, Route, Redirect } from 'react-router-dom'; +import { Switch, Route, Redirect, withRouter } from 'react-router-dom'; import { CRUD_APP_BASE_PATH, UIM_APP_LOAD } from './constants'; import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services'; import { RemoteClusterList, RemoteClusterAdd, RemoteClusterEdit } from './sections'; -export class App extends Component { - static contextTypes = { - router: PropTypes.shape({ - history: PropTypes.shape({ - push: PropTypes.func.isRequired, - createHref: PropTypes.func.isRequired - }).isRequired - }).isRequired - } +class AppComponent extends Component { + static propTypes = { + history: PropTypes.shape({ + push: PropTypes.func.isRequired, + createHref: PropTypes.func.isRequired, + }).isRequired, + }; constructor(...args) { super(...args); @@ -29,8 +27,11 @@ export class App extends Component { registerRouter() { // Share the router with the app without requiring React or context. - const { router } = this.context; - registerRouter(router); + const { history, location } = this.props; + registerRouter({ + history, + route: { location }, + }); } componentDidMount() { @@ -56,3 +57,5 @@ export class App extends Component { ); } } + +export const App = withRouter(AppComponent); diff --git a/x-pack/legacy/plugins/reporting/common/constants.ts b/x-pack/legacy/plugins/reporting/common/constants.ts index 723320e74bfbd8..03b2d51c7b3965 100644 --- a/x-pack/legacy/plugins/reporting/common/constants.ts +++ b/x-pack/legacy/plugins/reporting/common/constants.ts @@ -50,3 +50,9 @@ export const PNG_JOB_TYPE = 'PNG'; export const CSV_JOB_TYPE = 'csv'; export const CSV_FROM_SAVEDOBJECT_JOB_TYPE = 'csv_from_savedobject'; export const USES_HEADLESS_JOB_TYPES = [PDF_JOB_TYPE, PNG_JOB_TYPE]; + +export const LICENSE_TYPE_TRIAL = 'trial'; +export const LICENSE_TYPE_BASIC = 'basic'; +export const LICENSE_TYPE_STANDARD = 'standard'; +export const LICENSE_TYPE_GOLD = 'gold'; +export const LICENSE_TYPE_PLATINUM = 'platinum'; diff --git a/x-pack/legacy/plugins/reporting/common/export_types_registry.js b/x-pack/legacy/plugins/reporting/common/export_types_registry.js deleted file mode 100644 index 39abd8911e751f..00000000000000 --- a/x-pack/legacy/plugins/reporting/common/export_types_registry.js +++ /dev/null @@ -1,64 +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 { isString } from 'lodash'; - -export class ExportTypesRegistry { - - constructor() { - this._map = new Map(); - } - - register(item) { - if (!isString(item.id)) { - throw new Error(`'item' must have a String 'id' property `); - } - - if (this._map.has(item.id)) { - throw new Error(`'item' with id ${item.id} has already been registered`); - } - - this._map.set(item.id, item); - } - - getAll() { - return this._map.values(); - } - - getSize() { - return this._map.size; - } - - getById(id) { - if (!this._map.has(id)) { - throw new Error(`Unknown id ${id}`); - } - - return this._map.get(id); - } - - get(callback) { - let result; - for (const value of this._map.values()) { - if (!callback(value)) { - continue; - } - - if (result) { - throw new Error('Found multiple items matching predicate.'); - } - - result = value; - } - - if (!result) { - throw new Error('Found no items matching predicate'); - } - - return result; - } - -} diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts index 2b4411584d752a..152ef32e331b98 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts @@ -6,8 +6,12 @@ import * as Rx from 'rxjs'; import { first, mergeMap } from 'rxjs/operators'; -import { ServerFacade, CaptureConfig } from '../../../../types'; -import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver'; +import { + ServerFacade, + CaptureConfig, + HeadlessChromiumDriverFactory, + HeadlessChromiumDriver as HeadlessBrowser, +} from '../../../../types'; import { ElementsPositionAndAttribute, ScreenshotResults, @@ -26,10 +30,12 @@ import { getElementPositionAndAttributes } from './get_element_position_data'; import { getScreenshots } from './get_screenshots'; import { skipTelemetry } from './skip_telemetry'; -export function screenshotsObservableFactory(server: ServerFacade) { +export function screenshotsObservableFactory( + server: ServerFacade, + browserDriverFactory: HeadlessChromiumDriverFactory +) { const config = server.config(); const captureConfig: CaptureConfig = config.get('xpack.reporting.capture'); - const { browserDriverFactory } = server.plugins.reporting!; return function screenshotsObservable({ logger, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv/index.ts new file mode 100644 index 00000000000000..4f8aeb2be0c993 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/csv/index.ts @@ -0,0 +1,39 @@ +/* + * 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 { + CSV_JOB_TYPE as jobType, + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, +} from '../../common/constants'; +import { ExportTypeDefinition, ESQueueCreateJobFn, ESQueueWorkerExecuteFn } from '../../types'; +import { metadata } from './metadata'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { JobParamsDiscoverCsv, JobDocPayloadDiscoverCsv } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsDiscoverCsv, + ESQueueCreateJobFn, + JobDocPayloadDiscoverCsv, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentExtension: 'csv', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/index.ts deleted file mode 100644 index d752cdcd9779d3..00000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/index.ts +++ /dev/null @@ -1,22 +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 { ExportTypesRegistry } from '../../../types'; -import { createJobFactory } from './create_job'; -import { executeJobFactory } from './execute_job'; -import { metadata } from '../metadata'; -import { CSV_JOB_TYPE as jobType } from '../../../common/constants'; - -export function register(registry: ExportTypesRegistry) { - registry.register({ - ...metadata, - jobType, - jobContentExtension: 'csv', - createJobFactory, - executeJobFactory, - validLicenses: ['trial', 'basic', 'standard', 'gold', 'platinum'], - }); -} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/index.ts index 68ad4a4b491559..4876cea0b1b28b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/index.ts @@ -4,9 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + CSV_FROM_SAVEDOBJECT_JOB_TYPE, + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, +} from '../../common/constants'; +import { ExportTypeDefinition, ImmediateCreateJobFn, ImmediateExecuteFn } from '../../types'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { metadata } from './metadata'; +import { JobParamsPanelCsv } from './types'; + /* * These functions are exported to share with the API route handler that * generates csv from saved object immediately on request. */ export { executeJobFactory } from './server/execute_job'; export { createJobFactory } from './server/create_job'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPanelCsv, + ImmediateCreateJobFn, + JobParamsPanelCsv, + ImmediateExecuteFn +> => ({ + ...metadata, + jobType: CSV_FROM_SAVEDOBJECT_JOB_TYPE, + jobContentExtension: 'csv', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_BASIC, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/index.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/index.ts deleted file mode 100644 index b614fd3c681b39..00000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/index.ts +++ /dev/null @@ -1,22 +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 { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; -import { ExportTypesRegistry } from '../../../types'; -import { metadata } from '../metadata'; -import { createJobFactory } from './create_job'; -import { executeJobFactory } from './execute_job'; - -export function register(registry: ExportTypesRegistry) { - registry.register({ - ...metadata, - jobType: CSV_FROM_SAVEDOBJECT_JOB_TYPE, - jobContentExtension: 'csv', - createJobFactory, - executeJobFactory, - validLicenses: ['trial', 'basic', 'standard', 'gold', 'platinum'], - }); -} diff --git a/x-pack/legacy/plugins/reporting/export_types/png/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/index.ts new file mode 100644 index 00000000000000..bc00bc428f3066 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/png/index.ts @@ -0,0 +1,38 @@ +/* + * 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 { + PNG_JOB_TYPE as jobType, + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, +} from '../../common/constants'; +import { ExportTypeDefinition, ESQueueCreateJobFn, ESQueueWorkerExecuteFn } from '../../types'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { metadata } from './metadata'; +import { JobParamsPNG, JobDocPayloadPNG } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPNG, + ESQueueCreateJobFn, + JobDocPayloadPNG, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentEncoding: 'base64', + jobContentExtension: 'PNG', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js index 867d537017f416..267c606449c3a8 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js @@ -68,7 +68,7 @@ test(`passes browserTimezone to generatePng`, async () => { const generatePngObservable = generatePngObservableFactory(); generatePngObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const browserTimezone = 'UTC'; await executeJob('pngJobId', { relativeUrl: '/app/kibana#/something', browserTimezone, headers: encryptedHeaders }, cancellationToken); @@ -76,7 +76,7 @@ test(`passes browserTimezone to generatePng`, async () => { }); test(`returns content_type of application/png`, async () => { - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const encryptedHeaders = await encryptHeaders({}); const generatePngObservable = generatePngObservableFactory(); @@ -93,7 +93,7 @@ test(`returns content of generatePng getBuffer base64 encoded`, async () => { const generatePngObservable = generatePngObservableFactory(); generatePngObservable.mockReturnValue(Rx.of(Buffer.from(testContent))); - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob('pngJobId', { relativeUrl: '/app/kibana#/something', timeRange: {}, headers: encryptedHeaders }, cancellationToken); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index 6678a83079d312..b289ae45dde678 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -7,7 +7,12 @@ import * as Rx from 'rxjs'; import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators'; import { PLUGIN_ID, PNG_JOB_TYPE } from '../../../../common/constants'; -import { ServerFacade, ExecuteJobFactory, ESQueueWorkerExecuteFn } from '../../../../types'; +import { + ServerFacade, + ExecuteJobFactory, + ESQueueWorkerExecuteFn, + HeadlessChromiumDriverFactory, +} from '../../../../types'; import { LevelLogger } from '../../../../server/lib'; import { decryptJobHeaders, @@ -18,10 +23,13 @@ import { import { JobDocPayloadPNG } from '../../types'; import { generatePngObservableFactory } from '../lib/generate_png'; -export const executeJobFactory: ExecuteJobFactory> = function executeJobFactoryFn(server: ServerFacade) { - const generatePngObservable = generatePngObservableFactory(server); +type QueuedPngExecutorFactory = ExecuteJobFactory>; + +export const executeJobFactory: QueuedPngExecutorFactory = function executeJobFactoryFn( + server: ServerFacade, + { browserDriverFactory }: { browserDriverFactory: HeadlessChromiumDriverFactory } +) { + const generatePngObservable = generatePngObservableFactory(server, browserDriverFactory); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PNG_JOB_TYPE, 'execute']); return function executeJob( diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/index.ts deleted file mode 100644 index a569719f02324b..00000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/index.ts +++ /dev/null @@ -1,23 +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 { ExportTypesRegistry } from '../../../types'; -import { createJobFactory } from './create_job'; -import { executeJobFactory } from './execute_job'; -import { metadata } from '../metadata'; -import { PNG_JOB_TYPE as jobType } from '../../../common/constants'; - -export function register(registry: ExportTypesRegistry) { - registry.register({ - ...metadata, - jobType, - jobContentEncoding: 'base64', - jobContentExtension: 'PNG', - createJobFactory, - executeJobFactory, - validLicenses: ['trial', 'standard', 'gold', 'platinum'], - }); -} diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts index 90aeea25db8583..e2b1474515786e 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts @@ -7,13 +7,16 @@ import * as Rx from 'rxjs'; import { map } from 'rxjs/operators'; import { LevelLogger } from '../../../../server/lib'; -import { ServerFacade, ConditionalHeaders } from '../../../../types'; +import { ServerFacade, HeadlessChromiumDriverFactory, ConditionalHeaders } from '../../../../types'; import { screenshotsObservableFactory } from '../../../common/lib/screenshots'; import { PreserveLayout } from '../../../common/layouts/preserve_layout'; import { LayoutParams } from '../../../common/layouts/layout'; -export function generatePngObservableFactory(server: ServerFacade) { - const screenshotsObservable = screenshotsObservableFactory(server); +export function generatePngObservableFactory( + server: ServerFacade, + browserDriverFactory: HeadlessChromiumDriverFactory +) { + const screenshotsObservable = screenshotsObservableFactory(server, browserDriverFactory); return function generatePngObservable( logger: LevelLogger, diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/index.ts new file mode 100644 index 00000000000000..99880c1237a7a5 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/index.ts @@ -0,0 +1,38 @@ +/* + * 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 { + PDF_JOB_TYPE as jobType, + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, +} from '../../common/constants'; +import { ExportTypeDefinition, ESQueueCreateJobFn, ESQueueWorkerExecuteFn } from '../../types'; +import { createJobFactory } from './server/create_job'; +import { executeJobFactory } from './server/execute_job'; +import { metadata } from './metadata'; +import { JobParamsPDF, JobDocPayloadPDF } from './types'; + +export const getExportType = (): ExportTypeDefinition< + JobParamsPDF, + ESQueueCreateJobFn, + JobDocPayloadPDF, + ESQueueWorkerExecuteFn +> => ({ + ...metadata, + jobType, + jobContentEncoding: 'base64', + jobContentExtension: 'pdf', + createJobFactory, + executeJobFactory, + validLicenses: [ + LICENSE_TYPE_TRIAL, + LICENSE_TYPE_STANDARD, + LICENSE_TYPE_GOLD, + LICENSE_TYPE_PLATINUM, + ], +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js index 8084c077ed23f3..6a5c47829fd19f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js @@ -67,7 +67,7 @@ test(`passes browserTimezone to generatePdf`, async () => { const generatePdfObservable = generatePdfObservableFactory(); generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(''))); - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const browserTimezone = 'UTC'; await executeJob('pdfJobId', { objects: [], browserTimezone, headers: encryptedHeaders }, cancellationToken); @@ -84,7 +84,7 @@ test(`passes browserTimezone to generatePdf`, async () => { }); test(`returns content_type of application/pdf`, async () => { - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const encryptedHeaders = await encryptHeaders({}); const generatePdfObservable = generatePdfObservableFactory(); @@ -104,7 +104,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const generatePdfObservable = generatePdfObservableFactory(); generatePdfObservable.mockReturnValue(Rx.of(Buffer.from(testContent))); - const executeJob = executeJobFactory(mockServer); + const executeJob = executeJobFactory(mockServer, { browserDriverFactory: {} }); const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob('pdfJobId', { objects: [], timeRange: {}, headers: encryptedHeaders }, cancellationToken); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index 543d5b587906d0..e2b3183464cf24 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -6,7 +6,12 @@ import * as Rx from 'rxjs'; import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators'; -import { ExecuteJobFactory, ESQueueWorkerExecuteFn, ServerFacade } from '../../../../types'; +import { + ServerFacade, + ExecuteJobFactory, + ESQueueWorkerExecuteFn, + HeadlessChromiumDriverFactory, +} from '../../../../types'; import { JobDocPayloadPDF } from '../../types'; import { PLUGIN_ID, PDF_JOB_TYPE } from '../../../../common/constants'; import { LevelLogger } from '../../../../server/lib'; @@ -19,10 +24,13 @@ import { getCustomLogo, } from '../../../common/execute_job/'; -export const executeJobFactory: ExecuteJobFactory> = function executeJobFactoryFn(server: ServerFacade) { - const generatePdfObservable = generatePdfObservableFactory(server); +type QueuedPdfExecutorFactory = ExecuteJobFactory>; + +export const executeJobFactory: QueuedPdfExecutorFactory = function executeJobFactoryFn( + server: ServerFacade, + { browserDriverFactory }: { browserDriverFactory: HeadlessChromiumDriverFactory } +) { + const generatePdfObservable = generatePdfObservableFactory(server, browserDriverFactory); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PDF_JOB_TYPE, 'execute']); return function executeJob( diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/index.ts deleted file mode 100644 index df798a7af23ec8..00000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/index.ts +++ /dev/null @@ -1,23 +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 { ExportTypesRegistry } from '../../../types'; -import { createJobFactory } from './create_job'; -import { executeJobFactory } from './execute_job'; -import { metadata } from '../metadata'; -import { PDF_JOB_TYPE as jobType } from '../../../common/constants'; - -export function register(registry: ExportTypesRegistry) { - registry.register({ - ...metadata, - jobType, - jobContentEncoding: 'base64', - jobContentExtension: 'pdf', - createJobFactory, - executeJobFactory, - validLicenses: ['trial', 'standard', 'gold', 'platinum'], - }); -} diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts index 1e0245ebd513f1..898a13a2dfe805 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts @@ -8,7 +8,7 @@ import * as Rx from 'rxjs'; import { toArray, mergeMap } from 'rxjs/operators'; import { groupBy } from 'lodash'; import { LevelLogger } from '../../../../server/lib'; -import { ServerFacade, ConditionalHeaders } from '../../../../types'; +import { ServerFacade, HeadlessChromiumDriverFactory, ConditionalHeaders } from '../../../../types'; // @ts-ignore untyped module import { pdf } from './pdf'; import { screenshotsObservableFactory } from '../../../common/lib/screenshots'; @@ -26,8 +26,11 @@ const getTimeRange = (urlScreenshots: ScreenshotResults[]) => { return null; }; -export function generatePdfObservableFactory(server: ServerFacade) { - const screenshotsObservable = screenshotsObservableFactory(server); +export function generatePdfObservableFactory( + server: ServerFacade, + browserDriverFactory: HeadlessChromiumDriverFactory +) { + const screenshotsObservable = screenshotsObservableFactory(server, browserDriverFactory); const captureConcurrency = 1; return function generatePdfObservable( diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index 9add3accd262f2..c0c9e458132f05 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -13,8 +13,7 @@ import { registerRoutes } from './server/routes'; import { LevelLogger, checkLicenseFactory, - createQueueFactory, - exportTypesRegistryFactory, + getExportTypesRegistry, runValidations, } from './server/lib'; import { config as reportingConfig } from './config'; @@ -74,20 +73,23 @@ export const reporting = (kibana: any) => { // TODO: Decouple Hapi: Build a server facade object based on the server to // pass through to the libs. Do not pass server directly async init(server: ServerFacade) { + const exportTypesRegistry = getExportTypesRegistry(); + let isCollectorReady = false; // Register a function with server to manage the collection of usage stats const { usageCollection } = server.newPlatform.setup.plugins; - registerReportingUsageCollector(usageCollection, server, () => isCollectorReady); + registerReportingUsageCollector( + usageCollection, + server, + () => isCollectorReady, + exportTypesRegistry + ); const logger = LevelLogger.createForServer(server, [PLUGIN_ID]); - const [exportTypesRegistry, browserFactory] = await Promise.all([ - exportTypesRegistryFactory(server), - createBrowserDriverFactory(server), - ]); - server.expose('exportTypesRegistry', exportTypesRegistry); + const browserDriverFactory = await createBrowserDriverFactory(server); logConfiguration(server, logger); - runValidations(server, logger, browserFactory); + runValidations(server, logger, browserDriverFactory); const { xpack_main: xpackMainPlugin } = server.plugins; mirrorPluginStatus(xpackMainPlugin, this); @@ -101,11 +103,8 @@ export const reporting = (kibana: any) => { // Post initialization of the above code, the collector is now ready to fetch its data isCollectorReady = true; - server.expose('browserDriverFactory', browserFactory); - server.expose('queue', createQueueFactory(server)); - // Reporting routes - registerRoutes(server, logger); + registerRoutes(server, exportTypesRegistry, browserDriverFactory, logger); }, deprecations({ unused }: any) { diff --git a/x-pack/legacy/plugins/reporting/common/__tests__/export_types_registry.js b/x-pack/legacy/plugins/reporting/server/lib/__tests__/export_types_registry.js similarity index 100% rename from x-pack/legacy/plugins/reporting/common/__tests__/export_types_registry.js rename to x-pack/legacy/plugins/reporting/server/lib/__tests__/export_types_registry.js diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 174c6d587e523e..5cf760250ec0e3 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -5,7 +5,12 @@ */ import { PLUGIN_ID } from '../../common/constants'; -import { ServerFacade, QueueConfig } from '../../types'; +import { + ServerFacade, + ExportTypesRegistry, + HeadlessChromiumDriverFactory, + QueueConfig, +} from '../../types'; // @ts-ignore import { Esqueue } from './esqueue'; import { createWorkerFactory } from './create_worker'; @@ -13,7 +18,15 @@ import { LevelLogger } from './level_logger'; // @ts-ignore import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed -export function createQueueFactory(server: ServerFacade): Esqueue { +interface CreateQueueFactoryOpts { + exportTypesRegistry: ExportTypesRegistry; + browserDriverFactory: HeadlessChromiumDriverFactory; +} + +export function createQueueFactory( + server: ServerFacade, + { exportTypesRegistry, browserDriverFactory }: CreateQueueFactoryOpts +): Esqueue { const queueConfig: QueueConfig = server.config().get('xpack.reporting.queue'); const index = server.config().get('xpack.reporting.index'); @@ -29,7 +42,7 @@ export function createQueueFactory(server: ServerFacade): Esqueue { if (queueConfig.pollEnabled) { // create workers to poll the index for idle jobs waiting to be claimed and executed - const createWorker = createWorkerFactory(server); + const createWorker = createWorkerFactory(server, { exportTypesRegistry, browserDriverFactory }); createWorker(queue); } else { const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'create_queue']); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts index afad8f096a8bb9..8f843752491ecb 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts @@ -5,7 +5,8 @@ */ import * as sinon from 'sinon'; -import { ServerFacade } from '../../types'; +import { ServerFacade, HeadlessChromiumDriverFactory } from '../../types'; +import { ExportTypesRegistry } from './export_types_registry'; import { createWorkerFactory } from './create_worker'; // @ts-ignore import { Esqueue } from './esqueue'; @@ -22,16 +23,17 @@ configGetStub.withArgs('server.uuid').returns('g9ymiujthvy6v8yrh7567g6fwzgzftzfr const executeJobFactoryStub = sinon.stub(); -const getMockServer = ( - exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }] -): ServerFacade => { +const getMockServer = (): ServerFacade => { return ({ log: sinon.stub(), - expose: sinon.stub(), config: () => ({ get: configGetStub }), - plugins: { reporting: { exportTypesRegistry: { getAll: () => exportTypes } } }, } as unknown) as ServerFacade; }; +const getMockExportTypesRegistry = ( + exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }] +) => ({ + getAll: () => exportTypes, +}); describe('Create Worker', () => { let queue: Esqueue; @@ -44,7 +46,11 @@ describe('Create Worker', () => { }); test('Creates a single Esqueue worker for Reporting', async () => { - const createWorker = createWorkerFactory(getMockServer()); + const exportTypesRegistry = getMockExportTypesRegistry(); + const createWorker = createWorkerFactory(getMockServer(), { + exportTypesRegistry: exportTypesRegistry as ExportTypesRegistry, + browserDriverFactory: {} as HeadlessChromiumDriverFactory, + }); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); createWorker(queue); @@ -68,15 +74,17 @@ Object { }); test('Creates a single Esqueue worker for Reporting, even if there are multiple export types', async () => { - const createWorker = createWorkerFactory( - getMockServer([ - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - { executeJobFactory: executeJobFactoryStub }, - ]) - ); + const exportTypesRegistry = getMockExportTypesRegistry([ + { executeJobFactory: executeJobFactoryStub }, + { executeJobFactory: executeJobFactoryStub }, + { executeJobFactory: executeJobFactoryStub }, + { executeJobFactory: executeJobFactoryStub }, + { executeJobFactory: executeJobFactoryStub }, + ]); + const createWorker = createWorkerFactory(getMockServer(), { + exportTypesRegistry: exportTypesRegistry as ExportTypesRegistry, + browserDriverFactory: {} as HeadlessChromiumDriverFactory, + }); const registerWorkerSpy = sinon.spy(queue, 'registerWorker'); createWorker(queue); diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts index 01f59099a1d999..1326e411b6c5c7 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts @@ -5,6 +5,7 @@ */ import { PLUGIN_ID } from '../../common/constants'; +import { ExportTypesRegistry, HeadlessChromiumDriverFactory } from '../../types'; import { CancellationToken } from '../../common/cancellation_token'; import { ESQueueInstance, @@ -21,14 +22,21 @@ import { import { events as esqueueEvents } from './esqueue'; import { LevelLogger } from './level_logger'; -export function createWorkerFactory(server: ServerFacade) { +interface CreateWorkerFactoryOpts { + exportTypesRegistry: ExportTypesRegistry; + browserDriverFactory: HeadlessChromiumDriverFactory; +} + +export function createWorkerFactory( + server: ServerFacade, + { exportTypesRegistry, browserDriverFactory }: CreateWorkerFactoryOpts +) { type JobDocPayloadType = JobDocPayload; const config = server.config(); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'queue-worker']); const queueConfig: QueueConfig = config.get('xpack.reporting.queue'); const kibanaName: string = config.get('server.name'); const kibanaId: string = config.get('server.uuid'); - const { exportTypesRegistry } = server.plugins.reporting!; // Once more document types are added, this will need to be passed in return function createWorker(queue: ESQueueInstance) { @@ -41,8 +49,9 @@ export function createWorkerFactory(server: ServerFacade) { for (const exportType of exportTypesRegistry.getAll() as Array< ExportTypeDefinition >) { - const executeJobFactory = exportType.executeJobFactory(server); - jobExecutors.set(exportType.jobType, executeJobFactory); + // TODO: the executeJobFn should be unwrapped in the register method of the export types registry + const jobExecutor = exportType.executeJobFactory(server, { browserDriverFactory }); + jobExecutors.set(exportType.jobType, jobExecutor); } const workerFn = (jobSource: JobSource, ...workerRestArgs: any[]) => { diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index a8cefa3fdc49bb..2d044ab31a160e 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -8,12 +8,14 @@ import { get } from 'lodash'; // @ts-ignore import { events as esqueueEvents } from './esqueue'; import { + EnqueueJobFn, ESQueueCreateJobFn, ImmediateCreateJobFn, Job, ServerFacade, RequestFacade, Logger, + ExportTypesRegistry, CaptureConfig, QueueConfig, ConditionalHeaders, @@ -26,13 +28,20 @@ interface ConfirmedJob { _primary_term: number; } -export function enqueueJobFactory(server: ServerFacade) { +interface EnqueueJobFactoryOpts { + exportTypesRegistry: ExportTypesRegistry; + esqueue: any; +} + +export function enqueueJobFactory( + server: ServerFacade, + { exportTypesRegistry, esqueue }: EnqueueJobFactoryOpts +): EnqueueJobFn { const config = server.config(); const captureConfig: CaptureConfig = config.get('xpack.reporting.capture'); const browserType = captureConfig.browser.type; const maxAttempts = captureConfig.maxAttempts; const queueConfig: QueueConfig = config.get('xpack.reporting.queue'); - const { exportTypesRegistry, queue: jobQueue } = server.plugins.reporting!; return async function enqueueJob( parentLogger: Logger, @@ -46,6 +55,12 @@ export function enqueueJobFactory(server: ServerFacade) { const logger = parentLogger.clone(['queue-job']); const exportType = exportTypesRegistry.getById(exportTypeId); + + if (exportType == null) { + throw new Error(`Export type ${exportTypeId} does not exist in the registry!`); + } + + // TODO: the createJobFn should be unwrapped in the register method of the export types registry const createJob = exportType.createJobFactory(server) as CreateJobFn; const payload = await createJob(jobParams, headers, request); @@ -57,7 +72,7 @@ export function enqueueJobFactory(server: ServerFacade) { }; return new Promise((resolve, reject) => { - const job = jobQueue.addJob(exportType.jobType, payload, options); + const job = esqueue.addJob(exportType.jobType, payload, options); job.on(esqueueEvents.EVENT_JOB_CREATED, (createdJob: ConfirmedJob) => { if (createdJob.id === job.id) { diff --git a/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts b/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts index af1457ab52fe20..d553cc07ae3ef8 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts @@ -4,40 +4,110 @@ * you may not use this file except in compliance with the Elastic License. */ -import { resolve as pathResolve } from 'path'; -import glob from 'glob'; -import { ServerFacade } from '../../types'; -import { PLUGIN_ID } from '../../common/constants'; -import { oncePerServer } from './once_per_server'; -import { LevelLogger } from './level_logger'; -// @ts-ignore untype module TODO -import { ExportTypesRegistry } from '../../common/export_types_registry'; - -function scan(pattern: string) { - return new Promise((resolve, reject) => { - glob(pattern, {}, (err, files) => { - if (err) { - return reject(err); +import memoizeOne from 'memoize-one'; +import { isString } from 'lodash'; +import { getExportType as getTypeCsv } from '../../export_types/csv'; +import { getExportType as getTypeCsvFromSavedObject } from '../../export_types/csv_from_savedobject'; +import { getExportType as getTypePng } from '../../export_types/png'; +import { getExportType as getTypePrintablePdf } from '../../export_types/printable_pdf'; +import { ExportTypeDefinition } from '../../types'; + +type GetCallbackFn = ( + item: ExportTypeDefinition +) => boolean; +// => ExportTypeDefinition + +export class ExportTypesRegistry { + private _map: Map> = new Map(); + + constructor() {} + + register( + item: ExportTypeDefinition + ): void { + if (!isString(item.id)) { + throw new Error(`'item' must have a String 'id' property `); + } + + if (this._map.has(item.id)) { + throw new Error(`'item' with id ${item.id} has already been registered`); + } + + // TODO: Unwrap the execute function from the item's executeJobFactory + // Move that work out of server/lib/create_worker to reduce dependence on ESQueue + this._map.set(item.id, item); + } + + getAll() { + return Array.from(this._map.values()); + } + + getSize() { + return this._map.size; + } + + getById( + id: string + ): ExportTypeDefinition { + if (!this._map.has(id)) { + throw new Error(`Unknown id ${id}`); + } + + return this._map.get(id) as ExportTypeDefinition< + JobParamsType, + CreateJobFnType, + JobPayloadType, + ExecuteJobFnType + >; + } + + get( + findType: GetCallbackFn + ): ExportTypeDefinition { + let result; + for (const value of this._map.values()) { + if (!findType(value)) { + continue; // try next value } + const foundResult: ExportTypeDefinition< + JobParamsType, + CreateJobFnType, + JobPayloadType, + ExecuteJobFnType + > = value; - resolve(files); - }); - }); -} + if (result) { + throw new Error('Found multiple items matching predicate.'); + } + + result = foundResult; + } -const pattern = pathResolve(__dirname, '../../export_types/*/server/index.[jt]s'); -async function exportTypesRegistryFn(server: ServerFacade) { - const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'exportTypes']); - const exportTypesRegistry = new ExportTypesRegistry(); - const files: string[] = (await scan(pattern)) as string[]; + if (!result) { + throw new Error('Found no items matching predicate'); + } + + return result; + } +} - files.forEach(file => { - logger.debug(`Found exportType at ${file}`); +function getExportTypesRegistryFn(): ExportTypesRegistry { + const registry = new ExportTypesRegistry(); - const { register } = require(file); // eslint-disable-line @typescript-eslint/no-var-requires - register(exportTypesRegistry); + /* this replaces the previously async method of registering export types, + * where this would run a directory scan and types would be registered via + * discovery */ + const getTypeFns: Array<() => ExportTypeDefinition> = [ + getTypeCsv, + getTypeCsvFromSavedObject, + getTypePng, + getTypePrintablePdf, + ]; + getTypeFns.forEach(getType => { + registry.register(getType()); }); - return exportTypesRegistry; + return registry; } -export const exportTypesRegistryFactory = oncePerServer(exportTypesRegistryFn); +// FIXME: is this the best way to return a singleton? +export const getExportTypesRegistry = memoizeOne(getExportTypesRegistryFn); diff --git a/x-pack/legacy/plugins/reporting/server/lib/index.ts b/x-pack/legacy/plugins/reporting/server/lib/index.ts index b11f7bd95d9efd..50d1a276b6b5d0 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/index.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/index.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -export { exportTypesRegistryFactory } from './export_types_registry'; +export { getExportTypesRegistry } from './export_types_registry'; // @ts-ignore untyped module export { checkLicenseFactory } from './check_license'; export { LevelLogger } from './level_logger'; -export { createQueueFactory } from './create_queue'; export { cryptoFactory } from './crypto'; export { oncePerServer } from './once_per_server'; export { runValidations } from './validate'; +export { createQueueFactory } from './create_queue'; +export { enqueueJobFactory } from './enqueue_job'; diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 8b0dd1a6e7c4f6..bc96c27f64c103 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -10,6 +10,7 @@ import { ServerFacade, RequestFacade, ResponseFacade, + HeadlessChromiumDriverFactory, ReportingResponseToolkit, Logger, JobDocOutputExecuted, @@ -45,8 +46,17 @@ export function registerGenerateCsvFromSavedObjectImmediate( handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(request, { isImmediate: true }); + + /* TODO these functions should be made available in the export types registry: + * + * const { createJobFn, executeJobFn } = exportTypesRegistry.getById(CSV_FROM_SAVEDOBJECT_JOB_TYPE) + * + * Calling an execute job factory requires passing a browserDriverFactory option, so we should not call the factory from here + */ const createJobFn = createJobFactory(server); - const executeJobFn = executeJobFactory(server); + const executeJobFn = executeJobFactory(server, { + browserDriverFactory: {} as HeadlessChromiumDriverFactory, + }); const jobDocPayload: JobDocPayloadPanelCsv = await createJobFn( jobParams, request.headers, diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts new file mode 100644 index 00000000000000..7bed7bc5773e45 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -0,0 +1,83 @@ +/* + * 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 { API_BASE_URL } from '../../common/constants'; +import { + ServerFacade, + ExportTypesRegistry, + HeadlessChromiumDriverFactory, + RequestFacade, + ReportingResponseToolkit, + Logger, +} from '../../types'; +import { registerGenerateFromJobParams } from './generate_from_jobparams'; +import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; +import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; +import { registerLegacy } from './legacy'; +import { createQueueFactory, enqueueJobFactory } from '../lib'; + +export function registerJobGenerationRoutes( + server: ServerFacade, + exportTypesRegistry: ExportTypesRegistry, + browserDriverFactory: HeadlessChromiumDriverFactory, + logger: Logger +) { + const config = server.config(); + const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; + // @ts-ignore TODO + const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); + + const esqueue = createQueueFactory(server, { exportTypesRegistry, browserDriverFactory }); + const enqueueJob = enqueueJobFactory(server, { exportTypesRegistry, esqueue }); + + /* + * Generates enqueued job details to use in responses + */ + async function handler( + exportTypeId: string, + jobParams: object, + request: RequestFacade, + h: ReportingResponseToolkit + ) { + const user = request.pre.user; + const headers = request.headers; + + const job = await enqueueJob(logger, exportTypeId, jobParams, user, headers, request); + + // return the queue's job information + const jobJson = job.toJSON(); + + return h + .response({ + path: `${DOWNLOAD_BASE_URL}/${jobJson.id}`, + job: jobJson, + }) + .type('application/json'); + } + + function handleError(exportTypeId: string, err: Error) { + if (err instanceof esErrors['401']) { + return boom.unauthorized(`Sorry, you aren't authenticated`); + } + if (err instanceof esErrors['403']) { + return boom.forbidden(`Sorry, you are not authorized to create ${exportTypeId} reports`); + } + if (err instanceof esErrors['404']) { + return boom.boomify(err, { statusCode: 404 }); + } + return err; + } + + registerGenerateFromJobParams(server, handler, handleError); + registerLegacy(server, handler, handleError); + + // Register beta panel-action download-related API's + if (config.get('xpack.reporting.csv.enablePanelActionDownload')) { + registerGenerateCsvFromSavedObject(server, handler, handleError); + registerGenerateCsvFromSavedObjectImmediate(server, logger); + } +} diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index c48a37a36812e4..da664dcb91ae44 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -4,69 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import boom from 'boom'; -import { API_BASE_URL } from '../../common/constants'; -import { ServerFacade, RequestFacade, ReportingResponseToolkit, Logger } from '../../types'; -import { enqueueJobFactory } from '../lib/enqueue_job'; -import { registerGenerateFromJobParams } from './generate_from_jobparams'; -import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; -import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; -import { registerJobs } from './jobs'; -import { registerLegacy } from './legacy'; - -export function registerRoutes(server: ServerFacade, logger: Logger) { - const config = server.config(); - const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; - // @ts-ignore TODO - const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); - const enqueueJob = enqueueJobFactory(server); - - /* - * Generates enqueued job details to use in responses - */ - async function handler( - exportTypeId: string, - jobParams: object, - request: RequestFacade, - h: ReportingResponseToolkit - ) { - const user = request.pre.user; - const headers = request.headers; - - const job = await enqueueJob(logger, exportTypeId, jobParams, user, headers, request); - - // return the queue's job information - const jobJson = job.toJSON(); - - return h - .response({ - path: `${DOWNLOAD_BASE_URL}/${jobJson.id}`, - job: jobJson, - }) - .type('application/json'); - } - - function handleError(exportTypeId: string, err: Error) { - if (err instanceof esErrors['401']) { - return boom.unauthorized(`Sorry, you aren't authenticated`); - } - if (err instanceof esErrors['403']) { - return boom.forbidden(`Sorry, you are not authorized to create ${exportTypeId} reports`); - } - if (err instanceof esErrors['404']) { - return boom.boomify(err, { statusCode: 404 }); - } - return err; - } - - registerGenerateFromJobParams(server, handler, handleError); - registerLegacy(server, handler, handleError); - - // Register beta panel-action download-related API's - if (config.get('xpack.reporting.csv.enablePanelActionDownload')) { - registerGenerateCsvFromSavedObject(server, handler, handleError); - registerGenerateCsvFromSavedObjectImmediate(server, logger); - } - - registerJobs(server); +import { + ServerFacade, + ExportTypesRegistry, + HeadlessChromiumDriverFactory, + Logger, +} from '../../types'; +import { registerJobGenerationRoutes } from './generation'; +import { registerJobInfoRoutes } from './jobs'; + +export function registerRoutes( + server: ServerFacade, + exportTypesRegistry: ExportTypesRegistry, + browserDriverFactory: HeadlessChromiumDriverFactory, + logger: Logger +) { + registerJobGenerationRoutes(server, exportTypesRegistry, browserDriverFactory, logger); + registerJobInfoRoutes(server, exportTypesRegistry, logger); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js index 2d1f48dd790a0b..c4d4f6e42c9cb7 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.test.js @@ -6,8 +6,8 @@ import Hapi from 'hapi'; import { difference, memoize } from 'lodash'; -import { registerJobs } from './jobs'; -import { ExportTypesRegistry } from '../../common/export_types_registry'; +import { registerJobInfoRoutes } from './jobs'; +import { ExportTypesRegistry } from '../lib/export_types_registry'; jest.mock('./lib/authorized_user_pre_routing', () => { return { authorizedUserPreRoutingFactory: () => () => ({}) @@ -19,13 +19,17 @@ jest.mock('./lib/reporting_feature_pre_routing', () => { }; }); - let mockServer; +let exportTypesRegistry; +const mockLogger = { + error: jest.fn(), + debug: jest.fn(), +}; beforeEach(() => { mockServer = new Hapi.Server({ debug: false, port: 8080, routes: { log: { collect: true } } }); mockServer.config = memoize(() => ({ get: jest.fn() })); - const exportTypesRegistry = new ExportTypesRegistry(); + exportTypesRegistry = new ExportTypesRegistry(); exportTypesRegistry.register({ id: 'unencoded', jobType: 'unencodedJobType', @@ -44,9 +48,6 @@ beforeEach(() => { callWithRequest: jest.fn(), callWithInternalUser: jest.fn(), })) - }, - reporting: { - exportTypesRegistry } }; }); @@ -63,7 +64,7 @@ test(`returns 404 if job not found`, async () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits())); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', @@ -79,7 +80,7 @@ test(`returns 401 if not valid job type`, async () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits({ jobtype: 'invalidJobType' }))); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', @@ -91,12 +92,11 @@ test(`returns 401 if not valid job type`, async () => { }); describe(`when job is incomplete`, () => { - const getIncompleteResponse = async () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(getHits({ jobtype: 'unencodedJobType', status: 'pending' }))); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', @@ -133,7 +133,7 @@ describe(`when job is failed`, () => { mockServer.plugins.elasticsearch.getCluster('admin') .callWithInternalUser.mockReturnValue(Promise.resolve(hits)); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', @@ -178,7 +178,7 @@ describe(`when job is completed`, () => { }); mockServer.plugins.elasticsearch.getCluster('admin').callWithInternalUser.mockReturnValue(Promise.resolve(hits)); - registerJobs(mockServer); + registerJobInfoRoutes(mockServer, exportTypesRegistry, mockLogger); const request = { method: 'GET', diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 71d9f0d3ae13bb..fd5014911d262a 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -8,6 +8,8 @@ import boom from 'boom'; import { API_BASE_URL } from '../../common/constants'; import { ServerFacade, + ExportTypesRegistry, + Logger, RequestFacade, ReportingResponseToolkit, JobDocOutput, @@ -24,7 +26,11 @@ import { const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -export function registerJobs(server: ServerFacade) { +export function registerJobInfoRoutes( + server: ServerFacade, + exportTypesRegistry: ExportTypesRegistry, + logger: Logger +) { const jobsQuery = jobsQueryFactory(server); const getRouteConfig = getRouteConfigFactoryManagementPre(server); const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(server); @@ -119,7 +125,7 @@ export function registerJobs(server: ServerFacade) { }); // trigger a download of the output from a job - const jobResponseHandler = jobResponseHandlerFactory(server); + const jobResponseHandler = jobResponseHandlerFactory(server, exportTypesRegistry); server.route({ path: `${MAIN_ENTRY}/download/{docId}`, method: 'GET', @@ -136,13 +142,15 @@ export function registerJobs(server: ServerFacade) { const { statusCode } = response; if (statusCode !== 200) { - const logLevel = statusCode === 500 ? 'error' : 'debug'; - server.log( - [logLevel, 'reporting', 'download'], - `Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify( - response.source - )}]` - ); + if (statusCode === 500) { + logger.error(`Report ${docId} has failed: ${JSON.stringify(response.source)}`); + } else { + logger.debug( + `Report ${docId} has non-OK status: [${statusCode}] Reason: [${JSON.stringify( + response.source + )}]` + ); + } } if (!response.isBoom) { diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts index a69c19c006b615..c3a30f9dda4543 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/get_document_payload.ts @@ -9,6 +9,7 @@ import * as _ from 'lodash'; import contentDisposition from 'content-disposition'; import { ServerFacade, + ExportTypesRegistry, ExportTypeDefinition, JobDocExecuted, JobDocOutputExecuted, @@ -40,9 +41,10 @@ const getReportingHeaders = (output: JobDocOutputExecuted, exportType: ExportTyp return metaDataHeaders; }; -export function getDocumentPayloadFactory(server: ServerFacade) { - const exportTypesRegistry = server.plugins.reporting!.exportTypesRegistry; - +export function getDocumentPayloadFactory( + server: ServerFacade, + exportTypesRegistry: ExportTypesRegistry +) { function encodeContent(content: string | null, exportType: ExportTypeType) { switch (exportType.jobContentEncoding) { case 'base64': diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.js b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.js index 758c50816c381e..6bc370506a2557 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.js +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/job_response_handler.js @@ -9,9 +9,9 @@ import { jobsQueryFactory } from '../../lib/jobs_query'; import { WHITELISTED_JOB_CONTENT_TYPES } from '../../../common/constants'; import { getDocumentPayloadFactory } from './get_document_payload'; -export function jobResponseHandlerFactory(server) { +export function jobResponseHandlerFactory(server, exportTypesRegistry) { const jobsQuery = jobsQueryFactory(server); - const getDocumentPayload = getDocumentPayloadFactory(server); + const getDocumentPayload = getDocumentPayloadFactory(server, exportTypesRegistry); return function jobResponseHandler(validJobTypes, user, h, params, opts = {}) { const { docId } = params; diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.js b/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts similarity index 61% rename from x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.js rename to x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts index a1949b21aa0860..f8913a0dcea6b4 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.js +++ b/x-pack/legacy/plugins/reporting/server/usage/get_export_type_handler.ts @@ -4,17 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { exportTypesRegistryFactory } from '../lib/export_types_registry'; +import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; +import { ExportTypesRegistry } from '../lib/export_types_registry'; /* * Gets a handle to the Reporting export types registry and returns a few * functions for examining them - * @param {Object} server: Kibana server * @return {Object} export type handler */ -export async function getExportTypesHandler(server) { - const exportTypesRegistry = await exportTypesRegistryFactory(server); - +export function getExportTypesHandler(exportTypesRegistry: ExportTypesRegistry) { return { /* * Based on the X-Pack license and which export types are available, @@ -23,12 +21,17 @@ export async function getExportTypesHandler(server) { * @param {Object} xpackInfo: xpack_main plugin info object * @return {Object} availability of each export type */ - getAvailability(xpackInfo) { - const exportTypesAvailability = {}; + getAvailability(xpackInfo: XPackMainPlugin['info']) { + const exportTypesAvailability: { [exportType: string]: boolean } = {}; const xpackInfoAvailable = xpackInfo && xpackInfo.isAvailable(); - const licenseType = xpackInfo.license.getType(); - for(const exportType of exportTypesRegistry.getAll()) { - exportTypesAvailability[exportType.jobType] = xpackInfoAvailable ? exportType.validLicenses.includes(licenseType) : false; + const licenseType: string | undefined = xpackInfo.license.getType(); + if (!licenseType) { + throw new Error('No license type returned from XPackMainPlugin#info!'); + } + for (const exportType of exportTypesRegistry.getAll()) { + exportTypesAvailability[exportType.jobType] = xpackInfoAvailable + ? exportType.validLicenses.includes(licenseType) + : false; } return exportTypesAvailability; @@ -39,6 +42,6 @@ export async function getExportTypesHandler(server) { */ getNumExportTypes() { return exportTypesRegistry.getSize(); - } + }, }; } diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts b/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts index 0c85d39ae55d3e..bd2d0cb835a790 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage.ts @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { ServerFacade, ESCallCluster } from '../../types'; +import { ServerFacade, ExportTypesRegistry, ESCallCluster } from '../../types'; import { AggregationBuckets, AggregationResults, @@ -16,7 +16,6 @@ import { RangeStats, } from './types'; import { decorateRangeStats } from './decorate_range_stats'; -// @ts-ignore untyped module import { getExportTypesHandler } from './get_export_type_handler'; const JOB_TYPES_KEY = 'jobTypes'; @@ -101,7 +100,11 @@ async function handleResponse( }; } -export async function getReportingUsage(server: ServerFacade, callCluster: ESCallCluster) { +export async function getReportingUsage( + server: ServerFacade, + callCluster: ESCallCluster, + exportTypesRegistry: ExportTypesRegistry +) { const config = server.config(); const reportingIndex = config.get('xpack.reporting.index'); @@ -138,13 +141,13 @@ export async function getReportingUsage(server: ServerFacade, callCluster: ESCal return callCluster('search', params) .then((response: AggregationResults) => handleResponse(server, response)) - .then(async (usage: RangeStatSets) => { + .then((usage: RangeStatSets) => { // Allow this to explicitly throw an exception if/when this config is deprecated, // because we shouldn't collect browserType in that case! const browserType = config.get('xpack.reporting.capture.browser.type'); const xpackInfo = server.plugins.xpack_main.info; - const exportTypesHandler = await getExportTypesHandler(server); + const exportTypesHandler = getExportTypesHandler(exportTypesRegistry); const availability = exportTypesHandler.getAvailability(xpackInfo) as FeatureAvailabilityMap; const { lastDay, last7Days, ...all } = usage; diff --git a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js index f23f6798651463..f761f0d2d270b2 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js +++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import sinon from 'sinon'; +import { getExportTypesRegistry } from '../lib/export_types_registry'; import { getReportingUsageCollector } from './reporting_usage_collector'; +const exportTypesRegistry = getExportTypesRegistry(); + function getMockUsageCollection() { class MockUsageCollector { constructor(_server, { fetch }) { @@ -40,7 +43,6 @@ function getServerMock(customization) { }, }, }, - expose: () => {}, log: () => {}, config: () => ({ get: key => { @@ -67,8 +69,13 @@ describe('license checks', () => { .returns('basic'); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); const usageCollection = getMockUsageCollection(); - const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithBasicLicenseMock); - usageStats = await getReportingUsage(callClusterMock); + const { fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, + serverWithBasicLicenseMock, + () => {}, + exportTypesRegistry + ); + usageStats = await getReportingUsage(callClusterMock, exportTypesRegistry); }); test('sets enables to true', async () => { @@ -93,8 +100,13 @@ describe('license checks', () => { .returns('none'); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); const usageCollection = getMockUsageCollection(); - const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithNoLicenseMock); - usageStats = await getReportingUsage(callClusterMock); + const { fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, + serverWithNoLicenseMock, + () => {}, + exportTypesRegistry + ); + usageStats = await getReportingUsage(callClusterMock, exportTypesRegistry); }); test('sets enables to true', async () => { @@ -121,9 +133,11 @@ describe('license checks', () => { const usageCollection = getMockUsageCollection(); const { fetch: getReportingUsage } = getReportingUsageCollector( usageCollection, - serverWithPlatinumLicenseMock + serverWithPlatinumLicenseMock, + () => {}, + exportTypesRegistry ); - usageStats = await getReportingUsage(callClusterMock); + usageStats = await getReportingUsage(callClusterMock, exportTypesRegistry); }); test('sets enables to true', async () => { @@ -148,8 +162,13 @@ describe('license checks', () => { .returns('basic'); const callClusterMock = jest.fn(() => Promise.resolve({})); const usageCollection = getMockUsageCollection(); - const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithBasicLicenseMock); - usageStats = await getReportingUsage(callClusterMock); + const { fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, + serverWithBasicLicenseMock, + () => {}, + exportTypesRegistry + ); + usageStats = await getReportingUsage(callClusterMock, exportTypesRegistry); }); test('sets enables to true', async () => { @@ -170,7 +189,12 @@ describe('data modeling', () => { serverWithPlatinumLicenseMock.plugins.xpack_main.info.license.getType = sinon .stub() .returns('platinum'); - ({ fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithPlatinumLicenseMock)); + ({ fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, + serverWithPlatinumLicenseMock, + () => {}, + exportTypesRegistry + )); }); test('with normal looking usage data', async () => { @@ -295,6 +319,7 @@ describe('data modeling', () => { }) ) ); + const usageStats = await getReportingUsage(callClusterMock); expect(usageStats).toMatchInlineSnapshot(` Object { diff --git a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts index 0a7ef0a1944344..40cf315a78cbb5 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts @@ -7,7 +7,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; // @ts-ignore untyped module import { KIBANA_STATS_TYPE_MONITORING } from '../../../monitoring/common/constants'; -import { ServerFacade, ESCallCluster } from '../../types'; +import { ServerFacade, ExportTypesRegistry, ESCallCluster } from '../../types'; import { KIBANA_REPORTING_TYPE } from '../../common/constants'; import { getReportingUsage } from './get_reporting_usage'; import { RangeStats } from './types'; @@ -19,12 +19,14 @@ import { RangeStats } from './types'; export function getReportingUsageCollector( usageCollection: UsageCollectionSetup, server: ServerFacade, - isReady: () => boolean + isReady: () => boolean, + exportTypesRegistry: ExportTypesRegistry ) { return usageCollection.makeUsageCollector({ type: KIBANA_REPORTING_TYPE, isReady, - fetch: (callCluster: ESCallCluster) => getReportingUsage(server, callCluster), + fetch: (callCluster: ESCallCluster) => + getReportingUsage(server, callCluster, exportTypesRegistry), /* * Format the response data into a model for internal upload @@ -49,8 +51,14 @@ export function getReportingUsageCollector( export function registerReportingUsageCollector( usageCollection: UsageCollectionSetup, server: ServerFacade, - isReady: () => boolean + isReady: () => boolean, + exportTypesRegistry: ExportTypesRegistry ) { - const collector = getReportingUsageCollector(usageCollection, server, isReady); + const collector = getReportingUsageCollector( + usageCollection, + server, + isReady, + exportTypesRegistry + ); usageCollection.registerCollector(collector); } diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index afba9f3e178382..597e9cafdc2a20 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -13,9 +13,12 @@ import { CallCluster, } from '../../../../src/legacy/core_plugins/elasticsearch'; import { CancellationToken } from './common/cancellation_token'; +import { LevelLogger } from './server/lib/level_logger'; import { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; import { BrowserType } from './server/browsers/types'; +export type ReportingPlugin = object; // For Plugin contract + export type Job = EventEmitter & { id: string; toJSON: () => { @@ -23,21 +26,6 @@ export type Job = EventEmitter & { }; }; -export interface ReportingPlugin { - queue: { - addJob: (type: string, payload: PayloadType, options: object) => Job; - }; - // TODO: convert exportTypesRegistry to TS - exportTypesRegistry: { - getById: (id: string) => ExportTypeDefinition; - getAll: () => Array>; - get: ( - callback: (item: ExportTypeDefinition) => boolean - ) => ExportTypeDefinition; - }; - browserDriverFactory: HeadlessChromiumDriverFactory; -} - export interface ReportingConfigOptions { browser: BrowserConfig; poll: { @@ -88,7 +76,6 @@ export type ReportingPluginSpecOptions = Legacy.PluginSpecOptions; export type ServerFacade = Legacy.Server & { plugins: { - reporting?: ReportingPlugin; xpack_main?: XPackMainPlugin & { status?: any; }; @@ -107,6 +94,15 @@ interface ReportingRequest { }; } +export type EnqueueJobFn = ( + parentLogger: LevelLogger, + exportTypeId: string, + jobParams: JobParamsType, + user: string, + headers: Record, + request: RequestFacade +) => Promise; + export type RequestFacade = ReportingRequest & Legacy.Request; export type ResponseFacade = ResponseObject & { @@ -246,6 +242,10 @@ export interface JobDocOutputExecuted { size: number; } +export interface ESQueue { + addJob: (type: string, payload: object, options: object) => Job; +} + export interface ESQueueWorker { on: (event: string, handler: any) => void; } @@ -304,7 +304,12 @@ export interface ESQueueInstance { } export type CreateJobFactory = (server: ServerFacade) => CreateJobFnType; -export type ExecuteJobFactory = (server: ServerFacade) => ExecuteJobFnType; +export type ExecuteJobFactory = ( + server: ServerFacade, + opts: { + browserDriverFactory: HeadlessChromiumDriverFactory; + } +) => ExecuteJobFnType; export interface ExportTypeDefinition< JobParamsType, @@ -322,21 +327,13 @@ export interface ExportTypeDefinition< validLicenses: string[]; } -export interface ExportTypesRegistry { - register: ( - exportTypeDefinition: ExportTypeDefinition< - JobParamsType, - CreateJobFnType, - JobPayloadType, - ExecuteJobFnType - > - ) => void; -} - +export { ExportTypesRegistry } from './server/lib/export_types_registry'; +export { HeadlessChromiumDriver } from './server/browsers/chromium/driver'; +export { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; export { CancellationToken } from './common/cancellation_token'; // Prefer to import this type using: `import { LevelLogger } from 'relative/path/server/lib';` -export { LevelLogger as Logger } from './server/lib/level_logger'; +export { LevelLogger as Logger }; export interface AbsoluteURLFactoryOptions { defaultBasePath: string; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/app.js b/x-pack/legacy/plugins/rollup/public/crud_app/app.js index 0e42194097492d..c9f67afd830ecf 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/app.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/app.js @@ -6,22 +6,20 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import { HashRouter, Switch, Route, Redirect, withRouter } from 'react-router-dom'; import { UIM_APP_LOAD } from '../../common'; import { CRUD_APP_BASE_PATH } from './constants'; import { registerRouter, setUserHasLeftApp, trackUiMetric, METRIC_TYPE } from './services'; import { JobList, JobCreate } from './sections'; -class ShareRouter extends Component { - static contextTypes = { - router: PropTypes.shape({ - history: PropTypes.shape({ - push: PropTypes.func.isRequired, - createHref: PropTypes.func.isRequired - }).isRequired - }).isRequired - } +class ShareRouterComponent extends Component { + static propTypes = { + history: PropTypes.shape({ + push: PropTypes.func.isRequired, + createHref: PropTypes.func.isRequired, + }).isRequired, + }; constructor(...args) { super(...args); @@ -30,8 +28,8 @@ class ShareRouter extends Component { registerRouter() { // Share the router with the app without requiring React or context. - const { router } = this.context; - registerRouter(router); + const { history } = this.props; + registerRouter({ history }); } render() { @@ -39,6 +37,8 @@ class ShareRouter extends Component { } } +const ShareRouter = withRouter(ShareRouterComponent); + export class App extends Component { // eslint-disable-line react/no-multi-comp componentDidMount() { trackUiMetric(METRIC_TYPE.LOADED, UIM_APP_LOAD); diff --git a/x-pack/legacy/plugins/security/common/model/index.ts b/x-pack/legacy/plugins/security/common/model.ts similarity index 84% rename from x-pack/legacy/plugins/security/common/model/index.ts rename to x-pack/legacy/plugins/security/common/model.ts index 6c2976815559ba..90e6a5403dfe8e 100644 --- a/x-pack/legacy/plugins/security/common/model/index.ts +++ b/x-pack/legacy/plugins/security/common/model.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ApiKey } from './api_key'; export { + ApiKey, + ApiKeyToInvalidate, AuthenticatedUser, BuiltinESPrivileges, EditUser, @@ -19,4 +20,4 @@ export { User, canUserChangePassword, getUserDisplayName, -} from '../../../../../plugins/security/common/model'; +} from '../../../../plugins/security/common/model'; diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 115dd8b9b8206b..3a6f3692bc0b66 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -5,10 +5,6 @@ */ import { resolve } from 'path'; -import { initAuthenticateApi } from './server/routes/api/v1/authenticate'; -import { initUsersApi } from './server/routes/api/v1/users'; -import { initApiKeysApi } from './server/routes/api/v1/api_keys'; -import { initIndicesApi } from './server/routes/api/v1/indices'; import { initOverwrittenSessionView } from './server/routes/views/overwritten_session'; import { initLoginView } from './server/routes/views/login'; import { initLogoutView } from './server/routes/views/logout'; @@ -34,7 +30,7 @@ export const security = (kibana) => new kibana.Plugin({ lifespan: Joi.any().description('This key is handled in the new platform security plugin ONLY'), }).default(), secureCookies: Joi.any().description('This key is handled in the new platform security plugin ONLY'), - loginAssistanceMessage: Joi.string().default(), + loginAssistanceMessage: Joi.any().description('This key is handled in the new platform security plugin ONLY'), authorization: Joi.object({ legacyFallback: Joi.object({ enabled: Joi.boolean().default(true) // deprecated @@ -144,10 +140,6 @@ export const security = (kibana) => new kibana.Plugin({ server.expose({ getUser: request => securityPlugin.authc.getCurrentUser(KibanaRequest.from(request)) }); - initAuthenticateApi(securityPlugin, server); - initUsersApi(securityPlugin, server); - initApiKeysApi(server); - initIndicesApi(server); initLoginView(securityPlugin, server); initLogoutView(server); initLoggedOutView(securityPlugin, server); diff --git a/x-pack/legacy/plugins/security/public/hacks/on_unauthorized_response.js b/x-pack/legacy/plugins/security/public/hacks/on_unauthorized_response.js index 6d03f3da6e2f26..efc227e2c2789a 100644 --- a/x-pack/legacy/plugins/security/public/hacks/on_unauthorized_response.js +++ b/x-pack/legacy/plugins/security/public/hacks/on_unauthorized_response.js @@ -11,8 +11,8 @@ import 'plugins/security/services/auto_logout'; function isUnauthorizedResponseAllowed(response) { const API_WHITELIST = [ - '/api/security/v1/login', - '/api/security/v1/users/.*/password' + '/internal/security/login', + '/internal/security/users/.*/password' ]; const url = response.config.url; diff --git a/x-pack/legacy/plugins/security/public/lib/api.ts b/x-pack/legacy/plugins/security/public/lib/api.ts index e6e42ed5bd4da2..ffa08ca44f3765 100644 --- a/x-pack/legacy/plugins/security/public/lib/api.ts +++ b/x-pack/legacy/plugins/security/public/lib/api.ts @@ -7,12 +7,12 @@ import { kfetch } from 'ui/kfetch'; import { AuthenticatedUser, Role, User, EditUser } from '../../common/model'; -const usersUrl = '/api/security/v1/users'; +const usersUrl = '/internal/security/users'; const rolesUrl = '/api/security/role'; export class UserAPIClient { public async getCurrentUser(): Promise { - return await kfetch({ pathname: `/api/security/v1/me` }); + return await kfetch({ pathname: `/internal/security/me` }); } public async getUsers(): Promise { diff --git a/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts b/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts index c6dcef392af989..fbc0460c5908a7 100644 --- a/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts +++ b/x-pack/legacy/plugins/security/public/lib/api_keys_api.ts @@ -5,8 +5,7 @@ */ import { kfetch } from 'ui/kfetch'; -import { ApiKey, ApiKeyToInvalidate } from '../../common/model/api_key'; -import { INTERNAL_API_BASE_PATH } from '../../common/constants'; +import { ApiKey, ApiKeyToInvalidate } from '../../common/model'; interface CheckPrivilegesResponse { areApiKeysEnabled: boolean; @@ -22,7 +21,7 @@ interface GetApiKeysResponse { apiKeys: ApiKey[]; } -const apiKeysUrl = `${INTERNAL_API_BASE_PATH}/api_key`; +const apiKeysUrl = `/internal/security/api_key`; export class ApiKeysApi { public static async checkPrivileges(): Promise { diff --git a/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts b/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts index e0998eb8b8f6be..91d98782dab423 100644 --- a/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts +++ b/x-pack/legacy/plugins/security/public/objects/lib/get_fields.ts @@ -6,7 +6,7 @@ import { IHttpResponse } from 'angular'; import chrome from 'ui/chrome'; -const apiBase = chrome.addBasePath(`/api/security/v1/fields`); +const apiBase = chrome.addBasePath(`/internal/security/fields`); export async function getFields($http: any, query: string): Promise { return await $http diff --git a/x-pack/legacy/plugins/security/public/services/shield_indices.js b/x-pack/legacy/plugins/security/public/services/shield_indices.js index 2e25d73acbcee6..973569eb6e9c3f 100644 --- a/x-pack/legacy/plugins/security/public/services/shield_indices.js +++ b/x-pack/legacy/plugins/security/public/services/shield_indices.js @@ -10,7 +10,7 @@ const module = uiModules.get('security', []); module.service('shieldIndices', ($http, chrome) => { return { getFields: (query) => { - return $http.get(chrome.addBasePath(`/api/security/v1/fields/${query}`)) + return $http.get(chrome.addBasePath(`/internal/security/fields/${query}`)) .then(response => response.data); } }; diff --git a/x-pack/legacy/plugins/security/public/services/shield_user.js b/x-pack/legacy/plugins/security/public/services/shield_user.js index e77895caaa2baf..53252e851e3536 100644 --- a/x-pack/legacy/plugins/security/public/services/shield_user.js +++ b/x-pack/legacy/plugins/security/public/services/shield_user.js @@ -10,7 +10,7 @@ import { uiModules } from 'ui/modules'; const module = uiModules.get('security', ['ngResource']); module.service('ShieldUser', ($resource, chrome) => { - const baseUrl = chrome.addBasePath('/api/security/v1/users/:username'); + const baseUrl = chrome.addBasePath('/internal/security/users/:username'); const ShieldUser = $resource(baseUrl, { username: '@username' }, { @@ -21,7 +21,7 @@ module.service('ShieldUser', ($resource, chrome) => { }, getCurrent: { method: 'GET', - url: chrome.addBasePath('/api/security/v1/me') + url: chrome.addBasePath('/internal/security/me') } }); diff --git a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx index acdc29842d4c65..e6d3b5b7536b6a 100644 --- a/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/components/basic_login_form/basic_login_form.tsx @@ -190,7 +190,7 @@ class BasicLoginFormUI extends Component { const { username, password } = this.state; - http.post('./api/security/v1/login', { username, password }).then( + http.post('./internal/security/login', { username, password }).then( () => (window.location.href = next), (error: any) => { const { statusCode = 500 } = error.data || {}; diff --git a/x-pack/legacy/plugins/security/public/views/logout/logout.js b/x-pack/legacy/plugins/security/public/views/logout/logout.js index 4411ecdade8e75..5d76dfc2908c8f 100644 --- a/x-pack/legacy/plugins/security/public/views/logout/logout.js +++ b/x-pack/legacy/plugins/security/public/views/logout/logout.js @@ -12,5 +12,5 @@ chrome $window.sessionStorage.clear(); // Redirect user to the server logout endpoint to complete logout. - $window.location.href = chrome.addBasePath(`/api/security/v1/logout${$window.location.search}`); + $window.location.href = chrome.addBasePath(`/api/security/logout${$window.location.search}`); }); diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/api_keys_grid_page.tsx b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/api_keys_grid_page.tsx index 37838cfdb950d9..1613e3804c31dd 100644 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/api_keys_grid_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/api_keys_grid_page.tsx @@ -29,7 +29,7 @@ import _ from 'lodash'; import { toastNotifications } from 'ui/notify'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SectionLoading } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/section_loading'; -import { ApiKey, ApiKeyToInvalidate } from '../../../../../common/model/api_key'; +import { ApiKey, ApiKeyToInvalidate } from '../../../../../common/model'; import { ApiKeysApi } from '../../../../lib/api_keys_api'; import { PermissionDenied } from './permission_denied'; import { EmptyPrompt } from './empty_prompt'; diff --git a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/invalidate_provider/invalidate_provider.tsx b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/invalidate_provider/invalidate_provider.tsx index fe9ffc651db29c..a1627442b89b81 100644 --- a/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/invalidate_provider/invalidate_provider.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/api_keys_grid/components/invalidate_provider/invalidate_provider.tsx @@ -8,7 +8,7 @@ import React, { Fragment, useRef, useState } from 'react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -import { ApiKeyToInvalidate } from '../../../../../../common/model/api_key'; +import { ApiKeyToInvalidate } from '../../../../../../common/model'; import { ApiKeysApi } from '../../../../../lib/api_keys_api'; interface Props { diff --git a/x-pack/legacy/plugins/security/server/lib/__tests__/__fixtures__/request.ts b/x-pack/legacy/plugins/security/server/lib/__tests__/__fixtures__/request.ts deleted file mode 100644 index c928a38d88ef3a..00000000000000 --- a/x-pack/legacy/plugins/security/server/lib/__tests__/__fixtures__/request.ts +++ /dev/null @@ -1,39 +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 { Request } from 'hapi'; -import url from 'url'; - -interface RequestFixtureOptions { - headers?: Record; - auth?: string; - params?: Record; - path?: string; - basePath?: string; - search?: string; - payload?: unknown; -} - -export function requestFixture({ - headers = { accept: 'something/html' }, - auth, - params, - path = '/wat', - search = '', - payload, -}: RequestFixtureOptions = {}) { - return ({ - raw: { req: { headers } }, - auth, - headers, - params, - url: { path, search }, - query: search ? url.parse(search, true /* parseQueryString */).query : {}, - payload, - state: { user: 'these are the contents of the user client cookie' }, - route: { settings: {} }, - } as any) as Request; -} diff --git a/x-pack/legacy/plugins/security/server/lib/__tests__/__fixtures__/server.ts b/x-pack/legacy/plugins/security/server/lib/__tests__/__fixtures__/server.ts deleted file mode 100644 index 55b6f735cfced8..00000000000000 --- a/x-pack/legacy/plugins/security/server/lib/__tests__/__fixtures__/server.ts +++ /dev/null @@ -1,56 +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 { stub } from 'sinon'; - -export function serverFixture() { - return { - config: stub(), - register: stub(), - expose: stub(), - log: stub(), - route: stub(), - decorate: stub(), - - info: { - protocol: 'protocol', - }, - - auth: { - strategy: stub(), - test: stub(), - }, - - plugins: { - elasticsearch: { - createCluster: stub(), - }, - - kibana: { - systemApi: { isSystemApiRequest: stub() }, - }, - - security: { - getUser: stub(), - authenticate: stub(), - deauthenticate: stub(), - authorization: { - application: stub(), - }, - }, - - xpack_main: { - info: { - isAvailable: stub(), - feature: stub(), - license: { - isOneOf: stub(), - }, - }, - }, - }, - }; -} diff --git a/x-pack/legacy/plugins/security/server/lib/route_pre_check_license.js b/x-pack/legacy/plugins/security/server/lib/route_pre_check_license.js deleted file mode 100644 index 64816bf4d23d70..00000000000000 --- a/x-pack/legacy/plugins/security/server/lib/route_pre_check_license.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. - */ - -const Boom = require('boom'); - -export function routePreCheckLicense(server) { - return function forbidApiAccess() { - const licenseCheckResults = server.newPlatform.setup.plugins.security.__legacyCompat.license.getFeatures(); - if (!licenseCheckResults.showLinks) { - throw Boom.forbidden(licenseCheckResults.linksMessage); - } else { - return null; - } - }; -} diff --git a/x-pack/legacy/plugins/security/server/lib/user_schema.js b/x-pack/legacy/plugins/security/server/lib/user_schema.js deleted file mode 100644 index 57c66b2712025b..00000000000000 --- a/x-pack/legacy/plugins/security/server/lib/user_schema.js +++ /dev/null @@ -1,17 +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 Joi from 'joi'; - -export const userSchema = Joi.object({ - username: Joi.string().required(), - password: Joi.string(), - roles: Joi.array().items(Joi.string()), - full_name: Joi.string().allow(null, ''), - email: Joi.string().allow(null, ''), - metadata: Joi.object(), - enabled: Joi.boolean().default(true) -}); diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/authenticate.js b/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/authenticate.js deleted file mode 100644 index 5cea7c70b77811..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/authenticate.js +++ /dev/null @@ -1,260 +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 Boom from 'boom'; -import Joi from 'joi'; -import sinon from 'sinon'; - -import { serverFixture } from '../../../../lib/__tests__/__fixtures__/server'; -import { requestFixture } from '../../../../lib/__tests__/__fixtures__/request'; -import { AuthenticationResult, DeauthenticationResult } from '../../../../../../../../plugins/security/server'; -import { initAuthenticateApi } from '../authenticate'; -import { KibanaRequest } from '../../../../../../../../../src/core/server'; - -describe('Authentication routes', () => { - let serverStub; - let hStub; - let loginStub; - let logoutStub; - - beforeEach(() => { - serverStub = serverFixture(); - hStub = { - authenticated: sinon.stub(), - continue: 'blah', - redirect: sinon.stub(), - response: sinon.stub() - }; - loginStub = sinon.stub(); - logoutStub = sinon.stub(); - - initAuthenticateApi({ - authc: { login: loginStub, logout: logoutStub }, - __legacyCompat: { config: { authc: { providers: ['basic'] } } }, - }, serverStub); - }); - - describe('login', () => { - let loginRoute; - let request; - - beforeEach(() => { - loginRoute = serverStub.route - .withArgs(sinon.match({ path: '/api/security/v1/login' })) - .firstCall - .args[0]; - - request = requestFixture({ - headers: {}, - payload: { username: 'user', password: 'password' } - }); - }); - - it('correctly defines route.', async () => { - expect(loginRoute.method).to.be('POST'); - expect(loginRoute.path).to.be('/api/security/v1/login'); - expect(loginRoute.handler).to.be.a(Function); - expect(loginRoute.config).to.eql({ - auth: false, - validate: { - payload: Joi.object({ - username: Joi.string().required(), - password: Joi.string().required() - }) - }, - response: { - emptyStatusCode: 204, - } - }); - }); - - it('returns 500 if authentication throws unhandled exception.', async () => { - const unhandledException = new Error('Something went wrong.'); - loginStub.throws(unhandledException); - - return loginRoute - .handler(request, hStub) - .catch((response) => { - expect(response.isBoom).to.be(true); - expect(response.output.payload).to.eql({ - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred' - }); - }); - }); - - it('returns 401 if authentication fails.', async () => { - const failureReason = new Error('Something went wrong.'); - loginStub.resolves(AuthenticationResult.failed(failureReason)); - - return loginRoute - .handler(request, hStub) - .catch((response) => { - expect(response.isBoom).to.be(true); - expect(response.message).to.be(failureReason.message); - expect(response.output.statusCode).to.be(401); - }); - }); - - it('returns 401 if authentication is not handled.', async () => { - loginStub.resolves(AuthenticationResult.notHandled()); - - return loginRoute - .handler(request, hStub) - .catch((response) => { - expect(response.isBoom).to.be(true); - expect(response.message).to.be('Unauthorized'); - expect(response.output.statusCode).to.be(401); - }); - }); - - describe('authentication succeeds', () => { - - it(`returns user data`, async () => { - loginStub.resolves(AuthenticationResult.succeeded({ username: 'user' })); - - await loginRoute.handler(request, hStub); - - sinon.assert.calledOnce(hStub.response); - sinon.assert.calledOnce(loginStub); - sinon.assert.calledWithExactly( - loginStub, - sinon.match.instanceOf(KibanaRequest), - { provider: 'basic', value: { username: 'user', password: 'password' } } - ); - }); - }); - - }); - - describe('logout', () => { - let logoutRoute; - - beforeEach(() => { - serverStub.config.returns({ - get: sinon.stub().withArgs('server.basePath').returns('/test-base-path') - }); - - logoutRoute = serverStub.route - .withArgs(sinon.match({ path: '/api/security/v1/logout' })) - .firstCall - .args[0]; - }); - - it('correctly defines route.', async () => { - expect(logoutRoute.method).to.be('GET'); - expect(logoutRoute.path).to.be('/api/security/v1/logout'); - expect(logoutRoute.handler).to.be.a(Function); - expect(logoutRoute.config).to.eql({ auth: false }); - }); - - it('returns 500 if deauthentication throws unhandled exception.', async () => { - const request = requestFixture(); - - const unhandledException = new Error('Something went wrong.'); - logoutStub.rejects(unhandledException); - - return logoutRoute - .handler(request, hStub) - .catch((response) => { - expect(response).to.be(Boom.boomify(unhandledException)); - sinon.assert.notCalled(hStub.redirect); - }); - }); - - it('returns 500 if authenticator fails to logout.', async () => { - const request = requestFixture(); - - const failureReason = Boom.forbidden(); - logoutStub.resolves(DeauthenticationResult.failed(failureReason)); - - return logoutRoute - .handler(request, hStub) - .catch((response) => { - expect(response).to.be(Boom.boomify(failureReason)); - sinon.assert.notCalled(hStub.redirect); - sinon.assert.calledOnce(logoutStub); - sinon.assert.calledWithExactly( - logoutStub, - sinon.match.instanceOf(KibanaRequest) - ); - }); - }); - - it('returns 400 for AJAX requests that can not handle redirect.', async () => { - const request = requestFixture({ headers: { 'kbn-xsrf': 'xsrf' } }); - - return logoutRoute - .handler(request, hStub) - .catch((response) => { - expect(response.isBoom).to.be(true); - expect(response.message).to.be('Client should be able to process redirect response.'); - expect(response.output.statusCode).to.be(400); - sinon.assert.notCalled(hStub.redirect); - }); - }); - - it('redirects user to the URL returned by authenticator.', async () => { - const request = requestFixture(); - - logoutStub.resolves(DeauthenticationResult.redirectTo('https://custom.logout')); - - await logoutRoute.handler(request, hStub); - - sinon.assert.calledOnce(hStub.redirect); - sinon.assert.calledWithExactly(hStub.redirect, 'https://custom.logout'); - }); - - it('redirects user to the base path if deauthentication succeeds.', async () => { - const request = requestFixture(); - - logoutStub.resolves(DeauthenticationResult.succeeded()); - - await logoutRoute.handler(request, hStub); - - sinon.assert.calledOnce(hStub.redirect); - sinon.assert.calledWithExactly(hStub.redirect, '/test-base-path/'); - }); - - it('redirects user to the base path if deauthentication is not handled.', async () => { - const request = requestFixture(); - - logoutStub.resolves(DeauthenticationResult.notHandled()); - - await logoutRoute.handler(request, hStub); - - sinon.assert.calledOnce(hStub.redirect); - sinon.assert.calledWithExactly(hStub.redirect, '/test-base-path/'); - }); - }); - - describe('me', () => { - let meRoute; - - beforeEach(() => { - meRoute = serverStub.route - .withArgs(sinon.match({ path: '/api/security/v1/me' })) - .firstCall - .args[0]; - }); - - it('correctly defines route.', async () => { - expect(meRoute.method).to.be('GET'); - expect(meRoute.path).to.be('/api/security/v1/me'); - expect(meRoute.handler).to.be.a(Function); - expect(meRoute.config).to.be(undefined); - }); - - it('returns user from the authenticated request property.', async () => { - const request = { auth: { credentials: { username: 'user' } } }; - const response = await meRoute.handler(request, hStub); - - expect(response).to.eql({ username: 'user' }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/users.js b/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/users.js deleted file mode 100644 index 4077ab52e86de7..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/users.js +++ /dev/null @@ -1,214 +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 Joi from 'joi'; -import sinon from 'sinon'; - -import { serverFixture } from '../../../../lib/__tests__/__fixtures__/server'; -import { requestFixture } from '../../../../lib/__tests__/__fixtures__/request'; -import { AuthenticationResult } from '../../../../../../../../plugins/security/server'; -import { initUsersApi } from '../users'; -import * as ClientShield from '../../../../../../../server/lib/get_client_shield'; -import { KibanaRequest } from '../../../../../../../../../src/core/server'; - -describe('User routes', () => { - const sandbox = sinon.createSandbox(); - - let clusterStub; - let serverStub; - let loginStub; - - beforeEach(() => { - serverStub = serverFixture(); - loginStub = sinon.stub(); - - // Cluster is returned by `getClient` function that is wrapped into `once` making cluster - // a static singleton, so we should use sandbox to set/reset its behavior between tests. - clusterStub = sinon.stub({ callWithRequest() {} }); - sandbox.stub(ClientShield, 'getClient').returns(clusterStub); - - initUsersApi({ authc: { login: loginStub }, __legacyCompat: { config: { authc: { providers: ['basic'] } } } }, serverStub); - }); - - afterEach(() => sandbox.restore()); - - describe('change password', () => { - let changePasswordRoute; - let request; - - beforeEach(() => { - changePasswordRoute = serverStub.route - .withArgs(sinon.match({ path: '/api/security/v1/users/{username}/password' })) - .firstCall - .args[0]; - - request = requestFixture({ - headers: {}, - auth: { credentials: { username: 'user' } }, - params: { username: 'target-user' }, - payload: { password: 'old-password', newPassword: 'new-password' } - }); - }); - - it('correctly defines route.', async () => { - expect(changePasswordRoute.method).to.be('POST'); - expect(changePasswordRoute.path).to.be('/api/security/v1/users/{username}/password'); - expect(changePasswordRoute.handler).to.be.a(Function); - - expect(changePasswordRoute.config).to.not.have.property('auth'); - expect(changePasswordRoute.config).to.have.property('pre'); - expect(changePasswordRoute.config.pre).to.have.length(1); - expect(changePasswordRoute.config.validate).to.eql({ - payload: Joi.object({ - password: Joi.string(), - newPassword: Joi.string().required() - }) - }); - }); - - describe('own password', () => { - beforeEach(() => { - request.params.username = request.auth.credentials.username; - loginStub = loginStub - .withArgs( - sinon.match.instanceOf(KibanaRequest), - { provider: 'basic', value: { username: 'user', password: 'old-password' }, stateless: true } - ) - .resolves(AuthenticationResult.succeeded({})); - }); - - it('returns 403 if old password is wrong.', async () => { - loginStub.resolves(AuthenticationResult.failed(new Error('Something went wrong.'))); - - const response = await changePasswordRoute.handler(request); - - sinon.assert.notCalled(clusterStub.callWithRequest); - expect(response.isBoom).to.be(true); - expect(response.output.payload).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: 'Something went wrong.' - }); - }); - - it(`returns 401 if user can't authenticate with new password.`, async () => { - loginStub - .withArgs( - sinon.match.instanceOf(KibanaRequest), - { provider: 'basic', value: { username: 'user', password: 'new-password' } } - ) - .resolves(AuthenticationResult.failed(new Error('Something went wrong.'))); - - const response = await changePasswordRoute.handler(request); - - sinon.assert.calledOnce(clusterStub.callWithRequest); - sinon.assert.calledWithExactly( - clusterStub.callWithRequest, - sinon.match.same(request), - 'shield.changePassword', - { username: 'user', body: { password: 'new-password' } } - ); - - expect(response.isBoom).to.be(true); - expect(response.output.payload).to.eql({ - statusCode: 401, - error: 'Unauthorized', - message: 'Something went wrong.' - }); - }); - - it('returns 500 if password update request fails.', async () => { - clusterStub.callWithRequest - .withArgs( - sinon.match.same(request), - 'shield.changePassword', - { username: 'user', body: { password: 'new-password' } } - ) - .rejects(new Error('Request failed.')); - - const response = await changePasswordRoute.handler(request); - - expect(response.isBoom).to.be(true); - expect(response.output.payload).to.eql({ - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred' - }); - }); - - it('successfully changes own password if provided old password is correct.', async () => { - loginStub - .withArgs( - sinon.match.instanceOf(KibanaRequest), - { provider: 'basic', value: { username: 'user', password: 'new-password' } } - ) - .resolves(AuthenticationResult.succeeded({})); - - const hResponseStub = { code: sinon.stub() }; - const hStub = { response: sinon.stub().returns(hResponseStub) }; - - await changePasswordRoute.handler(request, hStub); - - sinon.assert.calledOnce(clusterStub.callWithRequest); - sinon.assert.calledWithExactly( - clusterStub.callWithRequest, - sinon.match.same(request), - 'shield.changePassword', - { username: 'user', body: { password: 'new-password' } } - ); - - sinon.assert.calledWithExactly(hStub.response); - sinon.assert.calledWithExactly(hResponseStub.code, 204); - }); - }); - - describe('other user password', () => { - it('returns 500 if password update request fails.', async () => { - clusterStub.callWithRequest - .withArgs( - sinon.match.same(request), - 'shield.changePassword', - { username: 'target-user', body: { password: 'new-password' } } - ) - .returns(Promise.reject(new Error('Request failed.'))); - - const response = await changePasswordRoute.handler(request); - - sinon.assert.notCalled(serverStub.plugins.security.getUser); - sinon.assert.notCalled(loginStub); - - expect(response.isBoom).to.be(true); - expect(response.output.payload).to.eql({ - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred' - }); - }); - - it('successfully changes user password.', async () => { - const hResponseStub = { code: sinon.stub() }; - const hStub = { response: sinon.stub().returns(hResponseStub) }; - - await changePasswordRoute.handler(request, hStub); - - sinon.assert.notCalled(serverStub.plugins.security.getUser); - sinon.assert.notCalled(loginStub); - - sinon.assert.calledOnce(clusterStub.callWithRequest); - sinon.assert.calledWithExactly( - clusterStub.callWithRequest, - sinon.match.same(request), - 'shield.changePassword', - { username: 'target-user', body: { password: 'new-password' } } - ); - - sinon.assert.calledWithExactly(hStub.response); - sinon.assert.calledWithExactly(hResponseStub.code, 204); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/get.js b/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/get.js deleted file mode 100644 index a236badcd0d6bb..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/get.js +++ /dev/null @@ -1,45 +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 Joi from 'joi'; -import { wrapError } from '../../../../../../../../plugins/security/server'; -import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants'; - -export function initGetApiKeysApi(server, callWithRequest, routePreCheckLicenseFn) { - server.route({ - method: 'GET', - path: `${INTERNAL_API_BASE_PATH}/api_key`, - async handler(request) { - try { - const { isAdmin } = request.query; - - const result = await callWithRequest( - request, - 'shield.getAPIKeys', - { - owner: !isAdmin - } - ); - - const validKeys = result.api_keys.filter(({ invalidated }) => !invalidated); - - return { - apiKeys: validKeys, - }; - } catch (error) { - return wrapError(error); - } - }, - config: { - pre: [routePreCheckLicenseFn], - validate: { - query: Joi.object().keys({ - isAdmin: Joi.bool().required(), - }).required(), - }, - } - }); -} diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/get.test.js b/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/get.test.js deleted file mode 100644 index 400e5b705aeb2a..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/get.test.js +++ /dev/null @@ -1,166 +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 Hapi from 'hapi'; -import Boom from 'boom'; - -import { initGetApiKeysApi } from './get'; -import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants'; - -const createMockServer = () => new Hapi.Server({ debug: false, port: 8080 }); - -describe('GET API keys', () => { - const getApiKeysTest = ( - description, - { - preCheckLicenseImpl = () => null, - callWithRequestImpl, - asserts, - isAdmin = true, - } - ) => { - test(description, async () => { - const mockServer = createMockServer(); - const pre = jest.fn().mockImplementation(preCheckLicenseImpl); - const mockCallWithRequest = jest.fn(); - - if (callWithRequestImpl) { - mockCallWithRequest.mockImplementation(callWithRequestImpl); - } - - initGetApiKeysApi(mockServer, mockCallWithRequest, pre); - - const headers = { - authorization: 'foo', - }; - - const request = { - method: 'GET', - url: `${INTERNAL_API_BASE_PATH}/api_key?isAdmin=${isAdmin}`, - headers, - }; - - const { result, statusCode } = await mockServer.inject(request); - - expect(pre).toHaveBeenCalled(); - - if (callWithRequestImpl) { - expect(mockCallWithRequest).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }), - 'shield.getAPIKeys', - { - owner: !isAdmin, - }, - ); - } else { - expect(mockCallWithRequest).not.toHaveBeenCalled(); - } - - expect(statusCode).toBe(asserts.statusCode); - expect(result).toEqual(asserts.result); - }); - }; - - describe('failure', () => { - getApiKeysTest('returns result of routePreCheckLicense', { - preCheckLicenseImpl: () => Boom.forbidden('test forbidden message'), - asserts: { - statusCode: 403, - result: { - error: 'Forbidden', - statusCode: 403, - message: 'test forbidden message', - }, - }, - }); - - getApiKeysTest('returns error from callWithRequest', { - callWithRequestImpl: async () => { - throw Boom.notAcceptable('test not acceptable message'); - }, - asserts: { - statusCode: 406, - result: { - error: 'Not Acceptable', - statusCode: 406, - message: 'test not acceptable message', - }, - }, - }); - }); - - describe('success', () => { - getApiKeysTest('returns API keys', { - callWithRequestImpl: async () => ({ - api_keys: - [{ - id: 'YCLV7m0BJ3xI4hhWB648', - name: 'test-api-key', - creation: 1571670001452, - expiration: 1571756401452, - invalidated: false, - username: 'elastic', - realm: 'reserved' - }] - }), - asserts: { - statusCode: 200, - result: { - apiKeys: - [{ - id: 'YCLV7m0BJ3xI4hhWB648', - name: 'test-api-key', - creation: 1571670001452, - expiration: 1571756401452, - invalidated: false, - username: 'elastic', - realm: 'reserved' - }] - }, - }, - }); - getApiKeysTest('returns only valid API keys', { - callWithRequestImpl: async () => ({ - api_keys: - [{ - id: 'YCLV7m0BJ3xI4hhWB648', - name: 'test-api-key1', - creation: 1571670001452, - expiration: 1571756401452, - invalidated: true, - username: 'elastic', - realm: 'reserved' - }, { - id: 'YCLV7m0BJ3xI4hhWB648', - name: 'test-api-key2', - creation: 1571670001452, - expiration: 1571756401452, - invalidated: false, - username: 'elastic', - realm: 'reserved' - }], - }), - asserts: { - statusCode: 200, - result: { - apiKeys: - [{ - id: 'YCLV7m0BJ3xI4hhWB648', - name: 'test-api-key2', - creation: 1571670001452, - expiration: 1571756401452, - invalidated: false, - username: 'elastic', - realm: 'reserved' - }] - }, - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/index.js b/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/index.js deleted file mode 100644 index fc55bdcc386616..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/index.js +++ /dev/null @@ -1,20 +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 { getClient } from '../../../../../../../server/lib/get_client_shield'; -import { routePreCheckLicense } from '../../../../lib/route_pre_check_license'; -import { initCheckPrivilegesApi } from './privileges'; -import { initGetApiKeysApi } from './get'; -import { initInvalidateApiKeysApi } from './invalidate'; - -export function initApiKeysApi(server) { - const callWithRequest = getClient(server).callWithRequest; - const routePreCheckLicenseFn = routePreCheckLicense(server); - - initCheckPrivilegesApi(server, callWithRequest, routePreCheckLicenseFn); - initGetApiKeysApi(server, callWithRequest, routePreCheckLicenseFn); - initInvalidateApiKeysApi(server, callWithRequest, routePreCheckLicenseFn); -} diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/invalidate.js b/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/invalidate.js deleted file mode 100644 index 293142c60be674..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/invalidate.js +++ /dev/null @@ -1,70 +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 Joi from 'joi'; -import { wrapError } from '../../../../../../../../plugins/security/server'; -import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants'; - -export function initInvalidateApiKeysApi(server, callWithRequest, routePreCheckLicenseFn) { - server.route({ - method: 'POST', - path: `${INTERNAL_API_BASE_PATH}/api_key/invalidate`, - async handler(request) { - try { - const { apiKeys, isAdmin } = request.payload; - const itemsInvalidated = []; - const errors = []; - - // Send the request to invalidate the API key and return an error if it could not be deleted. - const sendRequestToInvalidateApiKey = async (id) => { - try { - const body = { id }; - - if (!isAdmin) { - body.owner = true; - } - - await callWithRequest(request, 'shield.invalidateAPIKey', { body }); - return null; - } catch (error) { - return wrapError(error); - } - }; - - const invalidateApiKey = async ({ id, name }) => { - const error = await sendRequestToInvalidateApiKey(id); - if (error) { - errors.push({ id, name, error }); - } else { - itemsInvalidated.push({ id, name }); - } - }; - - // Invalidate all API keys in parallel. - await Promise.all(apiKeys.map((key) => invalidateApiKey(key))); - - return { - itemsInvalidated, - errors, - }; - } catch (error) { - return wrapError(error); - } - }, - config: { - pre: [routePreCheckLicenseFn], - validate: { - payload: Joi.object({ - apiKeys: Joi.array().items(Joi.object({ - id: Joi.string().required(), - name: Joi.string().required(), - })).required(), - isAdmin: Joi.bool().required(), - }) - }, - } - }); -} diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/invalidate.test.js b/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/invalidate.test.js deleted file mode 100644 index 3ed7ca94eb782a..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/invalidate.test.js +++ /dev/null @@ -1,200 +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 Hapi from 'hapi'; -import Boom from 'boom'; - -import { initInvalidateApiKeysApi } from './invalidate'; -import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants'; - -const createMockServer = () => new Hapi.Server({ debug: false, port: 8080 }); - -describe('POST invalidate', () => { - const postInvalidateTest = ( - description, - { - preCheckLicenseImpl = () => null, - callWithRequestImpls = [], - asserts, - payload, - } - ) => { - test(description, async () => { - const mockServer = createMockServer(); - const pre = jest.fn().mockImplementation(preCheckLicenseImpl); - const mockCallWithRequest = jest.fn(); - - for (const impl of callWithRequestImpls) { - mockCallWithRequest.mockImplementationOnce(impl); - } - - initInvalidateApiKeysApi(mockServer, mockCallWithRequest, pre); - - const headers = { - authorization: 'foo', - }; - - const request = { - method: 'POST', - url: `${INTERNAL_API_BASE_PATH}/api_key/invalidate`, - headers, - payload, - }; - - const { result, statusCode } = await mockServer.inject(request); - - expect(pre).toHaveBeenCalled(); - - if (asserts.callWithRequests) { - for (const args of asserts.callWithRequests) { - expect(mockCallWithRequest).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }), - ...args - ); - } - } else { - expect(mockCallWithRequest).not.toHaveBeenCalled(); - } - - expect(statusCode).toBe(asserts.statusCode); - expect(result).toEqual(asserts.result); - }); - }; - - describe('failure', () => { - postInvalidateTest('returns result of routePreCheckLicense', { - preCheckLicenseImpl: () => Boom.forbidden('test forbidden message'), - payload: { - apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], - isAdmin: true - }, - asserts: { - statusCode: 403, - result: { - error: 'Forbidden', - statusCode: 403, - message: 'test forbidden message', - }, - }, - }); - - postInvalidateTest('returns errors array from callWithRequest', { - callWithRequestImpls: [async () => { - throw Boom.notAcceptable('test not acceptable message'); - }], - payload: { - apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key', }], - isAdmin: true - }, - asserts: { - callWithRequests: [ - ['shield.invalidateAPIKey', { - body: { - id: 'si8If24B1bKsmSLTAhJV', - }, - }], - ], - statusCode: 200, - result: { - itemsInvalidated: [], - errors: [{ - id: 'si8If24B1bKsmSLTAhJV', - name: 'my-api-key', - error: Boom.notAcceptable('test not acceptable message'), - }] - }, - }, - }); - }); - - describe('success', () => { - postInvalidateTest('invalidates API keys', { - callWithRequestImpls: [async () => null], - payload: { - apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key', }], - isAdmin: true - }, - asserts: { - callWithRequests: [ - ['shield.invalidateAPIKey', { - body: { - id: 'si8If24B1bKsmSLTAhJV', - }, - }], - ], - statusCode: 200, - result: { - itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key', }], - errors: [], - }, - }, - }); - - postInvalidateTest('adds "owner" to body if isAdmin=false', { - callWithRequestImpls: [async () => null], - payload: { - apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key', }], - isAdmin: false - }, - asserts: { - callWithRequests: [ - ['shield.invalidateAPIKey', { - body: { - id: 'si8If24B1bKsmSLTAhJV', - owner: true, - }, - }], - ], - statusCode: 200, - result: { - itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], - errors: [], - }, - }, - }); - - postInvalidateTest('returns only successful invalidation requests', { - callWithRequestImpls: [ - async () => null, - async () => { - throw Boom.notAcceptable('test not acceptable message'); - }], - payload: { - apiKeys: [ - { id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key1' }, - { id: 'ab8If24B1bKsmSLTAhNC', name: 'my-api-key2' } - ], - isAdmin: true - }, - asserts: { - callWithRequests: [ - ['shield.invalidateAPIKey', { - body: { - id: 'si8If24B1bKsmSLTAhJV', - }, - }], - ['shield.invalidateAPIKey', { - body: { - id: 'ab8If24B1bKsmSLTAhNC', - }, - }], - ], - statusCode: 200, - result: { - itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key1' }], - errors: [{ - id: 'ab8If24B1bKsmSLTAhNC', - name: 'my-api-key2', - error: Boom.notAcceptable('test not acceptable message'), - }] - }, - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/privileges.js b/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/privileges.js deleted file mode 100644 index 3aa30c9a3b9bb3..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/privileges.js +++ /dev/null @@ -1,75 +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 { wrapError } from '../../../../../../../../plugins/security/server'; -import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants'; - -export function initCheckPrivilegesApi(server, callWithRequest, routePreCheckLicenseFn) { - server.route({ - method: 'GET', - path: `${INTERNAL_API_BASE_PATH}/api_key/privileges`, - async handler(request) { - try { - const result = await Promise.all([ - callWithRequest( - request, - 'shield.hasPrivileges', - { - body: { - cluster: [ - 'manage_security', - 'manage_api_key', - ], - }, - } - ), - new Promise(async (resolve, reject) => { - try { - const result = await callWithRequest( - request, - 'shield.getAPIKeys', - { - owner: true - } - ); - // If the API returns a truthy result that means it's enabled. - resolve({ areApiKeysEnabled: !!result }); - } catch (e) { - // This is a brittle dependency upon message. Tracked by https://github.com/elastic/elasticsearch/issues/47759. - if (e.message.includes('api keys are not enabled')) { - return resolve({ areApiKeysEnabled: false }); - } - - // It's a real error, so rethrow it. - reject(e); - } - }), - ]); - - const [{ - cluster: { - manage_security: manageSecurity, - manage_api_key: manageApiKey, - } - }, { - areApiKeysEnabled, - }] = result; - - const isAdmin = manageSecurity || manageApiKey; - - return { - areApiKeysEnabled, - isAdmin, - }; - } catch (error) { - return wrapError(error); - } - }, - config: { - pre: [routePreCheckLicenseFn] - } - }); -} diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/privileges.test.js b/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/privileges.test.js deleted file mode 100644 index 2a6f935e005950..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/api_keys/privileges.test.js +++ /dev/null @@ -1,254 +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 Hapi from 'hapi'; -import Boom from 'boom'; - -import { initCheckPrivilegesApi } from './privileges'; -import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants'; - -const createMockServer = () => new Hapi.Server({ debug: false, port: 8080 }); - -describe('GET privileges', () => { - const getPrivilegesTest = ( - description, - { - preCheckLicenseImpl = () => null, - callWithRequestImpls = [], - asserts, - } - ) => { - test(description, async () => { - const mockServer = createMockServer(); - const pre = jest.fn().mockImplementation(preCheckLicenseImpl); - const mockCallWithRequest = jest.fn(); - - for (const impl of callWithRequestImpls) { - mockCallWithRequest.mockImplementationOnce(impl); - } - - initCheckPrivilegesApi(mockServer, mockCallWithRequest, pre); - - const headers = { - authorization: 'foo', - }; - - const request = { - method: 'GET', - url: `${INTERNAL_API_BASE_PATH}/api_key/privileges`, - headers, - }; - - const { result, statusCode } = await mockServer.inject(request); - - expect(pre).toHaveBeenCalled(); - - if (asserts.callWithRequests) { - for (const args of asserts.callWithRequests) { - expect(mockCallWithRequest).toHaveBeenCalledWith( - expect.objectContaining({ - headers: expect.objectContaining({ - authorization: headers.authorization, - }), - }), - ...args - ); - } - } else { - expect(mockCallWithRequest).not.toHaveBeenCalled(); - } - - expect(statusCode).toBe(asserts.statusCode); - expect(result).toEqual(asserts.result); - }); - }; - - describe('failure', () => { - getPrivilegesTest('returns result of routePreCheckLicense', { - preCheckLicenseImpl: () => Boom.forbidden('test forbidden message'), - asserts: { - statusCode: 403, - result: { - error: 'Forbidden', - statusCode: 403, - message: 'test forbidden message', - }, - }, - }); - - getPrivilegesTest('returns error from first callWithRequest', { - callWithRequestImpls: [async () => { - throw Boom.notAcceptable('test not acceptable message'); - }, async () => { }], - asserts: { - callWithRequests: [ - ['shield.hasPrivileges', { - body: { - cluster: [ - 'manage_security', - 'manage_api_key', - ], - }, - }], - ['shield.getAPIKeys', { owner: true }], - ], - statusCode: 406, - result: { - error: 'Not Acceptable', - statusCode: 406, - message: 'test not acceptable message', - }, - }, - }); - - getPrivilegesTest('returns error from second callWithRequest', { - callWithRequestImpls: [async () => { }, async () => { - throw Boom.notAcceptable('test not acceptable message'); - }], - asserts: { - callWithRequests: [ - ['shield.hasPrivileges', { - body: { - cluster: [ - 'manage_security', - 'manage_api_key', - ], - }, - }], - ['shield.getAPIKeys', { owner: true }], - ], - statusCode: 406, - result: { - error: 'Not Acceptable', - statusCode: 406, - message: 'test not acceptable message', - }, - }, - }); - }); - - describe('success', () => { - getPrivilegesTest('returns areApiKeysEnabled and isAdmin', { - callWithRequestImpls: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: true, manage_security: true }, - index: {}, - application: {} - }), - async () => ( - { - api_keys: - [{ - id: 'si8If24B1bKsmSLTAhJV', - name: 'my-api-key', - creation: 1574089261632, - expiration: 1574175661632, - invalidated: false, - username: 'elastic', - realm: 'reserved' - }] - } - ), - ], - asserts: { - callWithRequests: [ - ['shield.getAPIKeys', { owner: true }], - ['shield.hasPrivileges', { - body: { - cluster: [ - 'manage_security', - 'manage_api_key', - ], - }, - }], - ], - statusCode: 200, - result: { - areApiKeysEnabled: true, - isAdmin: true, - }, - }, - }); - - getPrivilegesTest('returns areApiKeysEnabled=false when getAPIKeys error message includes "api keys are not enabled"', { - callWithRequestImpls: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: true, manage_security: true }, - index: {}, - application: {} - }), - async () => { - throw Boom.unauthorized('api keys are not enabled'); - }, - ], - asserts: { - callWithRequests: [ - ['shield.getAPIKeys', { owner: true }], - ['shield.hasPrivileges', { - body: { - cluster: [ - 'manage_security', - 'manage_api_key', - ], - }, - }], - ], - statusCode: 200, - result: { - areApiKeysEnabled: false, - isAdmin: true, - }, - }, - }); - - getPrivilegesTest('returns isAdmin=false when user has insufficient privileges', { - callWithRequestImpls: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: false, manage_security: false }, - index: {}, - application: {} - }), - async () => ( - { - api_keys: - [{ - id: 'si8If24B1bKsmSLTAhJV', - name: 'my-api-key', - creation: 1574089261632, - expiration: 1574175661632, - invalidated: false, - username: 'elastic', - realm: 'reserved' - }] - } - ), - ], - asserts: { - callWithRequests: [ - ['shield.getAPIKeys', { owner: true }], - ['shield.hasPrivileges', { - body: { - cluster: [ - 'manage_security', - 'manage_api_key', - ], - }, - }], - ], - statusCode: 200, - result: { - areApiKeysEnabled: true, - isAdmin: false, - }, - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js b/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js deleted file mode 100644 index f37c9a2fd917f1..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/authenticate.js +++ /dev/null @@ -1,227 +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 Joi from 'joi'; -import { schema } from '@kbn/config-schema'; -import { canRedirectRequest, wrapError, OIDCAuthenticationFlow } from '../../../../../../../plugins/security/server'; -import { KibanaRequest } from '../../../../../../../../src/core/server'; -import { createCSPRuleString } from '../../../../../../../../src/legacy/server/csp'; - -export function initAuthenticateApi({ authc: { login, logout }, __legacyCompat: { config } }, server) { - function prepareCustomResourceResponse(response, contentType) { - return response - .header('cache-control', 'private, no-cache, no-store') - .header('content-security-policy', createCSPRuleString(server.config().get('csp.rules'))) - .type(contentType); - } - - server.route({ - method: 'POST', - path: '/api/security/v1/login', - config: { - auth: false, - validate: { - payload: Joi.object({ - username: Joi.string().required(), - password: Joi.string().required() - }) - }, - response: { - emptyStatusCode: 204, - } - }, - async handler(request, h) { - const { username, password } = request.payload; - - try { - // We should prefer `token` over `basic` if possible. - const providerToLoginWith = config.authc.providers.includes('token') - ? 'token' - : 'basic'; - const authenticationResult = await login(KibanaRequest.from(request), { - provider: providerToLoginWith, - value: { username, password } - }); - - if (!authenticationResult.succeeded()) { - throw Boom.unauthorized(authenticationResult.error); - } - - return h.response(); - } catch(err) { - throw wrapError(err); - } - } - }); - - /** - * The route should be configured as a redirect URI in OP when OpenID Connect implicit flow - * is used, so that we can extract authentication response from URL fragment and send it to - * the `/api/security/v1/oidc` route. - */ - server.route({ - method: 'GET', - path: '/api/security/v1/oidc/implicit', - config: { auth: false }, - async handler(request, h) { - return prepareCustomResourceResponse( - h.response(` - - Kibana OpenID Connect Login - - `), - 'text/html' - ); - } - }); - - /** - * The route that accompanies `/api/security/v1/oidc/implicit` and renders a JavaScript snippet - * that extracts fragment part from the URL and send it to the `/api/security/v1/oidc` route. - * We need this separate endpoint because of default CSP policy that forbids inline scripts. - */ - server.route({ - method: 'GET', - path: '/api/security/v1/oidc/implicit.js', - config: { auth: false }, - async handler(request, h) { - return prepareCustomResourceResponse( - h.response(` - window.location.replace( - '${server.config().get('server.basePath')}/api/security/v1/oidc?authenticationResponseURI=' + - encodeURIComponent(window.location.href) - ); - `), - 'text/javascript' - ); - } - }); - - server.route({ - // POST is only allowed for Third Party initiated authentication - // Consider splitting this route into two (GET and POST) when it's migrated to New Platform. - method: ['GET', 'POST'], - path: '/api/security/v1/oidc', - config: { - auth: false, - validate: { - query: Joi.object().keys({ - iss: Joi.string().uri({ scheme: 'https' }), - login_hint: Joi.string(), - target_link_uri: Joi.string().uri(), - code: Joi.string(), - error: Joi.string(), - error_description: Joi.string(), - error_uri: Joi.string().uri(), - state: Joi.string(), - authenticationResponseURI: Joi.string(), - }).unknown(), - } - }, - async handler(request, h) { - try { - const query = request.query || {}; - const payload = request.payload || {}; - - // An HTTP GET request with a query parameter named `authenticationResponseURI` that includes URL fragment OpenID - // Connect Provider sent during implicit authentication flow to the Kibana own proxy page that extracted that URL - // fragment and put it into `authenticationResponseURI` query string parameter for this endpoint. See more details - // at https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth - let loginAttempt; - if (query.authenticationResponseURI) { - loginAttempt = { - flow: OIDCAuthenticationFlow.Implicit, - authenticationResponseURI: query.authenticationResponseURI, - }; - } else if (query.code || query.error) { - // An HTTP GET request with a query parameter named `code` (or `error`) as the response to a successful (or - // failed) authentication from an OpenID Connect Provider during authorization code authentication flow. - // See more details at https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. - loginAttempt = { - flow: OIDCAuthenticationFlow.AuthorizationCode, - // We pass the path only as we can't be sure of the full URL and Elasticsearch doesn't need it anyway. - authenticationResponseURI: request.url.path, - }; - } else if (query.iss || payload.iss) { - // An HTTP GET request with a query parameter named `iss` or an HTTP POST request with the same parameter in the - // payload as part of a 3rd party initiated authentication. See more details at - // https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin - loginAttempt = { - flow: OIDCAuthenticationFlow.InitiatedBy3rdParty, - iss: query.iss || payload.iss, - loginHint: query.login_hint || payload.login_hint, - }; - } - - if (!loginAttempt) { - throw Boom.badRequest('Unrecognized login attempt.'); - } - - // We handle the fact that the user might get redirected to Kibana while already having an session - // Return an error notifying the user they are already logged in. - const authenticationResult = await login(KibanaRequest.from(request), { - provider: 'oidc', - value: loginAttempt - }); - if (authenticationResult.succeeded()) { - return Boom.forbidden( - 'Sorry, you already have an active Kibana session. ' + - 'If you want to start a new one, please logout from the existing session first.' - ); - } - - if (authenticationResult.redirected()) { - return h.redirect(authenticationResult.redirectURL); - } - - throw Boom.unauthorized(authenticationResult.error); - } catch (err) { - throw wrapError(err); - } - } - }); - - server.route({ - method: 'GET', - path: '/api/security/v1/logout', - config: { - auth: false - }, - async handler(request, h) { - if (!canRedirectRequest(KibanaRequest.from(request))) { - throw Boom.badRequest('Client should be able to process redirect response.'); - } - - try { - const deauthenticationResult = await logout( - // Allow unknown query parameters as this endpoint can be hit by the 3rd-party with any - // set of query string parameters (e.g. SAML/OIDC logout request parameters). - KibanaRequest.from(request, { - query: schema.object({}, { allowUnknowns: true }), - }) - ); - if (deauthenticationResult.failed()) { - throw wrapError(deauthenticationResult.error); - } - - return h.redirect( - deauthenticationResult.redirectURL || `${server.config().get('server.basePath')}/` - ); - } catch (err) { - throw wrapError(err); - } - } - }); - - server.route({ - method: 'GET', - path: '/api/security/v1/me', - handler(request) { - return request.auth.credentials; - } - }); -} diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/indices.js b/x-pack/legacy/plugins/security/server/routes/api/v1/indices.js deleted file mode 100644 index 7265b83783fdd2..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/indices.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import { getClient } from '../../../../../../server/lib/get_client_shield'; -import { wrapError } from '../../../../../../../plugins/security/server'; - -export function initIndicesApi(server) { - const callWithRequest = getClient(server).callWithRequest; - - server.route({ - method: 'GET', - path: '/api/security/v1/fields/{query}', - handler(request) { - return callWithRequest(request, 'indices.getFieldMapping', { - index: request.params.query, - fields: '*', - allowNoIndices: false, - includeDefaults: true - }) - .then((mappings) => - _(mappings) - .map('mappings') - .flatten() - .map(_.keys) - .flatten() - .uniq() - .value() - ) - .catch(wrapError); - } - }); -} diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/users.js b/x-pack/legacy/plugins/security/server/routes/api/v1/users.js deleted file mode 100644 index d6dc39da657b12..00000000000000 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/users.js +++ /dev/null @@ -1,142 +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 _ from 'lodash'; -import Boom from 'boom'; -import Joi from 'joi'; -import { getClient } from '../../../../../../server/lib/get_client_shield'; -import { userSchema } from '../../../lib/user_schema'; -import { routePreCheckLicense } from '../../../lib/route_pre_check_license'; -import { wrapError } from '../../../../../../../plugins/security/server'; -import { KibanaRequest } from '../../../../../../../../src/core/server'; - -export function initUsersApi({ authc: { login }, __legacyCompat: { config } }, server) { - const callWithRequest = getClient(server).callWithRequest; - const routePreCheckLicenseFn = routePreCheckLicense(server); - - server.route({ - method: 'GET', - path: '/api/security/v1/users', - handler(request) { - return callWithRequest(request, 'shield.getUser').then( - _.values, - wrapError - ); - }, - config: { - pre: [routePreCheckLicenseFn] - } - }); - - server.route({ - method: 'GET', - path: '/api/security/v1/users/{username}', - handler(request) { - const username = request.params.username; - return callWithRequest(request, 'shield.getUser', { username }).then( - (response) => { - if (response[username]) return response[username]; - throw Boom.notFound(); - }, - wrapError); - }, - config: { - pre: [routePreCheckLicenseFn] - } - }); - - server.route({ - method: 'POST', - path: '/api/security/v1/users/{username}', - handler(request) { - const username = request.params.username; - const body = _(request.payload).omit(['username', 'enabled']).omit(_.isNull); - return callWithRequest(request, 'shield.putUser', { username, body }).then( - () => request.payload, - wrapError); - }, - config: { - validate: { - payload: userSchema - }, - pre: [routePreCheckLicenseFn] - } - }); - - server.route({ - method: 'DELETE', - path: '/api/security/v1/users/{username}', - handler(request, h) { - const username = request.params.username; - return callWithRequest(request, 'shield.deleteUser', { username }).then( - () => h.response().code(204), - wrapError); - }, - config: { - pre: [routePreCheckLicenseFn] - } - }); - - server.route({ - method: 'POST', - path: '/api/security/v1/users/{username}/password', - async handler(request, h) { - const username = request.params.username; - const { password, newPassword } = request.payload; - const isCurrentUser = username === request.auth.credentials.username; - - // We should prefer `token` over `basic` if possible. - const providerToLoginWith = config.authc.providers.includes('token') - ? 'token' - : 'basic'; - - // If user tries to change own password, let's check if old password is valid first by trying - // to login. - if (isCurrentUser) { - const authenticationResult = await login(KibanaRequest.from(request), { - provider: providerToLoginWith, - value: { username, password }, - // We shouldn't alter authentication state just yet. - stateless: true, - }); - - if (!authenticationResult.succeeded()) { - return Boom.forbidden(authenticationResult.error); - } - } - - try { - const body = { password: newPassword }; - await callWithRequest(request, 'shield.changePassword', { username, body }); - - // Now we authenticate user with the new password again updating current session if any. - if (isCurrentUser) { - const authenticationResult = await login(KibanaRequest.from(request), { - provider: providerToLoginWith, - value: { username, password: newPassword } - }); - - if (!authenticationResult.succeeded()) { - return Boom.unauthorized((authenticationResult.error)); - } - } - } catch(err) { - return wrapError(err); - } - - return h.response().code(204); - }, - config: { - validate: { - payload: Joi.object({ - password: Joi.string(), - newPassword: Joi.string().required() - }) - }, - pre: [routePreCheckLicenseFn] - } - }); -} diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index c3494c09699008..f08a6e66c3400f 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -45,6 +45,7 @@ export const SIGNALS_ID = `${APP_ID}.signals`; */ export const DETECTION_ENGINE_URL = '/api/detection_engine'; export const DETECTION_ENGINE_RULES_URL = `${DETECTION_ENGINE_URL}/rules`; +export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`; export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`; /** diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts index 8a9477ad67901c..b2b8ce7b9c0003 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts @@ -39,7 +39,7 @@ const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; /** * The Kibana server endpoint used for authentication */ -const LOGIN_API_ENDPOINT = '/api/security/v1/login'; +const LOGIN_API_ENDPOINT = '/internal/security/login'; /** * Authenticates with Kibana using, if specified, credentials specified by @@ -68,7 +68,7 @@ const credentialsProvidedByEnvironment = (): boolean => * Authenticates with Kibana by reading credentials from the * `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD` * environment variables, and POSTing the username and password directly to - * Kibana's `security/v1/login` endpoint, bypassing the login page (for speed). + * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). */ const loginViaEnvironmentCredentials = () => { cy.log( @@ -90,7 +90,7 @@ const loginViaEnvironmentCredentials = () => { /** * Authenticates with Kibana by reading credentials from the * `kibana.dev.yml` file and POSTing the username and password directly to - * Kibana's `security/v1/login` endpoint, bypassing the login page (for speed). + * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). */ const loginViaConfig = () => { cy.log( diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index 65b673e1c72a51..219c59dbf11a36 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -19,6 +19,7 @@ import { querySignalsRoute } from './lib/detection_engine/routes/signals/query_s import { ServerFacade } from './types'; import { deleteIndexRoute } from './lib/detection_engine/routes/index/delete_index_route'; import { isAlertExecutor } from './lib/detection_engine/signals/types'; +import { readPrivilegesRoute } from './lib/detection_engine/routes/privileges/read_privileges_route'; const APP_ID = 'siem'; @@ -52,4 +53,7 @@ export const initServerWithKibana = (context: PluginInitializerContext, __legacy createIndexRoute(__legacy); readIndexRoute(__legacy); deleteIndexRoute(__legacy); + + // Privileges API to get the generic user privileges + readPrivilegesRoute(__legacy); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts index 9c8dca0cb370f4..d7cb922b5b6c38 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/create_bootstrap_index.ts @@ -5,7 +5,7 @@ */ import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; // See the reference(s) below on explanations about why -000001 was chosen and // why the is_write_index is true as well as the bootstrapping step which is needed. diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_all_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_all_index.ts index 6f16eb8fbdeb18..b1d8f994615aea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_all_index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_all_index.ts @@ -6,7 +6,7 @@ import { IndicesDeleteParams } from 'elasticsearch'; import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const deleteAllIndex = async ( callWithRequest: CallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts index 153b9ae4e4136c..92003f165d9962 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_policy.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const deletePolicy = async ( callWithRequest: CallWithRequest<{ path: string; method: 'DELETE' }, {}, unknown>, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_template.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_template.ts index b048dd27efb83b..63c32d13ccb8de 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_template.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/delete_template.ts @@ -6,7 +6,7 @@ import { IndicesDeleteTemplateParams } from 'elasticsearch'; import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const deleteTemplate = async ( callWithRequest: CallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.ts index 24164e894788a4..ff65caa59a866e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.ts @@ -6,7 +6,7 @@ import { IndicesExistsParams } from 'elasticsearch'; import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const getIndexExists = async ( callWithRequest: CallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts index 847c32d9d61fb3..7541c4217b387e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_policy_exists.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const getPolicyExists = async ( callWithRequest: CallWithRequest<{ path: string; method: 'GET' }, {}, unknown>, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_template_exists.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_template_exists.ts index 482fc8d855828c..fac402155619e2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_template_exists.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_template_exists.ts @@ -6,7 +6,7 @@ import { IndicesExistsTemplateParams } from 'elasticsearch'; import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const getTemplateExists = async ( callWithRequest: CallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/read_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/read_index.ts index 6c9d529078a77d..0abe2b992b7804 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/read_index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/read_index.ts @@ -6,7 +6,7 @@ import { IndicesGetSettingsParams } from 'elasticsearch'; import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const readIndex = async ( callWithRequest: CallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts index 2511984b412f34..115f0af75898c7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_policy.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const setPolicy = async ( callWithRequest: CallWithRequest<{ path: string; method: 'PUT'; body: unknown }, {}, unknown>, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_template.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_template.ts index a679a61e10c001..dc9ad5dda9f7dc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_template.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/set_template.ts @@ -6,7 +6,7 @@ import { IndicesPutTemplateParams } from 'elasticsearch'; import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -import { CallWithRequest } from './types'; +import { CallWithRequest } from '../types'; export const setTemplate = async ( callWithRequest: CallWithRequest, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts new file mode 100644 index 00000000000000..3b84075b9e435a --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/privileges/read_privileges.ts @@ -0,0 +1,69 @@ +/* + * 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 { CallWithRequest } from '../types'; + +export const readPrivileges = async ( + callWithRequest: CallWithRequest, + index: string +): Promise => { + return callWithRequest('transport.request', { + path: `_security/user/_has_privileges`, + method: 'POST', + body: { + cluster: [ + 'all', + 'create_snapshot', + 'manage', + 'manage_api_key', + 'manage_ccr', + 'manage_transform', + 'manage_ilm', + 'manage_index_templates', + 'manage_ingest_pipelines', + 'manage_ml', + 'manage_own_api_key', + 'manage_pipeline', + 'manage_rollup', + 'manage_saml', + 'manage_security', + 'manage_token', + 'manage_watcher', + 'monitor', + 'monitor_transform', + 'monitor_ml', + 'monitor_rollup', + 'monitor_watcher', + 'read_ccr', + 'read_ilm', + 'transport_client', + ], + index: [ + { + names: [index], + privileges: [ + 'all', + 'create', + 'create_doc', + 'create_index', + 'delete', + 'delete_index', + 'index', + 'manage', + 'manage_follow_index', + 'manage_ilm', + 'manage_leader_index', + 'monitor', + 'read', + 'read_cross_cluster', + 'view_index_metadata', + 'write', + ], + }, + ], + }, + }); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 86726187c4fbd3..cb24b0d0c89b15 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -10,6 +10,7 @@ import { SignalsStatusRestParams, SignalsQueryRestParams } from '../../signals/t import { DETECTION_ENGINE_RULES_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, + DETECTION_ENGINE_PRIVILEGES_URL, DETECTION_ENGINE_QUERY_SIGNALS_URL, } from '../../../../../common/constants'; import { RuleAlertType } from '../../rules/types'; @@ -81,6 +82,11 @@ export const getFindRequest = (): ServerInjectOptions => ({ url: `${DETECTION_ENGINE_RULES_URL}/_find`, }); +export const getPrivilegeRequest = (): ServerInjectOptions => ({ + method: 'GET', + url: `${DETECTION_ENGINE_PRIVILEGES_URL}`, +}); + interface FindHit { page: number; perPage: number; @@ -225,3 +231,56 @@ export const updateActionResult = (): ActionResult => ({ name: '', config: {}, }); + +export const getMockPrivileges = () => ({ + username: 'test-space', + has_all_requested: false, + cluster: { + monitor_ml: true, + manage_ccr: false, + manage_index_templates: true, + monitor_watcher: true, + monitor_transform: true, + read_ilm: true, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: true, + manage_ingest_pipelines: true, + read_ccr: false, + manage_rollup: true, + monitor: true, + manage_watcher: true, + manage: true, + manage_transform: true, + manage_token: false, + manage_ml: true, + manage_pipeline: true, + monitor_rollup: true, + transport_client: true, + create_snapshot: true, + }, + index: { + '.siem-signals-frank-hassanabad-test-space': { + all: false, + manage_ilm: true, + read: false, + create_index: true, + read_cross_cluster: false, + index: false, + monitor: true, + delete: false, + manage: true, + delete_index: true, + create_doc: false, + view_index_metadata: true, + create: false, + manage_follow_index: true, + manage_leader_index: true, + write: false, + }, + }, + application: {}, +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts index 94c42664c281d0..0eb090179b1925 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts @@ -48,7 +48,7 @@ export const createCreateIndexRoute = (server: ServerFacade): Hapi.ServerRoute = const template = getSignalsTemplate(index); await setTemplate(callWithRequest, index, template); } - createBootstrapIndex(callWithRequest, index); + await createBootstrapIndex(callWithRequest, index); return { acknowledged: true }; } } catch (err) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts new file mode 100644 index 00000000000000..1ea681afb79491 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.test.ts @@ -0,0 +1,39 @@ +/* + * 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 { createMockServer } from '../__mocks__/_mock_server'; +import { getPrivilegeRequest, getMockPrivileges } from '../__mocks__/request_responses'; +import { readPrivilegesRoute } from './read_privileges_route'; +import * as myUtils from '../utils'; + +describe('read_privileges', () => { + let { server, elasticsearch } = createMockServer(); + + beforeEach(() => { + jest.spyOn(myUtils, 'getIndex').mockReturnValue('fakeindex'); + ({ server, elasticsearch } = createMockServer()); + elasticsearch.getCluster = jest.fn(() => ({ + callWithRequest: jest.fn(() => getMockPrivileges()), + })); + readPrivilegesRoute(server); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('normal status codes', () => { + test('returns 200 when doing a normal request', async () => { + const { statusCode } = await server.inject(getPrivilegeRequest()); + expect(statusCode).toBe(200); + }); + + test('returns the payload when doing a normal request', async () => { + const { payload } = await server.inject(getPrivilegeRequest()); + expect(JSON.parse(payload)).toEqual(getMockPrivileges()); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts new file mode 100644 index 00000000000000..457de05674f661 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts @@ -0,0 +1,41 @@ +/* + * 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 Hapi from 'hapi'; +import { DETECTION_ENGINE_PRIVILEGES_URL } from '../../../../../common/constants'; +import { RulesRequest } from '../../rules/types'; +import { ServerFacade } from '../../../../types'; +import { callWithRequestFactory, transformError, getIndex } from '../utils'; +import { readPrivileges } from '../../privileges/read_privileges'; + +export const createReadPrivilegesRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'GET', + path: DETECTION_ENGINE_PRIVILEGES_URL, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + }, + }, + async handler(request: RulesRequest) { + try { + const callWithRequest = callWithRequestFactory(request, server); + const index = getIndex(request, server); + const permissions = await readPrivileges(callWithRequest, index); + return permissions; + } catch (err) { + return transformError(err); + } + }, + }; +}; + +export const readPrivilegesRoute = (server: ServerFacade): void => { + server.route(createReadPrivilegesRulesRoute(server)); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_privileges.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_privileges.sh new file mode 100755 index 00000000000000..f82a0b6b34abf8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_privileges.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./get_privileges.sh +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/detection_engine/privileges | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts index d02595c368aa75..bb616554042f4f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts @@ -65,3 +65,5 @@ export type OutputRuleAlertRest = RuleAlertParamsRest & { created_by: string | undefined | null; updated_by: string | undefined | null; }; + +export type CallWithRequest = (endpoint: string, params: T, options?: U) => Promise; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_page_link.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_page_link.test.tsx.snap index e52977749142d9..376f1aa54f532b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_page_link.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_page_link.test.tsx.snap @@ -4,7 +4,6 @@ exports[`MonitorPageLink component renders a help link when link parameters pres @@ -14,7 +13,6 @@ exports[`MonitorPageLink component renders the link properly 1`] = ` diff --git a/x-pack/legacy/server/lib/esjs_shield_plugin.js b/x-pack/legacy/server/lib/esjs_shield_plugin.js deleted file mode 100644 index b6252035aa321f..00000000000000 --- a/x-pack/legacy/server/lib/esjs_shield_plugin.js +++ /dev/null @@ -1,579 +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. - */ - -(function (root, factory) { - if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef - define([], factory); // eslint-disable-line no-undef - } else if (typeof exports === 'object') { - module.exports = factory(); - } else { - root.ElasticsearchShield = factory(); - } -}(this, function () { - return function addShieldApi(Client, config, components) { - const ca = components.clientAction.factory; - - Client.prototype.shield = components.clientAction.namespaceFactory(); - const shield = Client.prototype.shield.prototype; - - /** - * Perform a [shield.authenticate](Retrieve details about the currently authenticated user) request - * - * @param {Object} params - An object with parameters used to carry out this action - */ - shield.authenticate = ca({ - params: {}, - url: { - fmt: '/_security/_authenticate' - } - }); - - /** - * Perform a [shield.changePassword](Change the password of a user) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.username - The username of the user to change the password for - */ - shield.changePassword = ca({ - params: { - refresh: { - type: 'boolean' - } - }, - urls: [ - { - fmt: '/_security/user/<%=username%>/_password', - req: { - username: { - type: 'string', - required: false - } - } - }, - { - fmt: '/_security/user/_password' - } - ], - needBody: true, - method: 'POST' - }); - - /** - * Perform a [shield.clearCachedRealms](Clears the internal user caches for specified realms) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String} params.usernames - Comma-separated list of usernames to clear from the cache - * @param {String} params.realms - Comma-separated list of realms to clear - */ - shield.clearCachedRealms = ca({ - params: { - usernames: { - type: 'string', - required: false - } - }, - url: { - fmt: '/_security/realm/<%=realms%>/_clear_cache', - req: { - realms: { - type: 'string', - required: true - } - } - }, - method: 'POST' - }); - - /** - * Perform a [shield.clearCachedRoles](Clears the internal caches for specified roles) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String} params.name - Role name - */ - shield.clearCachedRoles = ca({ - params: {}, - url: { - fmt: '/_security/role/<%=name%>/_clear_cache', - req: { - name: { - type: 'string', - required: true - } - } - }, - method: 'POST' - }); - - /** - * Perform a [shield.deleteRole](Remove a role from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.name - Role name - */ - shield.deleteRole = ca({ - params: { - refresh: { - type: 'boolean' - } - }, - url: { - fmt: '/_security/role/<%=name%>', - req: { - name: { - type: 'string', - required: true - } - } - }, - method: 'DELETE' - }); - - /** - * Perform a [shield.deleteUser](Remove a user from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.username - username - */ - shield.deleteUser = ca({ - params: { - refresh: { - type: 'boolean' - } - }, - url: { - fmt: '/_security/user/<%=username%>', - req: { - username: { - type: 'string', - required: true - } - } - }, - method: 'DELETE' - }); - - /** - * Perform a [shield.getRole](Retrieve one or more roles from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String} params.name - Role name - */ - shield.getRole = ca({ - params: {}, - urls: [ - { - fmt: '/_security/role/<%=name%>', - req: { - name: { - type: 'string', - required: false - } - } - }, - { - fmt: '/_security/role' - } - ] - }); - - /** - * Perform a [shield.getUser](Retrieve one or more users from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String, String[], Boolean} params.username - A comma-separated list of usernames - */ - shield.getUser = ca({ - params: {}, - urls: [ - { - fmt: '/_security/user/<%=username%>', - req: { - username: { - type: 'list', - required: false - } - } - }, - { - fmt: '/_security/user' - } - ] - }); - - /** - * Perform a [shield.putRole](Update or create a role for the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.name - Role name - */ - shield.putRole = ca({ - params: { - refresh: { - type: 'boolean' - } - }, - url: { - fmt: '/_security/role/<%=name%>', - req: { - name: { - type: 'string', - required: true - } - } - }, - needBody: true, - method: 'PUT' - }); - - /** - * Perform a [shield.putUser](Update or create a user for the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.username - The username of the User - */ - shield.putUser = ca({ - params: { - refresh: { - type: 'boolean' - } - }, - url: { - fmt: '/_security/user/<%=username%>', - req: { - username: { - type: 'string', - required: true - } - } - }, - needBody: true, - method: 'PUT' - }); - - /** - * Perform a [shield.getUserPrivileges](Retrieve a user's list of privileges) request - * - */ - shield.getUserPrivileges = ca({ - params: {}, - urls: [ - { - fmt: '/_security/user/_privileges' - } - ] - }); - - /** - * Asks Elasticsearch to prepare SAML authentication request to be sent to - * the 3rd-party SAML identity provider. - * - * @param {string} [acs] Optional assertion consumer service URL to use for SAML request or URL - * in the Kibana to which identity provider will post SAML response. Based on the ACS Elasticsearch - * will choose the right SAML realm. - * - * @param {string} [realm] Optional name of the Elasticsearch SAML realm to use to handle request. - * - * @returns {{realm: string, id: string, redirect: string}} Object that includes identifier - * of the SAML realm used to prepare authentication request, encrypted request token to be - * sent to Elasticsearch with SAML response and redirect URL to the identity provider that - * will be used to authenticate user. - */ - shield.samlPrepare = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/saml/prepare' - } - }); - - /** - * Sends SAML response returned by identity provider to Elasticsearch for validation. - * - * @param {Array.} ids A list of encrypted request tokens returned within SAML - * preparation response. - * @param {string} content SAML response returned by identity provider. - * @param {string} [realm] Optional string used to identify the name of the OpenID Connect realm - * that should be used to authenticate request. - * - * @returns {{username: string, access_token: string, expires_in: number}} Object that - * includes name of the user, access token to use for any consequent requests that - * need to be authenticated and a number of seconds after which access token will expire. - */ - shield.samlAuthenticate = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/saml/authenticate' - } - }); - - /** - * Invalidates SAML access token. - * - * @param {string} token SAML access token that needs to be invalidated. - * - * @returns {{redirect?: string}} - */ - shield.samlLogout = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/saml/logout' - } - }); - - /** - * Invalidates SAML session based on Logout Request received from the Identity Provider. - * - * @param {string} queryString URL encoded query string provided by Identity Provider. - * @param {string} [acs] Optional assertion consumer service URL to use for SAML request or URL in the - * Kibana to which identity provider will post SAML response. Based on the ACS Elasticsearch - * will choose the right SAML realm to invalidate session. - * @param {string} [realm] Optional name of the Elasticsearch SAML realm to use to handle request. - * - * @returns {{redirect?: string}} - */ - shield.samlInvalidate = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/saml/invalidate' - } - }); - - /** - * Asks Elasticsearch to prepare an OpenID Connect authentication request to be sent to - * the 3rd-party OpenID Connect provider. - * - * @param {string} realm The OpenID Connect realm name in Elasticsearch - * - * @returns {{state: string, nonce: string, redirect: string}} Object that includes two opaque parameters that need - * to be sent to Elasticsearch with the OpenID Connect response and redirect URL to the OpenID Connect provider that - * will be used to authenticate user. - */ - shield.oidcPrepare = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/oidc/prepare' - } - }); - - /** - * Sends the URL to which the OpenID Connect Provider redirected the UA to Elasticsearch for validation. - * - * @param {string} state The state parameter that was returned by Elasticsearch in the - * preparation response. - * @param {string} nonce The nonce parameter that was returned by Elasticsearch in the - * preparation response. - * @param {string} redirect_uri The URL to where the UA was redirected by the OpenID Connect provider. - * @param {string} [realm] Optional string used to identify the name of the OpenID Connect realm - * that should be used to authenticate request. - * - * @returns {{username: string, access_token: string, refresh_token; string, expires_in: number}} Object that - * includes name of the user, access token to use for any consequent requests that - * need to be authenticated and a number of seconds after which access token will expire. - */ - shield.oidcAuthenticate = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/oidc/authenticate' - } - }); - - /** - * Invalidates an access token and refresh token pair that was generated after an OpenID Connect authentication. - * - * @param {string} token An access token that was created by authenticating to an OpenID Connect realm and - * that needs to be invalidated. - * @param {string} refresh_token A refresh token that was created by authenticating to an OpenID Connect realm and - * that needs to be invalidated. - * - * @returns {{redirect?: string}} If the Elasticsearch OpenID Connect realm configuration and the - * OpenID Connect provider supports RP-initiated SLO, a URL to redirect the UA - */ - shield.oidcLogout = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/oidc/logout' - } - }); - - /** - * Refreshes an access token. - * - * @param {string} grant_type Currently only "refresh_token" grant type is supported. - * @param {string} refresh_token One-time refresh token that will be exchanged to the new access/refresh token pair. - * - * @returns {{access_token: string, type: string, expires_in: number, refresh_token: string}} - */ - shield.getAccessToken = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/oauth2/token' - } - }); - - /** - * Invalidates an access token. - * - * @param {string} token The access token to invalidate - * - * @returns {{created: boolean}} - */ - shield.deleteAccessToken = ca({ - method: 'DELETE', - needBody: true, - params: { - token: { - type: 'string' - } - }, - url: { - fmt: '/_security/oauth2/token' - } - }); - - shield.getPrivilege = ca({ - method: 'GET', - urls: [{ - fmt: '/_security/privilege/<%=privilege%>', - req: { - privilege: { - type: 'string', - required: false - } - } - }, { - fmt: '/_security/privilege' - }] - }); - - shield.deletePrivilege = ca({ - method: 'DELETE', - urls: [{ - fmt: '/_security/privilege/<%=application%>/<%=privilege%>', - req: { - application: { - type: 'string', - required: true - }, - privilege: { - type: 'string', - required: true - } - } - }] - }); - - shield.postPrivileges = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/privilege' - } - }); - - shield.hasPrivileges = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/user/_has_privileges' - } - }); - - shield.getBuiltinPrivileges = ca({ - params: {}, - urls: [ - { - fmt: '/_security/privilege/_builtin' - } - ] - }); - - /** - * Gets API keys in Elasticsearch - * @param {boolean} owner A boolean flag that can be used to query API keys owned by the currently authenticated user. - * Defaults to false. The realm_name or username parameters cannot be specified when this parameter is set to true as - * they are assumed to be the currently authenticated ones. - */ - shield.getAPIKeys = ca({ - method: 'GET', - urls: [{ - fmt: `/_security/api_key?owner=<%=owner%>`, - req: { - owner: { - type: 'boolean', - required: true - } - } - }] - }); - - /** - * Creates an API key in Elasticsearch for the current user. - * - * @param {string} name A name for this API key - * @param {object} role_descriptors Role descriptors for this API key, if not - * provided then permissions of authenticated user are applied. - * @param {string} [expiration] Optional expiration for the API key being generated. If expiration - * is not provided then the API keys do not expire. - * - * @returns {{id: string, name: string, api_key: string, expiration?: number}} - */ - shield.createAPIKey = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/api_key', - }, - }); - - /** - * Invalidates an API key in Elasticsearch. - * - * @param {string} [id] An API key id. - * @param {string} [name] An API key name. - * @param {string} [realm_name] The name of an authentication realm. - * @param {string} [username] The username of a user. - * - * NOTE: While all parameters are optional, at least one of them is required. - * - * @returns {{invalidated_api_keys: string[], previously_invalidated_api_keys: string[], error_count: number, error_details?: object[]}} - */ - shield.invalidateAPIKey = ca({ - method: 'DELETE', - needBody: true, - url: { - fmt: '/_security/api_key', - }, - }); - - /** - * Gets an access token in exchange to the certificate chain for the target subject distinguished name. - * - * @param {string[]} x509_certificate_chain An ordered array of base64-encoded (Section 4 of RFC4648 - not - * base64url-encoded) DER PKIX certificate values. - * - * @returns {{access_token: string, type: string, expires_in: number}} - */ - shield.delegatePKI = ca({ - method: 'POST', - needBody: true, - url: { - fmt: '/_security/delegate_pki', - }, - }); - }; -})); diff --git a/x-pack/legacy/server/lib/get_client_shield.ts b/x-pack/legacy/server/lib/get_client_shield.ts deleted file mode 100644 index 1f68c2e6d3466f..00000000000000 --- a/x-pack/legacy/server/lib/get_client_shield.ts +++ /dev/null @@ -1,14 +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 { Legacy } from 'kibana'; -// @ts-ignore -import esShield from './esjs_shield_plugin'; - -export const getClient = once((server: Legacy.Server) => { - return server.plugins.elasticsearch.createCluster('security', { plugins: [esShield] }); -}); diff --git a/x-pack/package.json b/x-pack/package.json index 8f8dfa658cfe92..e094c389300d02 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -92,7 +92,7 @@ "@types/react": "^16.9.11", "@types/react-dom": "^16.9.4", "@types/react-redux": "^6.0.6", - "@types/react-router-dom": "^4.3.1", + "@types/react-router-dom": "^5.1.3", "@types/react-sticky": "^6.0.3", "@types/react-test-renderer": "^16.9.1", "@types/recompose": "^0.30.6", @@ -304,7 +304,7 @@ "react-portal": "^3.2.0", "react-redux": "^5.1.2", "react-reverse-portal": "^1.0.4", - "react-router-dom": "^4.3.1", + "react-router-dom": "^5.1.2", "react-shortcuts": "^2.0.0", "react-sticky": "^6.0.3", "react-syntax-highlighter": "^5.7.0", diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts new file mode 100644 index 00000000000000..d3a69c01732fa6 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/create.test.ts @@ -0,0 +1,102 @@ +/* + * 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 sinon from 'sinon'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; +import { CUSTOM_ELEMENT_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { initializeCreateCustomElementRoute } from './create'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +const mockedUUID = '123abc'; +const now = new Date(); +const nowIso = now.toISOString(); + +jest.mock('uuid/v4', () => jest.fn().mockReturnValue('123abc')); + +describe('POST custom element', () => { + let routeHandler: RequestHandler; + let clock: sinon.SinonFakeTimers; + + beforeEach(() => { + clock = sinon.useFakeTimers(now); + + const httpService = httpServiceMock.createSetupContract(); + + const router = httpService.createRouter('') as jest.Mocked; + initializeCreateCustomElementRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.post.mock.calls[0][1]; + }); + + afterEach(() => { + clock.restore(); + }); + + it(`returns 200 when the custom element is created`, async () => { + const mockCustomElement = { + displayName: 'My Custom Element', + }; + + const request = httpServerMock.createKibanaRequest({ + method: 'post', + path: 'api/canvas/custom-element', + body: mockCustomElement, + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ ok: true }); + expect(mockRouteContext.core.savedObjects.client.create).toBeCalledWith( + CUSTOM_ELEMENT_TYPE, + { + ...mockCustomElement, + '@timestamp': nowIso, + '@created': nowIso, + }, + { + id: `custom-element-${mockedUUID}`, + } + ); + }); + + it(`returns bad request if create is unsuccessful`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'post', + path: 'api/canvas/custom-element', + body: {}, + }); + + (mockRouteContext.core.savedObjects.client.create as jest.Mock).mockImplementation(() => { + throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request'); + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(400); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/create.ts b/x-pack/plugins/canvas/server/routes/custom_elements/create.ts new file mode 100644 index 00000000000000..b8828291246966 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/create.ts @@ -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 { RouteInitializerDeps } from '../'; +import { + CUSTOM_ELEMENT_TYPE, + API_ROUTE_CUSTOM_ELEMENT, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { getId } from '../../../../../legacy/plugins/canvas/public/lib/get_id'; +import { CustomElementSchema } from './custom_element_schema'; +import { CustomElementAttributes } from './custom_element_attributes'; +import { okResponse } from '../ok_response'; +import { catchErrorHandler } from '../catch_error_handler'; + +export function initializeCreateCustomElementRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.post( + { + path: `${API_ROUTE_CUSTOM_ELEMENT}`, + validate: { + body: CustomElementSchema, + }, + options: { + body: { + maxBytes: 26214400, // 25MB payload limit + accepts: ['application/json'], + }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const customElement = request.body; + + const now = new Date().toISOString(); + const { id, ...payload } = customElement; + + await context.core.savedObjects.client.create( + CUSTOM_ELEMENT_TYPE, + { + ...payload, + '@timestamp': now, + '@created': now, + }, + { id: id || getId('custom-element') } + ); + + return response.ok({ + body: okResponse, + }); + }) + ); +} diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/custom_element_attributes.ts b/x-pack/plugins/canvas/server/routes/custom_elements/custom_element_attributes.ts new file mode 100644 index 00000000000000..e76526eeeb27b9 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/custom_element_attributes.ts @@ -0,0 +1,12 @@ +/* + * 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 { CustomElement } from '../../../../../legacy/plugins/canvas/types'; + +// Exclude ID attribute for the type used for SavedObjectClient +export type CustomElementAttributes = Pick> & { + '@timestamp': string; + '@created': string; +}; diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/custom_element_schema.ts b/x-pack/plugins/canvas/server/routes/custom_elements/custom_element_schema.ts new file mode 100644 index 00000000000000..956dccc5aaea21 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/custom_element_schema.ts @@ -0,0 +1,26 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const CustomElementSchema = schema.object({ + '@created': schema.maybe(schema.string()), + '@timestamp': schema.maybe(schema.string()), + content: schema.string(), + displayName: schema.string(), + help: schema.maybe(schema.string()), + id: schema.string(), + image: schema.maybe(schema.string()), + name: schema.string(), + tags: schema.maybe(schema.arrayOf(schema.string())), +}); + +export const CustomElementUpdateSchema = schema.object({ + displayName: schema.string(), + help: schema.maybe(schema.string()), + image: schema.maybe(schema.string()), + name: schema.string(), +}); diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts new file mode 100644 index 00000000000000..c108f2316db272 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/delete.test.ts @@ -0,0 +1,81 @@ +/* + * 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 { CUSTOM_ELEMENT_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { initializeDeleteCustomElementRoute } from './delete'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +describe('DELETE custom element', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeDeleteCustomElementRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.delete.mock.calls[0][1]; + }); + + it(`returns 200 ok when the custom element is deleted`, async () => { + const id = 'some-id'; + const request = httpServerMock.createKibanaRequest({ + method: 'delete', + path: `api/canvas/custom-element/${id}`, + params: { + id, + }, + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ ok: true }); + expect(mockRouteContext.core.savedObjects.client.delete).toBeCalledWith( + CUSTOM_ELEMENT_TYPE, + id + ); + }); + + it(`returns bad request if delete is unsuccessful`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'delete', + path: `api/canvas/custom-element/some-id`, + params: { + id: 'some-id', + }, + }); + + (mockRouteContext.core.savedObjects.client.delete as jest.Mock).mockImplementationOnce(() => { + throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request'); + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(400); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/delete.ts b/x-pack/plugins/canvas/server/routes/custom_elements/delete.ts new file mode 100644 index 00000000000000..5867539b95b532 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/delete.ts @@ -0,0 +1,32 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RouteInitializerDeps } from '../'; +import { + CUSTOM_ELEMENT_TYPE, + API_ROUTE_CUSTOM_ELEMENT, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { okResponse } from '../ok_response'; +import { catchErrorHandler } from '../catch_error_handler'; + +export function initializeDeleteCustomElementRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.delete( + { + path: `${API_ROUTE_CUSTOM_ELEMENT}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + catchErrorHandler(async (context, request, response) => { + await context.core.savedObjects.client.delete(CUSTOM_ELEMENT_TYPE, request.params.id); + return response.ok({ body: okResponse }); + }) + ); +} diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts new file mode 100644 index 00000000000000..6644d3b56c6815 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/find.test.ts @@ -0,0 +1,113 @@ +/* + * 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 { initializeFindCustomElementsRoute } from './find'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +describe('Find custom element', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeFindCustomElementsRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it(`returns 200 with the found custom elements`, async () => { + const name = 'something'; + const perPage = 10000; + const mockResults = { + total: 2, + saved_objects: [ + { id: 1, attributes: { key: 'value' } }, + { id: 2, attributes: { key: 'other-value' } }, + ], + }; + + const findMock = mockRouteContext.core.savedObjects.client.find as jest.Mock; + + findMock.mockResolvedValueOnce(mockResults); + + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: `api/canvas/custom-elements/find`, + query: { + name, + perPage, + }, + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + expect(response.status).toBe(200); + expect(findMock.mock.calls[0][0].search).toBe(`${name}* | ${name}`); + expect(findMock.mock.calls[0][0].perPage).toBe(perPage); + + expect(response.payload).toMatchInlineSnapshot(` + Object { + "customElements": Array [ + Object { + "id": 1, + "key": "value", + }, + Object { + "id": 2, + "key": "other-value", + }, + ], + "total": 2, + } + `); + }); + + it(`returns 200 with empty results on error`, async () => { + (mockRouteContext.core.savedObjects.client.find as jest.Mock).mockImplementationOnce(() => { + throw new Error('generic error'); + }); + + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: `api/canvas/custom-elements/find`, + query: { + name: 'something', + perPage: 1000, + }, + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toMatchInlineSnapshot(` + Object { + "customElements": Array [], + "total": 0, + } + `); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/find.ts b/x-pack/plugins/canvas/server/routes/custom_elements/find.ts new file mode 100644 index 00000000000000..5041ceb3e4711d --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/find.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 { schema } from '@kbn/config-schema'; +import { SavedObjectAttributes } from 'src/core/server'; +import { RouteInitializerDeps } from '../'; +import { + CUSTOM_ELEMENT_TYPE, + API_ROUTE_CUSTOM_ELEMENT, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; + +export function initializeFindCustomElementsRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.get( + { + path: `${API_ROUTE_CUSTOM_ELEMENT}/find`, + validate: { + query: schema.object({ + name: schema.string(), + page: schema.maybe(schema.number()), + perPage: schema.number(), + }), + }, + }, + async (context, request, response) => { + const savedObjectsClient = context.core.savedObjects.client; + const { name, page, perPage } = request.query; + + try { + const customElements = await savedObjectsClient.find({ + type: CUSTOM_ELEMENT_TYPE, + sortField: '@timestamp', + sortOrder: 'desc', + search: name ? `${name}* | ${name}` : '*', + searchFields: ['name'], + fields: [ + 'id', + 'name', + 'displayName', + 'help', + 'image', + 'content', + '@created', + '@timestamp', + ], + page, + perPage, + }); + + return response.ok({ + body: { + total: customElements.total, + customElements: customElements.saved_objects.map(hit => ({ + id: hit.id, + ...hit.attributes, + })), + }, + }); + } catch (error) { + return response.ok({ + body: { + total: 0, + customElements: [], + }, + }); + } + } + ); +} diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts new file mode 100644 index 00000000000000..5e8d536f779a9a --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/get.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { CUSTOM_ELEMENT_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { initializeGetCustomElementRoute } from './get'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +describe('GET custom element', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeGetCustomElementRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it(`returns 200 when the custom element is found`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: 'api/canvas/custom-element/123', + params: { + id: '123', + }, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockResolvedValueOnce({ + id: '123', + type: CUSTOM_ELEMENT_TYPE, + attributes: { foo: true }, + references: [], + }); + + mockRouteContext.core.savedObjects.client = savedObjectsClient; + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toMatchInlineSnapshot(` + Object { + "foo": true, + "id": "123", + } + `); + + expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "canvas-element", + "123", + ], + ] + `); + }); + + it('returns 404 if the custom element is not found', async () => { + const id = '123'; + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: 'api/canvas/custom-element/123', + params: { + id, + }, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockImplementation(() => { + throw savedObjectsClient.errors.createGenericNotFoundError(CUSTOM_ELEMENT_TYPE, id); + }); + mockRouteContext.core.savedObjects.client = savedObjectsClient; + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.payload).toMatchInlineSnapshot(` + Object { + "error": "Not Found", + "message": "Saved object [canvas-element/123] not found", + "statusCode": 404, + } + `); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/get.ts b/x-pack/plugins/canvas/server/routes/custom_elements/get.ts new file mode 100644 index 00000000000000..f092b001e141f7 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/get.ts @@ -0,0 +1,41 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RouteInitializerDeps } from '../'; +import { + CUSTOM_ELEMENT_TYPE, + API_ROUTE_CUSTOM_ELEMENT, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { CustomElementAttributes } from './custom_element_attributes'; +import { catchErrorHandler } from '../catch_error_handler'; + +export function initializeGetCustomElementRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.get( + { + path: `${API_ROUTE_CUSTOM_ELEMENT}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + catchErrorHandler(async (context, request, response) => { + const customElement = await context.core.savedObjects.client.get( + CUSTOM_ELEMENT_TYPE, + request.params.id + ); + + return response.ok({ + body: { + id: customElement.id, + ...customElement.attributes, + }, + }); + }) + ); +} diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/index.ts b/x-pack/plugins/canvas/server/routes/custom_elements/index.ts new file mode 100644 index 00000000000000..ade641e4913711 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/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 { RouteInitializerDeps } from '../'; +import { initializeFindCustomElementsRoute } from './find'; +import { initializeGetCustomElementRoute } from './get'; +import { initializeCreateCustomElementRoute } from './create'; +import { initializeUpdateCustomElementRoute } from './update'; +import { initializeDeleteCustomElementRoute } from './delete'; + +export function initCustomElementsRoutes(deps: RouteInitializerDeps) { + initializeFindCustomElementsRoute(deps); + initializeGetCustomElementRoute(deps); + initializeCreateCustomElementRoute(deps); + initializeUpdateCustomElementRoute(deps); + initializeDeleteCustomElementRoute(deps); +} diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts new file mode 100644 index 00000000000000..f21a9c25b6e64a --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts @@ -0,0 +1,164 @@ +/* + * 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 sinon from 'sinon'; +import { CustomElement } from '../../../../../legacy/plugins/canvas/types'; +import { CUSTOM_ELEMENT_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { initializeUpdateCustomElementRoute } from './update'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; +import { okResponse } from '../ok_response'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +const now = new Date(); +const nowIso = now.toISOString(); + +jest.mock('uuid/v4', () => jest.fn().mockReturnValue('123abc')); + +type CustomElementPayload = CustomElement & { + '@timestamp': string; + '@created': string; +}; + +const customElement: CustomElementPayload = { + id: 'my-custom-element', + name: 'MyCustomElement', + displayName: 'My Wonderful Custom Element', + content: 'This is content', + tags: ['filter', 'graphic'], + '@created': '2019-02-08T18:35:23.029Z', + '@timestamp': '2019-02-08T18:35:23.029Z', +}; + +describe('PUT custom element', () => { + let routeHandler: RequestHandler; + let clock: sinon.SinonFakeTimers; + + beforeEach(() => { + clock = sinon.useFakeTimers(now); + + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeUpdateCustomElementRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.put.mock.calls[0][1]; + }); + + afterEach(() => { + jest.resetAllMocks(); + clock.restore(); + }); + + it(`returns 200 ok when the custom element is updated`, async () => { + const updatedCustomElement = { name: 'new name' }; + const { id, ...customElementAttributes } = customElement; + + const request = httpServerMock.createKibanaRequest({ + method: 'put', + path: `api/canvas/custom-element/${id}`, + params: { + id, + }, + body: updatedCustomElement, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockResolvedValueOnce({ + id, + type: CUSTOM_ELEMENT_TYPE, + attributes: customElementAttributes as any, + references: [], + }); + + mockRouteContext.core.savedObjects.client = savedObjectsClient; + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toEqual(okResponse); + expect(mockRouteContext.core.savedObjects.client.create).toBeCalledWith( + CUSTOM_ELEMENT_TYPE, + { + ...customElementAttributes, + ...updatedCustomElement, + '@timestamp': nowIso, + '@created': customElement['@created'], + }, + { + overwrite: true, + id, + } + ); + }); + + it(`returns not found if existing custom element is not found`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'put', + path: 'api/canvas/custom-element/some-id', + params: { + id: 'not-found', + }, + body: {}, + }); + + (mockRouteContext.core.savedObjects.client.get as jest.Mock).mockImplementationOnce(() => { + throw mockRouteContext.core.savedObjects.client.errors.createGenericNotFoundError( + 'not found' + ); + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + expect(response.status).toBe(404); + }); + + it(`returns bad request if the write fails`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'put', + path: 'api/canvas/custom-element/some-id', + params: { + id: 'some-id', + }, + body: {}, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'some-id', + type: CUSTOM_ELEMENT_TYPE, + attributes: {}, + references: [], + }); + + mockRouteContext.core.savedObjects.client = savedObjectsClient; + (mockRouteContext.core.savedObjects.client.create as jest.Mock).mockImplementationOnce(() => { + throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request'); + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(400); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/update.ts b/x-pack/plugins/canvas/server/routes/custom_elements/update.ts new file mode 100644 index 00000000000000..51c363249dd793 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/custom_elements/update.ts @@ -0,0 +1,63 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { omit } from 'lodash'; +import { RouteInitializerDeps } from '../'; +import { + CUSTOM_ELEMENT_TYPE, + API_ROUTE_CUSTOM_ELEMENT, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { CustomElementUpdateSchema } from './custom_element_schema'; +import { CustomElementAttributes } from './custom_element_attributes'; +import { okResponse } from '../ok_response'; +import { catchErrorHandler } from '../catch_error_handler'; + +export function initializeUpdateCustomElementRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.put( + { + path: `${API_ROUTE_CUSTOM_ELEMENT}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + body: CustomElementUpdateSchema, + }, + options: { + body: { + maxBytes: 26214400, // 25MB payload limit + accepts: ['application/json'], + }, + }, + }, + catchErrorHandler(async (context, request, response) => { + const payload = request.body; + const id = request.params.id; + + const now = new Date().toISOString(); + + const customElementObject = await context.core.savedObjects.client.get< + CustomElementAttributes + >(CUSTOM_ELEMENT_TYPE, id); + + await context.core.savedObjects.client.create( + CUSTOM_ELEMENT_TYPE, + { + ...customElementObject.attributes, + ...omit(payload, 'id'), // never write the id property + '@timestamp': now, + '@created': customElementObject.attributes['@created'], // ensure created is not modified + }, + { overwrite: true, id } + ); + + return response.ok({ + body: okResponse, + }); + }) + ); +} diff --git a/x-pack/plugins/canvas/server/routes/index.ts b/x-pack/plugins/canvas/server/routes/index.ts index 46873a6b325423..8b2d77d6347609 100644 --- a/x-pack/plugins/canvas/server/routes/index.ts +++ b/x-pack/plugins/canvas/server/routes/index.ts @@ -6,6 +6,7 @@ import { IRouter, Logger } from 'src/core/server'; import { initWorkpadRoutes } from './workpad'; +import { initCustomElementsRoutes } from './custom_elements'; export interface RouteInitializerDeps { router: IRouter; @@ -14,4 +15,5 @@ export interface RouteInitializerDeps { export function initRoutes(deps: RouteInitializerDeps) { initWorkpadRoutes(deps); + initCustomElementsRoutes(deps); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/ok_response.ts b/x-pack/plugins/canvas/server/routes/ok_response.ts similarity index 100% rename from x-pack/plugins/canvas/server/routes/workpad/ok_response.ts rename to x-pack/plugins/canvas/server/routes/ok_response.ts diff --git a/x-pack/plugins/canvas/server/routes/workpad/create.ts b/x-pack/plugins/canvas/server/routes/workpad/create.ts index be904356720b68..fc847d4816dbd4 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/create.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/create.ts @@ -11,15 +11,11 @@ import { } from '../../../../../legacy/plugins/canvas/common/lib/constants'; import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types'; import { getId } from '../../../../../legacy/plugins/canvas/public/lib/get_id'; +import { WorkpadAttributes } from './workpad_attributes'; import { WorkpadSchema } from './workpad_schema'; -import { okResponse } from './ok_response'; +import { okResponse } from '../ok_response'; import { catchErrorHandler } from '../catch_error_handler'; -export type WorkpadAttributes = Pick> & { - '@timestamp': string; - '@created': string; -}; - export function initializeCreateWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; router.post( diff --git a/x-pack/plugins/canvas/server/routes/workpad/delete.ts b/x-pack/plugins/canvas/server/routes/workpad/delete.ts index 7adf11e7a887be..8de4ea0f9a27f3 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/delete.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/delete.ts @@ -10,7 +10,7 @@ import { CANVAS_TYPE, API_ROUTE_WORKPAD, } from '../../../../../legacy/plugins/canvas/common/lib/constants'; -import { okResponse } from './ok_response'; +import { okResponse } from '../ok_response'; import { catchErrorHandler } from '../catch_error_handler'; export function initializeDeleteWorkpadRoute(deps: RouteInitializerDeps) { diff --git a/x-pack/plugins/canvas/server/routes/workpad/get.ts b/x-pack/plugins/canvas/server/routes/workpad/get.ts index 7a51006aa9f024..d7a5e77670f6e5 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/get.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/get.ts @@ -10,14 +10,9 @@ import { CANVAS_TYPE, API_ROUTE_WORKPAD, } from '../../../../../legacy/plugins/canvas/common/lib/constants'; -import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types'; +import { WorkpadAttributes } from './workpad_attributes'; import { catchErrorHandler } from '../catch_error_handler'; -export type WorkpadAttributes = Pick> & { - '@timestamp': string; - '@created': string; -}; - export function initializeGetWorkpadRoute(deps: RouteInitializerDeps) { const { router } = deps; router.get( diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts index 492a6c98d71ee3..de098dd9717ed0 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts @@ -20,7 +20,7 @@ import { loggingServiceMock, } from 'src/core/server/mocks'; import { workpads } from '../../../../../legacy/plugins/canvas/__tests__/fixtures/workpads'; -import { okResponse } from './ok_response'; +import { okResponse } from '../ok_response'; const mockRouteContext = ({ core: { diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.ts b/x-pack/plugins/canvas/server/routes/workpad/update.ts index 460aa174038ae8..74dedb605472c9 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/update.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/update.ts @@ -15,16 +15,11 @@ import { API_ROUTE_WORKPAD_STRUCTURES, API_ROUTE_WORKPAD_ASSETS, } from '../../../../../legacy/plugins/canvas/common/lib/constants'; -import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types'; +import { WorkpadAttributes } from './workpad_attributes'; import { WorkpadSchema, WorkpadAssetSchema } from './workpad_schema'; -import { okResponse } from './ok_response'; +import { okResponse } from '../ok_response'; import { catchErrorHandler } from '../catch_error_handler'; -export type WorkpadAttributes = Pick> & { - '@timestamp': string; - '@created': string; -}; - const AssetsRecordSchema = schema.recordOf(schema.string(), WorkpadAssetSchema); const AssetPayloadSchema = schema.object({ diff --git a/x-pack/plugins/canvas/server/routes/workpad/workpad_attributes.ts b/x-pack/plugins/canvas/server/routes/workpad/workpad_attributes.ts new file mode 100644 index 00000000000000..2b7b6cca4ba2bc --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/workpad_attributes.ts @@ -0,0 +1,11 @@ +/* + * 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 { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types'; + +export type WorkpadAttributes = Pick> & { + '@timestamp': string; + '@created': string; +}; diff --git a/x-pack/legacy/plugins/security/common/model/api_key.ts b/x-pack/plugins/security/common/model/api_key.ts similarity index 100% rename from x-pack/legacy/plugins/security/common/model/api_key.ts rename to x-pack/plugins/security/common/model/api_key.ts diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index c6ccd2518d2610..226ea3b70afe2a 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export { ApiKey, ApiKeyToInvalidate } from './api_key'; export { User, EditUser, getUserDisplayName } from './user'; export { AuthenticatedUser, canUserChangePassword } from './authenticated_user'; export { BuiltinESPrivileges } from './builtin_es_privileges'; 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 c1d7dcca4c78ff..ad7eab76db088d 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -42,7 +42,7 @@ describe('OIDCAuthenticationProvider', () => { describe('`login` method', () => { it('redirects third party initiated login attempts to the OpenId Connect Provider.', async () => { - const request = httpServerMock.createKibanaRequest({ path: '/api/security/v1/oidc' }); + const request = httpServerMock.createKibanaRequest({ path: '/api/security/oidc' }); mockOptions.client.callAsInternalUser.withArgs('shield.oidcPrepare').resolves({ state: 'statevalue', @@ -205,13 +205,13 @@ describe('OIDCAuthenticationProvider', () => { describe('authorization code flow', () => { defineAuthenticationFlowTests(() => ({ request: httpServerMock.createKibanaRequest({ - path: '/api/security/v1/oidc?code=somecodehere&state=somestatehere', + path: '/api/security/oidc?code=somecodehere&state=somestatehere', }), attempt: { flow: OIDCAuthenticationFlow.AuthorizationCode, - authenticationResponseURI: '/api/security/v1/oidc?code=somecodehere&state=somestatehere', + authenticationResponseURI: '/api/security/oidc?code=somecodehere&state=somestatehere', }, - expectedRedirectURI: '/api/security/v1/oidc?code=somecodehere&state=somestatehere', + expectedRedirectURI: '/api/security/oidc?code=somecodehere&state=somestatehere', })); }); @@ -219,14 +219,13 @@ describe('OIDCAuthenticationProvider', () => { defineAuthenticationFlowTests(() => ({ request: httpServerMock.createKibanaRequest({ path: - '/api/security/v1/oidc?authenticationResponseURI=http://kibana/api/security/v1/oidc/implicit#id_token=sometoken', + '/api/security/oidc?authenticationResponseURI=http://kibana/api/security/oidc/implicit#id_token=sometoken', }), attempt: { flow: OIDCAuthenticationFlow.Implicit, - authenticationResponseURI: - 'http://kibana/api/security/v1/oidc/implicit#id_token=sometoken', + authenticationResponseURI: 'http://kibana/api/security/oidc/implicit#id_token=sometoken', }, - expectedRedirectURI: 'http://kibana/api/security/v1/oidc/implicit#id_token=sometoken', + expectedRedirectURI: 'http://kibana/api/security/oidc/implicit#id_token=sometoken', })); }); }); diff --git a/x-pack/plugins/security/server/elasticsearch_client_plugin.ts b/x-pack/plugins/security/server/elasticsearch_client_plugin.ts new file mode 100644 index 00000000000000..60d947bd658637 --- /dev/null +++ b/x-pack/plugins/security/server/elasticsearch_client_plugin.ts @@ -0,0 +1,576 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export function elasticsearchClientPlugin(Client: any, config: unknown, components: any) { + const ca = components.clientAction.factory; + + Client.prototype.shield = components.clientAction.namespaceFactory(); + const shield = Client.prototype.shield.prototype; + + /** + * Perform a [shield.authenticate](Retrieve details about the currently authenticated user) request + * + * @param {Object} params - An object with parameters used to carry out this action + */ + shield.authenticate = ca({ + params: {}, + url: { + fmt: '/_security/_authenticate', + }, + }); + + /** + * Perform a [shield.changePassword](Change the password of a user) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {Boolean} params.refresh - Refresh the index after performing the operation + * @param {String} params.username - The username of the user to change the password for + */ + shield.changePassword = ca({ + params: { + refresh: { + type: 'boolean', + }, + }, + urls: [ + { + fmt: '/_security/user/<%=username%>/_password', + req: { + username: { + type: 'string', + required: false, + }, + }, + }, + { + fmt: '/_security/user/_password', + }, + ], + needBody: true, + method: 'POST', + }); + + /** + * Perform a [shield.clearCachedRealms](Clears the internal user caches for specified realms) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {String} params.usernames - Comma-separated list of usernames to clear from the cache + * @param {String} params.realms - Comma-separated list of realms to clear + */ + shield.clearCachedRealms = ca({ + params: { + usernames: { + type: 'string', + required: false, + }, + }, + url: { + fmt: '/_security/realm/<%=realms%>/_clear_cache', + req: { + realms: { + type: 'string', + required: true, + }, + }, + }, + method: 'POST', + }); + + /** + * Perform a [shield.clearCachedRoles](Clears the internal caches for specified roles) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {String} params.name - Role name + */ + shield.clearCachedRoles = ca({ + params: {}, + url: { + fmt: '/_security/role/<%=name%>/_clear_cache', + req: { + name: { + type: 'string', + required: true, + }, + }, + }, + method: 'POST', + }); + + /** + * Perform a [shield.deleteRole](Remove a role from the native shield realm) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {Boolean} params.refresh - Refresh the index after performing the operation + * @param {String} params.name - Role name + */ + shield.deleteRole = ca({ + params: { + refresh: { + type: 'boolean', + }, + }, + url: { + fmt: '/_security/role/<%=name%>', + req: { + name: { + type: 'string', + required: true, + }, + }, + }, + method: 'DELETE', + }); + + /** + * Perform a [shield.deleteUser](Remove a user from the native shield realm) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {Boolean} params.refresh - Refresh the index after performing the operation + * @param {String} params.username - username + */ + shield.deleteUser = ca({ + params: { + refresh: { + type: 'boolean', + }, + }, + url: { + fmt: '/_security/user/<%=username%>', + req: { + username: { + type: 'string', + required: true, + }, + }, + }, + method: 'DELETE', + }); + + /** + * Perform a [shield.getRole](Retrieve one or more roles from the native shield realm) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {String} params.name - Role name + */ + shield.getRole = ca({ + params: {}, + urls: [ + { + fmt: '/_security/role/<%=name%>', + req: { + name: { + type: 'string', + required: false, + }, + }, + }, + { + fmt: '/_security/role', + }, + ], + }); + + /** + * Perform a [shield.getUser](Retrieve one or more users from the native shield realm) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {String, String[], Boolean} params.username - A comma-separated list of usernames + */ + shield.getUser = ca({ + params: {}, + urls: [ + { + fmt: '/_security/user/<%=username%>', + req: { + username: { + type: 'list', + required: false, + }, + }, + }, + { + fmt: '/_security/user', + }, + ], + }); + + /** + * Perform a [shield.putRole](Update or create a role for the native shield realm) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {Boolean} params.refresh - Refresh the index after performing the operation + * @param {String} params.name - Role name + */ + shield.putRole = ca({ + params: { + refresh: { + type: 'boolean', + }, + }, + url: { + fmt: '/_security/role/<%=name%>', + req: { + name: { + type: 'string', + required: true, + }, + }, + }, + needBody: true, + method: 'PUT', + }); + + /** + * Perform a [shield.putUser](Update or create a user for the native shield realm) request + * + * @param {Object} params - An object with parameters used to carry out this action + * @param {Boolean} params.refresh - Refresh the index after performing the operation + * @param {String} params.username - The username of the User + */ + shield.putUser = ca({ + params: { + refresh: { + type: 'boolean', + }, + }, + url: { + fmt: '/_security/user/<%=username%>', + req: { + username: { + type: 'string', + required: true, + }, + }, + }, + needBody: true, + method: 'PUT', + }); + + /** + * Perform a [shield.getUserPrivileges](Retrieve a user's list of privileges) request + * + */ + shield.getUserPrivileges = ca({ + params: {}, + urls: [ + { + fmt: '/_security/user/_privileges', + }, + ], + }); + + /** + * Asks Elasticsearch to prepare SAML authentication request to be sent to + * the 3rd-party SAML identity provider. + * + * @param {string} [acs] Optional assertion consumer service URL to use for SAML request or URL + * in the Kibana to which identity provider will post SAML response. Based on the ACS Elasticsearch + * will choose the right SAML realm. + * + * @param {string} [realm] Optional name of the Elasticsearch SAML realm to use to handle request. + * + * @returns {{realm: string, id: string, redirect: string}} Object that includes identifier + * of the SAML realm used to prepare authentication request, encrypted request token to be + * sent to Elasticsearch with SAML response and redirect URL to the identity provider that + * will be used to authenticate user. + */ + shield.samlPrepare = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/saml/prepare', + }, + }); + + /** + * Sends SAML response returned by identity provider to Elasticsearch for validation. + * + * @param {Array.} ids A list of encrypted request tokens returned within SAML + * preparation response. + * @param {string} content SAML response returned by identity provider. + * @param {string} [realm] Optional string used to identify the name of the OpenID Connect realm + * that should be used to authenticate request. + * + * @returns {{username: string, access_token: string, expires_in: number}} Object that + * includes name of the user, access token to use for any consequent requests that + * need to be authenticated and a number of seconds after which access token will expire. + */ + shield.samlAuthenticate = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/saml/authenticate', + }, + }); + + /** + * Invalidates SAML access token. + * + * @param {string} token SAML access token that needs to be invalidated. + * + * @returns {{redirect?: string}} + */ + shield.samlLogout = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/saml/logout', + }, + }); + + /** + * Invalidates SAML session based on Logout Request received from the Identity Provider. + * + * @param {string} queryString URL encoded query string provided by Identity Provider. + * @param {string} [acs] Optional assertion consumer service URL to use for SAML request or URL in the + * Kibana to which identity provider will post SAML response. Based on the ACS Elasticsearch + * will choose the right SAML realm to invalidate session. + * @param {string} [realm] Optional name of the Elasticsearch SAML realm to use to handle request. + * + * @returns {{redirect?: string}} + */ + shield.samlInvalidate = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/saml/invalidate', + }, + }); + + /** + * Asks Elasticsearch to prepare an OpenID Connect authentication request to be sent to + * the 3rd-party OpenID Connect provider. + * + * @param {string} realm The OpenID Connect realm name in Elasticsearch + * + * @returns {{state: string, nonce: string, redirect: string}} Object that includes two opaque parameters that need + * to be sent to Elasticsearch with the OpenID Connect response and redirect URL to the OpenID Connect provider that + * will be used to authenticate user. + */ + shield.oidcPrepare = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/oidc/prepare', + }, + }); + + /** + * Sends the URL to which the OpenID Connect Provider redirected the UA to Elasticsearch for validation. + * + * @param {string} state The state parameter that was returned by Elasticsearch in the + * preparation response. + * @param {string} nonce The nonce parameter that was returned by Elasticsearch in the + * preparation response. + * @param {string} redirect_uri The URL to where the UA was redirected by the OpenID Connect provider. + * @param {string} [realm] Optional string used to identify the name of the OpenID Connect realm + * that should be used to authenticate request. + * + * @returns {{username: string, access_token: string, refresh_token; string, expires_in: number}} Object that + * includes name of the user, access token to use for any consequent requests that + * need to be authenticated and a number of seconds after which access token will expire. + */ + shield.oidcAuthenticate = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/oidc/authenticate', + }, + }); + + /** + * Invalidates an access token and refresh token pair that was generated after an OpenID Connect authentication. + * + * @param {string} token An access token that was created by authenticating to an OpenID Connect realm and + * that needs to be invalidated. + * @param {string} refresh_token A refresh token that was created by authenticating to an OpenID Connect realm and + * that needs to be invalidated. + * + * @returns {{redirect?: string}} If the Elasticsearch OpenID Connect realm configuration and the + * OpenID Connect provider supports RP-initiated SLO, a URL to redirect the UA + */ + shield.oidcLogout = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/oidc/logout', + }, + }); + + /** + * Refreshes an access token. + * + * @param {string} grant_type Currently only "refresh_token" grant type is supported. + * @param {string} refresh_token One-time refresh token that will be exchanged to the new access/refresh token pair. + * + * @returns {{access_token: string, type: string, expires_in: number, refresh_token: string}} + */ + shield.getAccessToken = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/oauth2/token', + }, + }); + + /** + * Invalidates an access token. + * + * @param {string} token The access token to invalidate + * + * @returns {{created: boolean}} + */ + shield.deleteAccessToken = ca({ + method: 'DELETE', + needBody: true, + params: { + token: { + type: 'string', + }, + }, + url: { + fmt: '/_security/oauth2/token', + }, + }); + + shield.getPrivilege = ca({ + method: 'GET', + urls: [ + { + fmt: '/_security/privilege/<%=privilege%>', + req: { + privilege: { + type: 'string', + required: false, + }, + }, + }, + { + fmt: '/_security/privilege', + }, + ], + }); + + shield.deletePrivilege = ca({ + method: 'DELETE', + urls: [ + { + fmt: '/_security/privilege/<%=application%>/<%=privilege%>', + req: { + application: { + type: 'string', + required: true, + }, + privilege: { + type: 'string', + required: true, + }, + }, + }, + ], + }); + + shield.postPrivileges = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/privilege', + }, + }); + + shield.hasPrivileges = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/user/_has_privileges', + }, + }); + + shield.getBuiltinPrivileges = ca({ + params: {}, + urls: [ + { + fmt: '/_security/privilege/_builtin', + }, + ], + }); + + /** + * Gets API keys in Elasticsearch + * @param {boolean} owner A boolean flag that can be used to query API keys owned by the currently authenticated user. + * Defaults to false. The realm_name or username parameters cannot be specified when this parameter is set to true as + * they are assumed to be the currently authenticated ones. + */ + shield.getAPIKeys = ca({ + method: 'GET', + urls: [ + { + fmt: `/_security/api_key?owner=<%=owner%>`, + req: { + owner: { + type: 'boolean', + required: true, + }, + }, + }, + ], + }); + + /** + * Creates an API key in Elasticsearch for the current user. + * + * @param {string} name A name for this API key + * @param {object} role_descriptors Role descriptors for this API key, if not + * provided then permissions of authenticated user are applied. + * @param {string} [expiration] Optional expiration for the API key being generated. If expiration + * is not provided then the API keys do not expire. + * + * @returns {{id: string, name: string, api_key: string, expiration?: number}} + */ + shield.createAPIKey = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/api_key', + }, + }); + + /** + * Invalidates an API key in Elasticsearch. + * + * @param {string} [id] An API key id. + * @param {string} [name] An API key name. + * @param {string} [realm_name] The name of an authentication realm. + * @param {string} [username] The username of a user. + * + * NOTE: While all parameters are optional, at least one of them is required. + * + * @returns {{invalidated_api_keys: string[], previously_invalidated_api_keys: string[], error_count: number, error_details?: object[]}} + */ + shield.invalidateAPIKey = ca({ + method: 'DELETE', + needBody: true, + url: { + fmt: '/_security/api_key', + }, + }); + + /** + * Gets an access token in exchange to the certificate chain for the target subject distinguished name. + * + * @param {string[]} x509_certificate_chain An ordered array of base64-encoded (Section 4 of RFC4648 - not + * base64url-encoded) DER PKIX certificate values. + * + * @returns {{access_token: string, type: string, expires_in: number}} + */ + shield.delegatePKI = ca({ + method: 'POST', + needBody: true, + url: { + fmt: '/_security/delegate_pki', + }, + }); +} diff --git a/x-pack/plugins/security/server/errors.ts b/x-pack/plugins/security/server/errors.ts index e0c2918991696c..b5f3667558f557 100644 --- a/x-pack/plugins/security/server/errors.ts +++ b/x-pack/plugins/security/server/errors.ts @@ -5,11 +5,25 @@ */ import Boom from 'boom'; +import { CustomHttpResponseOptions, ResponseError } from '../../../../src/core/server'; export function wrapError(error: any) { return Boom.boomify(error, { statusCode: getErrorStatusCode(error) }); } +/** + * Wraps error into error suitable for Core's custom error response. + * @param error Any error instance. + */ +export function wrapIntoCustomErrorResponse(error: any) { + const wrappedError = wrapError(error); + return { + body: wrappedError, + headers: wrappedError.output.headers, + statusCode: wrappedError.output.statusCode, + } as CustomHttpResponseOptions; +} + /** * Extracts error code from Boom and Elasticsearch "native" errors. * @param error Error instance to extract status code from. diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index ec43bbd95901aa..e72e94e9cd94b0 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -9,18 +9,8 @@ import { ConfigSchema } from './config'; import { Plugin } from './plugin'; // These exports are part of public Security plugin contract, any change in signature of exported -// functions or removal of exports should be considered as a breaking change. Ideally we should -// reduce number of such exports to zero and provide everything we want to expose via Setup/Start -// run-time contracts. -export { wrapError } from './errors'; -export { - canRedirectRequest, - AuthenticationResult, - DeauthenticationResult, - OIDCAuthenticationFlow, - CreateAPIKeyResult, -} from './authentication'; - +// functions or removal of exports should be considered as a breaking change. +export { AuthenticationResult, DeauthenticationResult, CreateAPIKeyResult } from './authentication'; export { PluginSetupContract } from './plugin'; export const config = { schema: ConfigSchema }; diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 26788c3ef9230a..0569f5f4de3a67 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -7,6 +7,7 @@ import { of } from 'rxjs'; import { ByteSizeValue } from '@kbn/config-schema'; import { IClusterClient, CoreSetup } from '../../../../src/core/server'; +import { elasticsearchClientPlugin } from './elasticsearch_client_plugin'; import { Plugin, PluginSetupDependencies } from './plugin'; import { coreMock, elasticsearchServiceMock } from '../../../../src/core/server/mocks'; @@ -48,12 +49,6 @@ describe('Security Plugin', () => { Object { "__legacyCompat": Object { "config": Object { - "authc": Object { - "providers": Array [ - "saml", - "token", - ], - }, "cookieName": "sid", "loginAssistanceMessage": undefined, "secureCookies": true, @@ -115,7 +110,7 @@ describe('Security Plugin', () => { expect(mockCoreSetup.elasticsearch.createClient).toHaveBeenCalledTimes(1); expect(mockCoreSetup.elasticsearch.createClient).toHaveBeenCalledWith('security', { - plugins: [require('../../../legacy/server/lib/esjs_shield_plugin')], + plugins: [elasticsearchClientPlugin], }); }); }); diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 84d448331cef25..633b064da6d618 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -28,6 +28,7 @@ import { defineRoutes } from './routes'; import { SecurityLicenseService, SecurityLicense } from './licensing'; import { setupSavedObjects } from './saved_objects'; import { SecurityAuditLogger } from './audit'; +import { elasticsearchClientPlugin } from './elasticsearch_client_plugin'; export type SpacesService = Pick< SpacesPluginSetup['spacesService'], @@ -76,7 +77,8 @@ export interface PluginSetupContract { lifespan: number | null; }; secureCookies: boolean; - authc: { providers: string[] }; + cookieName: string; + loginAssistanceMessage: string; }>; }; } @@ -128,7 +130,7 @@ export class Plugin { .toPromise(); this.clusterClient = core.elasticsearch.createClient('security', { - plugins: [require('../../../legacy/server/lib/esjs_shield_plugin')], + plugins: [elasticsearchClientPlugin], }); const { license, update: updateLicense } = new SecurityLicenseService().setup(); @@ -213,7 +215,6 @@ export class Plugin { }, secureCookies: config.secureCookies, cookieName: config.cookieName, - authc: { providers: config.authc.providers }, }, }, }); diff --git a/x-pack/plugins/security/server/routes/api_keys/get.test.ts b/x-pack/plugins/security/server/routes/api_keys/get.test.ts new file mode 100644 index 00000000000000..2b2283edea2e83 --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/get.test.ts @@ -0,0 +1,160 @@ +/* + * 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 { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE, LicenseCheck } from '../../../../licensing/server'; +import { defineGetApiKeysRoutes } from './get'; + +import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { routeDefinitionParamsMock } from '../index.mock'; +import Boom from 'boom'; + +interface TestOptions { + isAdmin?: boolean; + licenseCheckResult?: LicenseCheck; + apiResponse?: () => Promise; + asserts: { statusCode: number; result?: Record }; +} + +describe('Get API keys', () => { + const getApiKeysTest = ( + description: string, + { + licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, + apiResponse, + asserts, + isAdmin = true, + }: TestOptions + ) => { + test(description, async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + if (apiResponse) { + mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + } + + defineGetApiKeysRoutes(mockRouteDefinitionParams); + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: '/internal/security/api_key', + query: { isAdmin: isAdmin.toString() }, + headers, + }); + const mockContext = ({ + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + if (apiResponse) { + expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( + 'shield.getAPIKeys', + { owner: !isAdmin } + ); + } else { + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + } + expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); + }); + }; + + describe('failure', () => { + getApiKeysTest('returns result of license checker', { + licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, + }); + + const error = Boom.notAcceptable('test not acceptable message'); + getApiKeysTest('returns error from cluster client', { + apiResponse: async () => { + throw error; + }, + asserts: { statusCode: 406, result: error }, + }); + }); + + describe('success', () => { + getApiKeysTest('returns API keys', { + apiResponse: async () => ({ + api_keys: [ + { + id: 'YCLV7m0BJ3xI4hhWB648', + name: 'test-api-key', + creation: 1571670001452, + expiration: 1571756401452, + invalidated: false, + username: 'elastic', + realm: 'reserved', + }, + ], + }), + asserts: { + statusCode: 200, + result: { + apiKeys: [ + { + id: 'YCLV7m0BJ3xI4hhWB648', + name: 'test-api-key', + creation: 1571670001452, + expiration: 1571756401452, + invalidated: false, + username: 'elastic', + realm: 'reserved', + }, + ], + }, + }, + }); + getApiKeysTest('returns only valid API keys', { + apiResponse: async () => ({ + api_keys: [ + { + id: 'YCLV7m0BJ3xI4hhWB648', + name: 'test-api-key1', + creation: 1571670001452, + expiration: 1571756401452, + invalidated: true, + username: 'elastic', + realm: 'reserved', + }, + { + id: 'YCLV7m0BJ3xI4hhWB648', + name: 'test-api-key2', + creation: 1571670001452, + expiration: 1571756401452, + invalidated: false, + username: 'elastic', + realm: 'reserved', + }, + ], + }), + asserts: { + statusCode: 200, + result: { + apiKeys: [ + { + id: 'YCLV7m0BJ3xI4hhWB648', + name: 'test-api-key2', + creation: 1571670001452, + expiration: 1571756401452, + invalidated: false, + username: 'elastic', + realm: 'reserved', + }, + ], + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/api_keys/get.ts b/x-pack/plugins/security/server/routes/api_keys/get.ts new file mode 100644 index 00000000000000..6e98b4b098405b --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/get.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 { schema } from '@kbn/config-schema'; +import { ApiKey } from '../../../common/model'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { RouteDefinitionParams } from '..'; + +export function defineGetApiKeysRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.get( + { + path: '/internal/security/api_key', + validate: { + query: schema.object({ + // We don't use `schema.boolean` here, because all query string parameters are treated as + // strings and @kbn/config-schema doesn't coerce strings to booleans. + // + // A boolean flag that can be used to query API keys owned by the currently authenticated + // user. `false` means that only API keys of currently authenticated user will be returned. + isAdmin: schema.oneOf([schema.literal('true'), schema.literal('false')]), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const isAdmin = request.query.isAdmin === 'true'; + const { api_keys: apiKeys } = (await clusterClient + .asScoped(request) + .callAsCurrentUser('shield.getAPIKeys', { owner: !isAdmin })) as { api_keys: ApiKey[] }; + + const validKeys = apiKeys.filter(({ invalidated }) => !invalidated); + + return response.ok({ body: { apiKeys: validKeys } }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/api_keys/index.ts b/x-pack/plugins/security/server/routes/api_keys/index.ts new file mode 100644 index 00000000000000..d75eb1bcbe9614 --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/index.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 { defineGetApiKeysRoutes } from './get'; +import { defineCheckPrivilegesRoutes } from './privileges'; +import { defineInvalidateApiKeysRoutes } from './invalidate'; +import { RouteDefinitionParams } from '..'; + +export function defineApiKeysRoutes(params: RouteDefinitionParams) { + defineGetApiKeysRoutes(params); + defineCheckPrivilegesRoutes(params); + defineInvalidateApiKeysRoutes(params); +} diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts new file mode 100644 index 00000000000000..4ea21bda5f743b --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts @@ -0,0 +1,220 @@ +/* + * 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 { Type } from '@kbn/config-schema'; +import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE, LicenseCheck } from '../../../../licensing/server'; +import { defineInvalidateApiKeysRoutes } from './invalidate'; + +import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { routeDefinitionParamsMock } from '../index.mock'; + +interface TestOptions { + licenseCheckResult?: LicenseCheck; + apiResponses?: Array<() => Promise>; + payload?: Record; + asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] }; +} + +describe('Invalidate API keys', () => { + const postInvalidateTest = ( + description: string, + { + licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, + apiResponses = [], + asserts, + payload, + }: TestOptions + ) => { + test(description, async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + for (const apiResponse of apiResponses) { + mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + } + + defineInvalidateApiKeysRoutes(mockRouteDefinitionParams); + const [[{ validate }, handler]] = mockRouteDefinitionParams.router.post.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'post', + path: '/internal/security/api_key/invalidate', + body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, + headers, + }); + const mockContext = ({ + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + if (Array.isArray(asserts.apiArguments)) { + for (const apiArguments of asserts.apiArguments) { + expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith( + mockRequest + ); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); + } + } else { + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + } + expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); + }); + }; + + describe('request validation', () => { + let requestBodySchema: Type; + beforeEach(() => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + defineInvalidateApiKeysRoutes(mockRouteDefinitionParams); + + const [[{ validate }]] = mockRouteDefinitionParams.router.post.mock.calls; + requestBodySchema = (validate as any).body; + }); + + test('requires both isAdmin and apiKeys parameters', () => { + expect(() => + requestBodySchema.validate({}, {}, 'request body') + ).toThrowErrorMatchingInlineSnapshot( + `"[request body.apiKeys]: expected value of type [array] but got [undefined]"` + ); + + expect(() => + requestBodySchema.validate({ apiKeys: [] }, {}, 'request body') + ).toThrowErrorMatchingInlineSnapshot( + `"[request body.isAdmin]: expected value of type [boolean] but got [undefined]"` + ); + + expect(() => + requestBodySchema.validate({ apiKeys: {}, isAdmin: true }, {}, 'request body') + ).toThrowErrorMatchingInlineSnapshot( + `"[request body.apiKeys]: expected value of type [array] but got [Object]"` + ); + + expect(() => + requestBodySchema.validate( + { + apiKeys: [{ id: 'some-id', name: 'some-name', unknown: 'some-unknown' }], + isAdmin: true, + }, + {}, + 'request body' + ) + ).toThrowErrorMatchingInlineSnapshot( + `"[request body.apiKeys.0.unknown]: definition for this key is missing"` + ); + }); + }); + + describe('failure', () => { + postInvalidateTest('returns result of license checker', { + licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + payload: { apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], isAdmin: true }, + asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, + }); + + const error = Boom.notAcceptable('test not acceptable message'); + postInvalidateTest('returns error from cluster client', { + apiResponses: [ + async () => { + throw error; + }, + ], + payload: { + apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], + isAdmin: true, + }, + asserts: { + apiArguments: [['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }]], + statusCode: 200, + result: { + itemsInvalidated: [], + errors: [ + { + id: 'si8If24B1bKsmSLTAhJV', + name: 'my-api-key', + error: Boom.notAcceptable('test not acceptable message'), + }, + ], + }, + }, + }); + }); + + describe('success', () => { + postInvalidateTest('invalidates API keys', { + apiResponses: [async () => null], + payload: { + apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], + isAdmin: true, + }, + asserts: { + apiArguments: [['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }]], + statusCode: 200, + result: { + itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], + errors: [], + }, + }, + }); + + postInvalidateTest('adds "owner" to body if isAdmin=false', { + apiResponses: [async () => null], + payload: { + apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], + isAdmin: false, + }, + asserts: { + apiArguments: [ + ['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV', owner: true } }], + ], + statusCode: 200, + result: { + itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], + errors: [], + }, + }, + }); + + postInvalidateTest('returns only successful invalidation requests', { + apiResponses: [ + async () => null, + async () => { + throw Boom.notAcceptable('test not acceptable message'); + }, + ], + payload: { + apiKeys: [ + { id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key1' }, + { id: 'ab8If24B1bKsmSLTAhNC', name: 'my-api-key2' }, + ], + isAdmin: true, + }, + asserts: { + apiArguments: [ + ['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }], + ['shield.invalidateAPIKey', { body: { id: 'ab8If24B1bKsmSLTAhNC' } }], + ], + statusCode: 200, + result: { + itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key1' }], + errors: [ + { + id: 'ab8If24B1bKsmSLTAhNC', + name: 'my-api-key2', + error: Boom.notAcceptable('test not acceptable message'), + }, + ], + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.ts new file mode 100644 index 00000000000000..cb86c1024ae9a0 --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.ts @@ -0,0 +1,69 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { ApiKey } from '../../../common/model'; +import { wrapError, wrapIntoCustomErrorResponse } from '../../errors'; +import { RouteDefinitionParams } from '..'; + +interface ResponseType { + itemsInvalidated: Array>; + errors: Array & { error: Error }>; +} + +export function defineInvalidateApiKeysRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/api_key/invalidate', + validate: { + body: schema.object({ + apiKeys: schema.arrayOf(schema.object({ id: schema.string(), name: schema.string() })), + isAdmin: schema.boolean(), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const scopedClusterClient = clusterClient.asScoped(request); + + // Invalidate all API keys in parallel. + const invalidationResult = ( + await Promise.all( + request.body.apiKeys.map(async key => { + try { + const body: { id: string; owner?: boolean } = { id: key.id }; + if (!request.body.isAdmin) { + body.owner = true; + } + + // Send the request to invalidate the API key and return an error if it could not be deleted. + await scopedClusterClient.callAsCurrentUser('shield.invalidateAPIKey', { body }); + return { key, error: undefined }; + } catch (error) { + return { key, error: wrapError(error) }; + } + }) + ) + ).reduce( + (responseBody, { key, error }) => { + if (error) { + responseBody.errors.push({ ...key, error }); + } else { + responseBody.itemsInvalidated.push(key); + } + return responseBody; + }, + { itemsInvalidated: [], errors: [] } as ResponseType + ); + + return response.ok({ body: invalidationResult }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts new file mode 100644 index 00000000000000..866e455063bdc0 --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts @@ -0,0 +1,187 @@ +/* + * 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 { LICENSE_CHECK_STATE, LicenseCheck } from '../../../../licensing/server'; +import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; + +import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { routeDefinitionParamsMock } from '../index.mock'; +import { defineCheckPrivilegesRoutes } from './privileges'; + +interface TestOptions { + licenseCheckResult?: LicenseCheck; + apiResponses?: Array<() => Promise>; + asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] }; +} + +describe('Check API keys privileges', () => { + const getPrivilegesTest = ( + description: string, + { + licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, + apiResponses = [], + asserts, + }: TestOptions + ) => { + test(description, async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + for (const apiResponse of apiResponses) { + mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + } + + defineCheckPrivilegesRoutes(mockRouteDefinitionParams); + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: '/internal/security/api_key/privileges', + headers, + }); + const mockContext = ({ + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + if (Array.isArray(asserts.apiArguments)) { + for (const apiArguments of asserts.apiArguments) { + expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith( + mockRequest + ); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); + } + } else { + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + } + expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); + }); + }; + + describe('failure', () => { + getPrivilegesTest('returns result of license checker', { + licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, + }); + + const error = Boom.notAcceptable('test not acceptable message'); + getPrivilegesTest('returns error from cluster client', { + apiResponses: [ + async () => { + throw error; + }, + async () => {}, + ], + asserts: { + apiArguments: [ + ['shield.hasPrivileges', { body: { cluster: ['manage_security', 'manage_api_key'] } }], + ['shield.getAPIKeys', { owner: true }], + ], + statusCode: 406, + result: error, + }, + }); + }); + + describe('success', () => { + getPrivilegesTest('returns areApiKeysEnabled and isAdmin', { + apiResponses: [ + async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: true, manage_security: true }, + index: {}, + application: {}, + }), + async () => ({ + api_keys: [ + { + id: 'si8If24B1bKsmSLTAhJV', + name: 'my-api-key', + creation: 1574089261632, + expiration: 1574175661632, + invalidated: false, + username: 'elastic', + realm: 'reserved', + }, + ], + }), + ], + asserts: { + apiArguments: [ + ['shield.getAPIKeys', { owner: true }], + ['shield.hasPrivileges', { body: { cluster: ['manage_security', 'manage_api_key'] } }], + ], + statusCode: 200, + result: { areApiKeysEnabled: true, isAdmin: true }, + }, + }); + + getPrivilegesTest( + 'returns areApiKeysEnabled=false when getAPIKeys error message includes "api keys are not enabled"', + { + apiResponses: [ + async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: true, manage_security: true }, + index: {}, + application: {}, + }), + async () => { + throw Boom.unauthorized('api keys are not enabled'); + }, + ], + asserts: { + apiArguments: [ + ['shield.getAPIKeys', { owner: true }], + ['shield.hasPrivileges', { body: { cluster: ['manage_security', 'manage_api_key'] } }], + ], + statusCode: 200, + result: { areApiKeysEnabled: false, isAdmin: true }, + }, + } + ); + + getPrivilegesTest('returns isAdmin=false when user has insufficient privileges', { + apiResponses: [ + async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: false, manage_security: false }, + index: {}, + application: {}, + }), + async () => ({ + api_keys: [ + { + id: 'si8If24B1bKsmSLTAhJV', + name: 'my-api-key', + creation: 1574089261632, + expiration: 1574175661632, + invalidated: false, + username: 'elastic', + realm: 'reserved', + }, + ], + }), + ], + asserts: { + apiArguments: [ + ['shield.getAPIKeys', { owner: true }], + ['shield.hasPrivileges', { body: { cluster: ['manage_security', 'manage_api_key'] } }], + ], + statusCode: 200, + result: { areApiKeysEnabled: true, isAdmin: false }, + }, + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/api_keys/privileges.ts b/x-pack/plugins/security/server/routes/api_keys/privileges.ts new file mode 100644 index 00000000000000..216d1ef1bf4a44 --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/privileges.ts @@ -0,0 +1,49 @@ +/* + * 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 { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { RouteDefinitionParams } from '..'; + +export function defineCheckPrivilegesRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.get( + { + path: '/internal/security/api_key/privileges', + validate: false, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const scopedClusterClient = clusterClient.asScoped(request); + + const [ + { + cluster: { manage_security: manageSecurity, manage_api_key: manageApiKey }, + }, + { areApiKeysEnabled }, + ] = await Promise.all([ + scopedClusterClient.callAsCurrentUser('shield.hasPrivileges', { + body: { cluster: ['manage_security', 'manage_api_key'] }, + }), + scopedClusterClient.callAsCurrentUser('shield.getAPIKeys', { owner: true }).then( + // If the API returns a truthy result that means it's enabled. + result => ({ areApiKeysEnabled: !!result }), + // This is a brittle dependency upon message. Tracked by https://github.com/elastic/elasticsearch/issues/47759. + e => + e.message.includes('api keys are not enabled') + ? Promise.resolve({ areApiKeysEnabled: false }) + : Promise.reject(e) + ), + ]); + + return response.ok({ + body: { areApiKeysEnabled, isAdmin: manageSecurity || manageApiKey }, + }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/authentication/basic.test.ts b/x-pack/plugins/security/server/routes/authentication/basic.test.ts new file mode 100644 index 00000000000000..8e24f99b1302d4 --- /dev/null +++ b/x-pack/plugins/security/server/routes/authentication/basic.test.ts @@ -0,0 +1,172 @@ +/* + * 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 { Type } from '@kbn/config-schema'; +import { + IRouter, + kibanaResponseFactory, + RequestHandler, + RequestHandlerContext, + RouteConfig, +} from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; +import { Authentication, AuthenticationResult } from '../../authentication'; +import { ConfigType } from '../../config'; +import { LegacyAPI } from '../../plugin'; +import { defineBasicRoutes } from './basic'; + +import { + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, + loggingServiceMock, +} from '../../../../../../src/core/server/mocks'; +import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { authenticationMock } from '../../authentication/index.mock'; +import { authorizationMock } from '../../authorization/index.mock'; + +describe('Basic authentication routes', () => { + let router: jest.Mocked; + let authc: jest.Mocked; + let mockContext: RequestHandlerContext; + beforeEach(() => { + router = httpServiceMock.createRouter(); + authc = authenticationMock.create(); + + mockContext = ({ + licensing: { + license: { check: jest.fn().mockReturnValue({ check: LICENSE_CHECK_STATE.Valid }) }, + }, + } as unknown) as RequestHandlerContext; + + defineBasicRoutes({ + router, + clusterClient: elasticsearchServiceMock.createClusterClient(), + basePath: httpServiceMock.createBasePath(), + logger: loggingServiceMock.create().get(), + config: { authc: { providers: ['saml'] } } as ConfigType, + authc, + authz: authorizationMock.create(), + getLegacyAPI: () => ({ cspRules: 'test-csp-rule' } as LegacyAPI), + }); + }); + + describe('login', () => { + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { username: 'user', password: 'password' }, + }); + + beforeEach(() => { + const [loginRouteConfig, loginRouteHandler] = router.post.mock.calls.find( + ([{ path }]) => path === '/internal/security/login' + )!; + + routeConfig = loginRouteConfig; + routeHandler = loginRouteHandler; + }); + + it('correctly defines route.', async () => { + expect(routeConfig.options).toEqual({ authRequired: false }); + expect(routeConfig.validate).toEqual({ + body: expect.any(Type), + query: undefined, + params: undefined, + }); + + const bodyValidator = (routeConfig.validate as any).body as Type; + expect(bodyValidator.validate({ username: 'user', password: 'password' })).toEqual({ + username: 'user', + password: 'password', + }); + + expect(() => bodyValidator.validate({})).toThrowErrorMatchingInlineSnapshot( + `"[username]: expected value of type [string] but got [undefined]"` + ); + expect(() => bodyValidator.validate({ username: 'user' })).toThrowErrorMatchingInlineSnapshot( + `"[password]: expected value of type [string] but got [undefined]"` + ); + expect(() => + bodyValidator.validate({ password: 'password' }) + ).toThrowErrorMatchingInlineSnapshot( + `"[username]: expected value of type [string] but got [undefined]"` + ); + expect(() => + bodyValidator.validate({ username: '', password: '' }) + ).toThrowErrorMatchingInlineSnapshot( + `"[username]: value is [] but it must have a minimum length of [1]."` + ); + expect(() => + bodyValidator.validate({ username: 'user', password: '' }) + ).toThrowErrorMatchingInlineSnapshot( + `"[password]: value is [] but it must have a minimum length of [1]."` + ); + expect(() => + bodyValidator.validate({ username: '', password: 'password' }) + ).toThrowErrorMatchingInlineSnapshot( + `"[username]: value is [] but it must have a minimum length of [1]."` + ); + }); + + it('returns 500 if authentication throws unhandled exception.', async () => { + const unhandledException = new Error('Something went wrong.'); + authc.login.mockRejectedValue(unhandledException); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(500); + expect(response.payload).toEqual(unhandledException); + expect(authc.login).toHaveBeenCalledWith(mockRequest, { + provider: 'basic', + value: { username: 'user', password: 'password' }, + }); + }); + + it('returns 401 if authentication fails.', async () => { + const failureReason = new Error('Something went wrong.'); + authc.login.mockResolvedValue(AuthenticationResult.failed(failureReason)); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(401); + expect(response.payload).toEqual(failureReason); + expect(authc.login).toHaveBeenCalledWith(mockRequest, { + provider: 'basic', + value: { username: 'user', password: 'password' }, + }); + }); + + it('returns 401 if authentication is not handled.', async () => { + authc.login.mockResolvedValue(AuthenticationResult.notHandled()); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(401); + expect(response.payload).toEqual('Unauthorized'); + expect(authc.login).toHaveBeenCalledWith(mockRequest, { + provider: 'basic', + value: { username: 'user', password: 'password' }, + }); + }); + + describe('authentication succeeds', () => { + it(`returns user data`, async () => { + authc.login.mockResolvedValue(AuthenticationResult.succeeded(mockAuthenticatedUser())); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(204); + expect(response.payload).toBeUndefined(); + expect(authc.login).toHaveBeenCalledWith(mockRequest, { + provider: 'basic', + 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 new file mode 100644 index 00000000000000..453dc1c4ea3b55 --- /dev/null +++ b/x-pack/plugins/security/server/routes/authentication/basic.ts @@ -0,0 +1,48 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { RouteDefinitionParams } from '..'; + +/** + * Defines routes required for Basic/Token authentication. + */ +export function defineBasicRoutes({ router, authc, config }: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/login', + validate: { + body: schema.object({ + username: schema.string({ minLength: 1 }), + password: schema.string({ minLength: 1 }), + }), + }, + options: { authRequired: false }, + }, + createLicensedRouteHandler(async (context, request, response) => { + const { username, password } = 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 }, + }); + + if (!authenticationResult.succeeded()) { + return response.unauthorized({ body: authenticationResult.error }); + } + + return response.noContent(); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/authentication/common.test.ts b/x-pack/plugins/security/server/routes/authentication/common.test.ts new file mode 100644 index 00000000000000..f57fb1d5a7d668 --- /dev/null +++ b/x-pack/plugins/security/server/routes/authentication/common.test.ts @@ -0,0 +1,202 @@ +/* + * 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 { Type } from '@kbn/config-schema'; +import { + IRouter, + kibanaResponseFactory, + RequestHandler, + RequestHandlerContext, + RouteConfig, +} from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; +import { Authentication, DeauthenticationResult } from '../../authentication'; +import { ConfigType } from '../../config'; +import { LegacyAPI } from '../../plugin'; +import { defineCommonRoutes } from './common'; + +import { + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, + loggingServiceMock, +} from '../../../../../../src/core/server/mocks'; +import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { authenticationMock } from '../../authentication/index.mock'; +import { authorizationMock } from '../../authorization/index.mock'; + +describe('Common authentication routes', () => { + let router: jest.Mocked; + let authc: jest.Mocked; + let mockContext: RequestHandlerContext; + beforeEach(() => { + router = httpServiceMock.createRouter(); + authc = authenticationMock.create(); + + mockContext = ({ + licensing: { + license: { check: jest.fn().mockReturnValue({ check: LICENSE_CHECK_STATE.Valid }) }, + }, + } as unknown) as RequestHandlerContext; + + defineCommonRoutes({ + router, + clusterClient: elasticsearchServiceMock.createClusterClient(), + basePath: httpServiceMock.createBasePath(), + logger: loggingServiceMock.create().get(), + config: { authc: { providers: ['saml'] } } as ConfigType, + authc, + authz: authorizationMock.create(), + getLegacyAPI: () => ({ cspRules: 'test-csp-rule' } as LegacyAPI), + }); + }); + + describe('logout', () => { + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { username: 'user', password: 'password' }, + }); + + beforeEach(() => { + const [loginRouteConfig, loginRouteHandler] = router.get.mock.calls.find( + ([{ path }]) => path === '/api/security/logout' + )!; + + routeConfig = loginRouteConfig; + routeHandler = loginRouteHandler; + }); + + it('correctly defines route.', async () => { + expect(routeConfig.options).toEqual({ authRequired: false }); + expect(routeConfig.validate).toEqual({ + body: undefined, + query: expect.any(Type), + params: undefined, + }); + + const queryValidator = (routeConfig.validate as any).query as Type; + expect(queryValidator.validate({ someRandomField: 'some-random' })).toEqual({ + someRandomField: 'some-random', + }); + expect(queryValidator.validate({})).toEqual({}); + expect(queryValidator.validate(undefined)).toEqual({}); + }); + + it('returns 500 if deauthentication throws unhandled exception.', async () => { + const unhandledException = new Error('Something went wrong.'); + authc.logout.mockRejectedValue(unhandledException); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(500); + expect(response.payload).toEqual(unhandledException); + expect(authc.logout).toHaveBeenCalledWith(mockRequest); + }); + + it('returns 500 if authenticator fails to logout.', async () => { + const failureReason = new Error('Something went wrong.'); + authc.logout.mockResolvedValue(DeauthenticationResult.failed(failureReason)); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(500); + expect(response.payload).toEqual(failureReason); + expect(authc.logout).toHaveBeenCalledWith(mockRequest); + }); + + it('returns 400 for AJAX requests that can not handle redirect.', async () => { + const mockAjaxRequest = httpServerMock.createKibanaRequest({ + headers: { 'kbn-xsrf': 'xsrf' }, + }); + + const response = await routeHandler(mockContext, mockAjaxRequest, kibanaResponseFactory); + + expect(response.status).toBe(400); + expect(response.payload).toEqual('Client should be able to process redirect response.'); + expect(authc.logout).not.toHaveBeenCalled(); + }); + + it('redirects user to the URL returned by authenticator.', async () => { + authc.logout.mockResolvedValue(DeauthenticationResult.redirectTo('https://custom.logout')); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(302); + expect(response.payload).toBeUndefined(); + expect(response.options).toEqual({ headers: { location: 'https://custom.logout' } }); + expect(authc.logout).toHaveBeenCalledWith(mockRequest); + }); + + it('redirects user to the base path if deauthentication succeeds.', async () => { + authc.logout.mockResolvedValue(DeauthenticationResult.succeeded()); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(302); + expect(response.payload).toBeUndefined(); + expect(response.options).toEqual({ headers: { location: '/mock-server-basepath/' } }); + expect(authc.logout).toHaveBeenCalledWith(mockRequest); + }); + + it('redirects user to the base path if deauthentication is not handled.', async () => { + authc.logout.mockResolvedValue(DeauthenticationResult.notHandled()); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(302); + expect(response.payload).toBeUndefined(); + expect(response.options).toEqual({ headers: { location: '/mock-server-basepath/' } }); + expect(authc.logout).toHaveBeenCalledWith(mockRequest); + }); + }); + + describe('me', () => { + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + + const mockRequest = httpServerMock.createKibanaRequest({ + body: { username: 'user', password: 'password' }, + }); + + beforeEach(() => { + const [loginRouteConfig, loginRouteHandler] = router.get.mock.calls.find( + ([{ path }]) => path === '/internal/security/me' + )!; + + routeConfig = loginRouteConfig; + routeHandler = loginRouteHandler; + }); + + it('correctly defines route.', async () => { + expect(routeConfig.options).toBeUndefined(); + expect(routeConfig.validate).toBe(false); + }); + + it('returns 500 if cannot retrieve current user due to unhandled exception.', async () => { + const unhandledException = new Error('Something went wrong.'); + authc.getCurrentUser.mockRejectedValue(unhandledException); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(500); + expect(response.payload).toEqual(unhandledException); + expect(authc.getCurrentUser).toHaveBeenCalledWith(mockRequest); + }); + + it('returns current user.', async () => { + const mockUser = mockAuthenticatedUser(); + authc.getCurrentUser.mockResolvedValue(mockUser); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toEqual(mockUser); + expect(authc.getCurrentUser).toHaveBeenCalledWith(mockRequest); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/authentication/common.ts b/x-pack/plugins/security/server/routes/authentication/common.ts new file mode 100644 index 00000000000000..cb4ec196459eee --- /dev/null +++ b/x-pack/plugins/security/server/routes/authentication/common.ts @@ -0,0 +1,78 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { canRedirectRequest } from '../../authentication'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { RouteDefinitionParams } from '..'; + +/** + * Defines routes that are common to various authentication mechanisms. + */ +export function defineCommonRoutes({ router, authc, basePath, logger }: RouteDefinitionParams) { + // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. + for (const path of ['/api/security/logout', '/api/security/v1/logout']) { + router.get( + { + path, + // Allow unknown query parameters as this endpoint can be hit by the 3rd-party with any + // set of query string parameters (e.g. SAML/OIDC logout request parameters). + validate: { query: schema.object({}, { allowUnknowns: true }) }, + options: { authRequired: false }, + }, + async (context, request, response) => { + const serverBasePath = basePath.serverBasePath; + if (path === '/api/security/v1/logout') { + logger.warn( + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/logout" URL instead.`, + { tags: ['deprecation'] } + ); + } + + if (!canRedirectRequest(request)) { + return response.badRequest({ + body: 'Client should be able to process redirect response.', + }); + } + + try { + const deauthenticationResult = await authc.logout(request); + if (deauthenticationResult.failed()) { + return response.customError(wrapIntoCustomErrorResponse(deauthenticationResult.error)); + } + + return response.redirected({ + headers: { location: deauthenticationResult.redirectURL || `${serverBasePath}/` }, + }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + } + ); + } + + // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. + for (const path of ['/internal/security/me', '/api/security/v1/me']) { + router.get( + { path, validate: false }, + createLicensedRouteHandler(async (context, request, response) => { + if (path === '/api/security/v1/me') { + logger.warn( + `The "${basePath.serverBasePath}${path}" endpoint is deprecated and will be removed in the next major version.`, + { tags: ['deprecation'] } + ); + } + + try { + return response.ok({ body: (await authc.getCurrentUser(request)) as any }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); + } +} diff --git a/x-pack/plugins/security/server/routes/authentication/index.ts b/x-pack/plugins/security/server/routes/authentication/index.ts index 086647dcb34597..21f015cc23b68c 100644 --- a/x-pack/plugins/security/server/routes/authentication/index.ts +++ b/x-pack/plugins/security/server/routes/authentication/index.ts @@ -6,11 +6,39 @@ import { defineSessionRoutes } from './session'; import { defineSAMLRoutes } from './saml'; +import { defineBasicRoutes } from './basic'; +import { defineCommonRoutes } from './common'; +import { defineOIDCRoutes } from './oidc'; import { RouteDefinitionParams } from '..'; +export function createCustomResourceResponse(body: string, contentType: string, cspRules: string) { + return { + body, + headers: { + 'content-type': contentType, + 'cache-control': 'private, no-cache, no-store', + 'content-security-policy': cspRules, + }, + statusCode: 200, + }; +} + export function defineAuthenticationRoutes(params: RouteDefinitionParams) { defineSessionRoutes(params); + defineCommonRoutes(params); + + if ( + params.config.authc.providers.includes('basic') || + params.config.authc.providers.includes('token') + ) { + defineBasicRoutes(params); + } + if (params.config.authc.providers.includes('saml')) { defineSAMLRoutes(params); } + + if (params.config.authc.providers.includes('oidc')) { + defineOIDCRoutes(params); + } } diff --git a/x-pack/plugins/security/server/routes/authentication/oidc.ts b/x-pack/plugins/security/server/routes/authentication/oidc.ts new file mode 100644 index 00000000000000..8483630763ae6b --- /dev/null +++ b/x-pack/plugins/security/server/routes/authentication/oidc.ts @@ -0,0 +1,274 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; +import { KibanaRequest, KibanaResponseFactory } from '../../../../../../src/core/server'; +import { OIDCAuthenticationFlow } from '../../authentication'; +import { createCustomResourceResponse } from '.'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { ProviderLoginAttempt } from '../../authentication/providers/oidc'; +import { RouteDefinitionParams } from '..'; + +/** + * Defines routes required for SAML authentication. + */ +export function defineOIDCRoutes({ + router, + logger, + authc, + getLegacyAPI, + basePath, +}: RouteDefinitionParams) { + // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. + for (const path of ['/api/security/oidc/implicit', '/api/security/v1/oidc/implicit']) { + /** + * The route should be configured as a redirect URI in OP when OpenID Connect implicit flow + * is used, so that we can extract authentication response from URL fragment and send it to + * the `/api/security/oidc` route. + */ + router.get( + { + path, + validate: false, + options: { authRequired: false }, + }, + (context, request, response) => { + const serverBasePath = basePath.serverBasePath; + if (path === '/api/security/v1/oidc/implicit') { + logger.warn( + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/implicit" URL instead.`, + { tags: ['deprecation'] } + ); + } + return response.custom( + createCustomResourceResponse( + ` + + Kibana OpenID Connect Login + + + `, + 'text/html', + getLegacyAPI().cspRules + ) + ); + } + ); + } + + /** + * The route that accompanies `/api/security/oidc/implicit` and renders a JavaScript snippet + * that extracts fragment part from the URL and send it to the `/api/security/oidc` route. + * We need this separate endpoint because of default CSP policy that forbids inline scripts. + */ + router.get( + { + path: '/internal/security/oidc/implicit.js', + validate: false, + options: { authRequired: false }, + }, + (context, request, response) => { + const serverBasePath = basePath.serverBasePath; + return response.custom( + createCustomResourceResponse( + ` + window.location.replace( + '${serverBasePath}/api/security/oidc?authenticationResponseURI=' + encodeURIComponent(window.location.href) + ); + `, + 'text/javascript', + getLegacyAPI().cspRules + ) + ); + } + ); + + // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. + for (const path of ['/api/security/oidc', '/api/security/v1/oidc']) { + router.get( + { + path, + validate: { + query: schema.object( + { + authenticationResponseURI: schema.maybe(schema.uri()), + code: schema.maybe(schema.string()), + error: schema.maybe(schema.string()), + error_description: schema.maybe(schema.string()), + error_uri: schema.maybe(schema.uri()), + iss: schema.maybe(schema.uri({ scheme: ['https'] })), + login_hint: schema.maybe(schema.string()), + target_link_uri: schema.maybe(schema.uri()), + state: schema.maybe(schema.string()), + }, + // The client MUST ignore unrecognized response parameters according to + // https://openid.net/specs/openid-connect-core-1_0.html#AuthResponseValidation and + // https://tools.ietf.org/html/rfc6749#section-4.1.2. + { allowUnknowns: true } + ), + }, + options: { authRequired: false }, + }, + createLicensedRouteHandler(async (context, request, response) => { + const serverBasePath = basePath.serverBasePath; + + // An HTTP GET request with a query parameter named `authenticationResponseURI` that includes URL fragment OpenID + // Connect Provider sent during implicit authentication flow to the Kibana own proxy page that extracted that URL + // fragment and put it into `authenticationResponseURI` query string parameter for this endpoint. See more details + // at https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth + let loginAttempt: ProviderLoginAttempt | undefined; + if (request.query.authenticationResponseURI) { + loginAttempt = { + flow: OIDCAuthenticationFlow.Implicit, + authenticationResponseURI: request.query.authenticationResponseURI, + }; + } else if (request.query.code || request.query.error) { + if (path === '/api/security/v1/oidc') { + logger.warn( + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc" URL instead.`, + { tags: ['deprecation'] } + ); + } + + // An HTTP GET request with a query parameter named `code` (or `error`) as the response to a successful (or + // failed) authentication from an OpenID Connect Provider during authorization code authentication flow. + // See more details at https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth. + loginAttempt = { + flow: OIDCAuthenticationFlow.AuthorizationCode, + // We pass the path only as we can't be sure of the full URL and Elasticsearch doesn't need it anyway. + authenticationResponseURI: request.url.path!, + }; + } else if (request.query.iss) { + logger.warn( + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`, + { tags: ['deprecation'] } + ); + // An HTTP GET request with a query parameter named `iss` as part of a 3rd party initiated authentication. + // See more details at https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin + loginAttempt = { + flow: OIDCAuthenticationFlow.InitiatedBy3rdParty, + iss: request.query.iss, + loginHint: request.query.login_hint, + }; + } + + if (!loginAttempt) { + return response.badRequest({ body: 'Unrecognized login attempt.' }); + } + + return performOIDCLogin(request, response, loginAttempt); + }) + ); + } + + // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. + for (const path of ['/api/security/oidc/initiate_login', '/api/security/v1/oidc']) { + /** + * An HTTP POST request with the payload parameter named `iss` as part of a 3rd party initiated authentication. + * See more details at https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin + */ + router.post( + { + path, + validate: { + body: schema.object( + { + iss: schema.uri({ scheme: ['https'] }), + login_hint: schema.maybe(schema.string()), + target_link_uri: schema.maybe(schema.uri()), + }, + // Other parameters MAY be sent, if defined by extensions. Any parameters used that are not understood MUST + // be ignored by the Client according to https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin. + { allowUnknowns: true } + ), + }, + options: { authRequired: false }, + }, + createLicensedRouteHandler(async (context, request, response) => { + const serverBasePath = basePath.serverBasePath; + if (path === '/api/security/v1/oidc') { + logger.warn( + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`, + { tags: ['deprecation'] } + ); + } + + return performOIDCLogin(request, response, { + flow: OIDCAuthenticationFlow.InitiatedBy3rdParty, + iss: request.body.iss, + loginHint: request.body.login_hint, + }); + }) + ); + } + + /** + * An HTTP GET request with the query string parameter named `iss` as part of a 3rd party initiated authentication. + * See more details at https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin + */ + router.get( + { + path: '/api/security/oidc/initiate_login', + validate: { + query: schema.object( + { + iss: schema.uri({ scheme: ['https'] }), + login_hint: schema.maybe(schema.string()), + target_link_uri: schema.maybe(schema.uri()), + }, + // Other parameters MAY be sent, if defined by extensions. Any parameters used that are not understood MUST + // be ignored by the Client according to https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin. + { allowUnknowns: true } + ), + }, + options: { authRequired: false }, + }, + createLicensedRouteHandler(async (context, request, response) => { + return performOIDCLogin(request, response, { + flow: OIDCAuthenticationFlow.InitiatedBy3rdParty, + iss: request.query.iss, + loginHint: request.query.login_hint, + }); + }) + ); + + async function performOIDCLogin( + request: KibanaRequest, + response: KibanaResponseFactory, + loginAttempt: ProviderLoginAttempt + ) { + try { + // We handle the fact that the user might get redirected to Kibana while already having a session + // Return an error notifying the user they are already logged in. + const authenticationResult = await authc.login(request, { + provider: 'oidc', + value: loginAttempt, + }); + + if (authenticationResult.succeeded()) { + return response.forbidden({ + body: i18n.translate('xpack.security.conflictingSessionError', { + defaultMessage: + 'Sorry, you already have an active Kibana session. ' + + 'If you want to start a new one, please logout from the existing session first.', + }), + }); + } + + if (authenticationResult.redirected()) { + return response.redirected({ + headers: { location: authenticationResult.redirectURL! }, + }); + } + + return response.unauthorized({ body: authenticationResult.error }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + } +} diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index 61f40e583d24ef..f724d0e7708be4 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -6,6 +6,7 @@ import { schema } from '@kbn/config-schema'; import { SAMLLoginStep } from '../../authentication'; +import { createCustomResourceResponse } from '.'; import { RouteDefinitionParams } from '..'; /** @@ -18,18 +19,6 @@ export function defineSAMLRoutes({ getLegacyAPI, basePath, }: RouteDefinitionParams) { - function createCustomResourceResponse(body: string, contentType: string) { - return { - body, - headers: { - 'content-type': contentType, - 'cache-control': 'private, no-cache, no-store', - 'content-security-policy': getLegacyAPI().cspRules, - }, - statusCode: 200, - }; - } - router.get( { path: '/api/security/saml/capture-url-fragment', @@ -46,7 +35,8 @@ export function defineSAMLRoutes({ `, - 'text/html' + 'text/html', + getLegacyAPI().cspRules ) ); } @@ -66,7 +56,8 @@ export function defineSAMLRoutes({ '${basePath.serverBasePath}/api/security/saml/start?redirectURLFragment=' + encodeURIComponent(window.location.hash) ); `, - 'text/javascript' + 'text/javascript', + getLegacyAPI().cspRules ) ); } diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts index 10fe0cdd678118..6afbad8e83ebe7 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts @@ -81,7 +81,7 @@ describe('GET privileges', () => { }; describe('failure', () => { - getPrivilegesTest(`returns result of routePreCheckLicense`, { + getPrivilegesTest('returns result of license checker', { licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts index 61c5747550d75a..22268245c3a447 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts @@ -73,16 +73,18 @@ describe('DELETE role', () => { }; describe('failure', () => { - deleteRoleTest(`returns result of license checker`, { + deleteRoleTest('returns result of license checker', { name: 'foo-role', licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); const error = Boom.notFound('test not found message'); - deleteRoleTest(`returns error from cluster client`, { + deleteRoleTest('returns error from cluster client', { name: 'foo-role', - apiResponse: () => Promise.reject(error), + apiResponse: async () => { + throw error; + }, asserts: { statusCode: 404, result: error }, }); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts index aab815fbe449ff..de966d6f2a7586 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDefinitionParams } from '../../index'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import { wrapError } from '../../../errors'; +import { wrapIntoCustomErrorResponse } from '../../../errors'; export function defineDeleteRolesRoutes({ router, clusterClient }: RouteDefinitionParams) { router.delete( @@ -23,11 +23,7 @@ export function defineDeleteRolesRoutes({ router, clusterClient }: RouteDefiniti return response.noContent(); } catch (error) { - const wrappedError = wrapError(error); - return response.customError({ - body: wrappedError, - statusCode: wrappedError.output.statusCode, - }); + return response.customError(wrapIntoCustomErrorResponse(error)); } }) ); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index 447d890605cc9f..bb9edbd17b2c8d 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -75,15 +75,17 @@ describe('GET role', () => { }; describe('failure', () => { - getRoleTest(`returns result of license check`, { + getRoleTest('returns result of license checker', { licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); const error = Boom.notAcceptable('test not acceptable message'); - getRoleTest(`returns error from cluster client`, { + getRoleTest('returns error from cluster client', { name: 'first_role', - apiResponse: () => Promise.reject(error), + apiResponse: async () => { + throw error; + }, asserts: { statusCode: 406, result: error }, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index 1173d37cba64f4..8c158bee1a15e5 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDefinitionParams } from '../..'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import { wrapError } from '../../../errors'; +import { wrapIntoCustomErrorResponse } from '../../../errors'; import { transformElasticsearchRoleToRole } from './model'; export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefinitionParams) { @@ -35,11 +35,7 @@ export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefi return response.notFound(); } catch (error) { - const wrappedError = wrapError(error); - return response.customError({ - body: wrappedError, - statusCode: wrappedError.output.statusCode, - }); + return response.customError(wrapIntoCustomErrorResponse(error)); } }) ); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 3bd85122c95d12..96f065d6c765ae 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -67,14 +67,16 @@ describe('GET all roles', () => { }; describe('failure', () => { - getRolesTest(`returns result of license check`, { + getRolesTest('returns result of license checker', { licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); const error = Boom.notAcceptable('test not acceptable message'); - getRolesTest(`returns error from cluster client`, { - apiResponse: () => Promise.reject(error), + getRolesTest('returns error from cluster client', { + apiResponse: async () => { + throw error; + }, asserts: { statusCode: 406, result: error }, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index 6ff431f0f8b6a7..24be6c60e4b129 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -6,7 +6,7 @@ import { RouteDefinitionParams } from '../..'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import { wrapError } from '../../../errors'; +import { wrapIntoCustomErrorResponse } from '../../../errors'; import { ElasticsearchRole, transformElasticsearchRoleToRole } from './model'; export function defineGetAllRolesRoutes({ router, authz, clusterClient }: RouteDefinitionParams) { @@ -37,11 +37,7 @@ export function defineGetAllRolesRoutes({ router, authz, clusterClient }: RouteD }), }); } catch (error) { - const wrappedError = wrapError(error); - return response.customError({ - body: wrappedError, - statusCode: wrappedError.output.statusCode, - }); + return response.customError(wrapIntoCustomErrorResponse(error)); } }) ); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index cb80549df8417c..d19debe6924607 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -138,7 +138,7 @@ describe('PUT role', () => { }); describe('failure', () => { - putRoleTest(`returns result of license checker`, { + putRoleTest('returns result of license checker', { name: 'foo-role', licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index e0245e72604460..5db83375afa965 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDefinitionParams } from '../../index'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -import { wrapError } from '../../../errors'; +import { wrapIntoCustomErrorResponse } from '../../../errors'; import { ElasticsearchRole, getPutPayloadSchema, @@ -52,11 +52,7 @@ export function definePutRolesRoutes({ router, authz, clusterClient }: RouteDefi return response.noContent(); } catch (error) { - const wrappedError = wrapError(error); - return response.customError({ - body: wrappedError, - statusCode: wrappedError.output.statusCode, - }); + return response.customError(wrapIntoCustomErrorResponse(error)); } }) ); diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 73e276832f4741..756eaa76e2c2e6 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -12,6 +12,9 @@ import { LegacyAPI } from '../plugin'; import { defineAuthenticationRoutes } from './authentication'; import { defineAuthorizationRoutes } from './authorization'; +import { defineApiKeysRoutes } from './api_keys'; +import { defineIndicesRoutes } from './indices'; +import { defineUsersRoutes } from './users'; /** * Describes parameters used to define HTTP routes. @@ -30,4 +33,7 @@ export interface RouteDefinitionParams { export function defineRoutes(params: RouteDefinitionParams) { defineAuthenticationRoutes(params); defineAuthorizationRoutes(params); + defineApiKeysRoutes(params); + defineIndicesRoutes(params); + defineUsersRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.ts b/x-pack/plugins/security/server/routes/indices/get_fields.ts new file mode 100644 index 00000000000000..64c3d4f7471ef0 --- /dev/null +++ b/x-pack/plugins/security/server/routes/indices/get_fields.ts @@ -0,0 +1,47 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RouteDefinitionParams } from '../index'; +import { wrapIntoCustomErrorResponse } from '../../errors'; + +export function defineGetFieldsRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.get( + { + path: '/internal/security/fields/{query}', + validate: { params: schema.object({ query: schema.string() }) }, + }, + async (context, request, response) => { + try { + const indexMappings = (await clusterClient + .asScoped(request) + .callAsCurrentUser('indices.getFieldMapping', { + index: request.params.query, + fields: '*', + allowNoIndices: false, + includeDefaults: true, + })) as Record }>; + + // The flow is the following (see response format at https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html): + // 1. Iterate over all matched indices. + // 2. Extract all the field names from the `mappings` field of the particular index. + // 3. Collect and flatten the list of the field names. + // 4. Use `Set` to get only unique field names. + return response.ok({ + body: Array.from( + new Set( + Object.values(indexMappings) + .map(indexMapping => Object.keys(indexMapping.mappings)) + .flat() + ) + ), + }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + } + ); +} diff --git a/x-pack/legacy/plugins/observability/public/components/example_shared_component.tsx b/x-pack/plugins/security/server/routes/indices/index.ts similarity index 51% rename from x-pack/legacy/plugins/observability/public/components/example_shared_component.tsx rename to x-pack/plugins/security/server/routes/indices/index.ts index e7cac9e3d70151..d6b5eccf0fadad 100644 --- a/x-pack/legacy/plugins/observability/public/components/example_shared_component.tsx +++ b/x-pack/plugins/security/server/routes/indices/index.ts @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import { defineGetFieldsRoutes } from './get_fields'; +import { RouteDefinitionParams } from '..'; -interface Props { - message?: string; -} - -export function ExampleSharedComponent({ message = 'See how it loads.' }: Props) { - return

This is an example of an observability shared component. {message}

; +export function defineIndicesRoutes(params: RouteDefinitionParams) { + defineGetFieldsRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts new file mode 100644 index 00000000000000..9f88d28bc115f1 --- /dev/null +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -0,0 +1,207 @@ +/* + * 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 { ObjectType } from '@kbn/config-schema'; +import { + IClusterClient, + IRouter, + IScopedClusterClient, + kibanaResponseFactory, + RequestHandler, + RequestHandlerContext, + RouteConfig, +} from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; +import { Authentication, AuthenticationResult } from '../../authentication'; +import { ConfigType } from '../../config'; +import { LegacyAPI } from '../../plugin'; +import { defineChangeUserPasswordRoutes } from './change_password'; + +import { + elasticsearchServiceMock, + loggingServiceMock, + httpServiceMock, + httpServerMock, +} from '../../../../../../src/core/server/mocks'; +import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; +import { authorizationMock } from '../../authorization/index.mock'; +import { authenticationMock } from '../../authentication/index.mock'; + +describe('Change password', () => { + let router: jest.Mocked; + let authc: jest.Mocked; + let mockClusterClient: jest.Mocked; + let mockScopedClusterClient: jest.Mocked; + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + let mockContext: RequestHandlerContext; + + function checkPasswordChangeAPICall( + username: string, + request: ReturnType + ) { + expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); + expect(mockClusterClient.asScoped).toHaveBeenCalledWith(request); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( + 'shield.changePassword', + { username, body: { password: 'new-password' } } + ); + } + + beforeEach(() => { + router = httpServiceMock.createRouter(); + authc = authenticationMock.create(); + + authc.getCurrentUser.mockResolvedValue(mockAuthenticatedUser({ username: 'user' })); + authc.login.mockResolvedValue(AuthenticationResult.succeeded(mockAuthenticatedUser())); + + mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockClusterClient = elasticsearchServiceMock.createClusterClient(); + mockClusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + + mockContext = ({ + licensing: { + license: { check: jest.fn().mockReturnValue({ check: LICENSE_CHECK_STATE.Valid }) }, + }, + } as unknown) as RequestHandlerContext; + + defineChangeUserPasswordRoutes({ + router, + clusterClient: mockClusterClient, + basePath: httpServiceMock.createBasePath(), + logger: loggingServiceMock.create().get(), + config: { authc: { providers: ['saml'] } } as ConfigType, + authc, + authz: authorizationMock.create(), + getLegacyAPI: () => ({ cspRules: 'test-csp-rule' } as LegacyAPI), + }); + + const [changePasswordRouteConfig, changePasswordRouteHandler] = router.post.mock.calls[0]; + routeConfig = changePasswordRouteConfig; + routeHandler = changePasswordRouteHandler; + }); + + it('correctly defines route.', async () => { + expect(routeConfig.path).toBe('/internal/security/users/{username}/password'); + + const paramsSchema = (routeConfig.validate as any).params as ObjectType; + expect(() => paramsSchema.validate({})).toThrowErrorMatchingInlineSnapshot( + `"[username]: expected value of type [string] but got [undefined]"` + ); + expect(() => paramsSchema.validate({ username: '' })).toThrowErrorMatchingInlineSnapshot( + `"[username]: value is [] but it must have a minimum length of [1]."` + ); + expect(() => + paramsSchema.validate({ username: 'a'.repeat(1025) }) + ).toThrowErrorMatchingInlineSnapshot( + `"[username]: value is [aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa] but it must have a maximum length of [1024]."` + ); + + const bodySchema = (routeConfig.validate as any).body as ObjectType; + expect(() => bodySchema.validate({})).toThrowErrorMatchingInlineSnapshot( + `"[newPassword]: expected value of type [string] but got [undefined]"` + ); + expect(() => bodySchema.validate({ newPassword: '' })).toThrowErrorMatchingInlineSnapshot( + `"[newPassword]: value is [] but it must have a minimum length of [1]."` + ); + expect(() => + bodySchema.validate({ newPassword: '123456', password: '' }) + ).toThrowErrorMatchingInlineSnapshot( + `"[password]: value is [] but it must have a minimum length of [1]."` + ); + }); + + describe('own password', () => { + const username = 'user'; + const mockRequest = httpServerMock.createKibanaRequest({ + params: { username }, + body: { password: 'old-password', newPassword: 'new-password' }, + }); + + it('returns 403 if old password is wrong.', async () => { + const loginFailureReason = new Error('Something went wrong.'); + authc.login.mockResolvedValue(AuthenticationResult.failed(loginFailureReason)); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(403); + expect(response.payload).toEqual(loginFailureReason); + expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + }); + + it(`returns 401 if user can't authenticate with new password.`, async () => { + const loginFailureReason = new Error('Something went wrong.'); + authc.login.mockImplementation(async (request, attempt) => { + const credentials = attempt.value as { username: string; password: string }; + if (credentials.username === 'user' && credentials.password === 'new-password') { + return AuthenticationResult.failed(loginFailureReason); + } + + return AuthenticationResult.succeeded(mockAuthenticatedUser()); + }); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(401); + expect(response.payload).toEqual(loginFailureReason); + + checkPasswordChangeAPICall(username, mockRequest); + }); + + it('returns 500 if password update request fails.', async () => { + const failureReason = new Error('Request failed.'); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(500); + expect(response.payload).toEqual(failureReason); + + checkPasswordChangeAPICall(username, mockRequest); + }); + + it('successfully changes own password if provided old password is correct.', async () => { + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(204); + expect(response.payload).toBeUndefined(); + + checkPasswordChangeAPICall(username, mockRequest); + }); + }); + + describe('other user password', () => { + const username = 'target-user'; + const mockRequest = httpServerMock.createKibanaRequest({ + params: { username }, + body: { newPassword: 'new-password' }, + }); + + it('returns 500 if password update request fails.', async () => { + const failureReason = new Error('Request failed.'); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(500); + expect(response.payload).toEqual(failureReason); + expect(authc.login).not.toHaveBeenCalled(); + + checkPasswordChangeAPICall(username, mockRequest); + }); + + it('successfully changes user password.', async () => { + const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); + + expect(response.status).toBe(204); + expect(response.payload).toBeUndefined(); + expect(authc.login).not.toHaveBeenCalled(); + + checkPasswordChangeAPICall(username, mockRequest); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/users/change_password.ts b/x-pack/plugins/security/server/routes/users/change_password.ts new file mode 100644 index 00000000000000..b9d04b4bd1e0e5 --- /dev/null +++ b/x-pack/plugins/security/server/routes/users/change_password.ts @@ -0,0 +1,80 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { RouteDefinitionParams } from '..'; + +export function defineChangeUserPasswordRoutes({ + authc, + router, + clusterClient, + config, +}: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/users/{username}/password', + validate: { + params: schema.object({ username: schema.string({ minLength: 1, maxLength: 1024 }) }), + body: schema.object({ + password: schema.maybe(schema.string({ minLength: 1 })), + newPassword: schema.string({ minLength: 1 }), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + const username = request.params.username; + const { password, newPassword } = request.body; + const isCurrentUser = username === (await authc.getCurrentUser(request))!.username; + + // We should prefer `token` over `basic` if possible. + const providerToLoginWith = config.authc.providers.includes('token') ? 'token' : 'basic'; + + // If user tries to change own password, let's check if old password is valid first by trying + // to login. + if (isCurrentUser) { + try { + const authenticationResult = await authc.login(request, { + provider: providerToLoginWith, + value: { username, password }, + // We shouldn't alter authentication state just yet. + stateless: true, + }); + + if (!authenticationResult.succeeded()) { + return response.forbidden({ body: authenticationResult.error }); + } + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + } + + try { + await clusterClient.asScoped(request).callAsCurrentUser('shield.changePassword', { + username, + body: { password: newPassword }, + }); + + // Now we authenticate user with the new password again updating current session if any. + if (isCurrentUser) { + const authenticationResult = await authc.login(request, { + provider: providerToLoginWith, + value: { username, password: newPassword }, + }); + + if (!authenticationResult.succeeded()) { + return response.unauthorized({ body: authenticationResult.error }); + } + } + + return response.noContent(); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/users/create_or_update.ts b/x-pack/plugins/security/server/routes/users/create_or_update.ts new file mode 100644 index 00000000000000..5a3e50bb11d5c2 --- /dev/null +++ b/x-pack/plugins/security/server/routes/users/create_or_update.ts @@ -0,0 +1,47 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { RouteDefinitionParams } from '..'; + +export function defineCreateOrUpdateUserRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/users/{username}', + validate: { + params: schema.object({ username: schema.string({ minLength: 1, maxLength: 1024 }) }), + body: schema.object({ + username: schema.string({ minLength: 1, maxLength: 1024 }), + password: schema.maybe(schema.string({ minLength: 1 })), + roles: schema.arrayOf(schema.string({ minLength: 1 })), + full_name: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])), + email: schema.maybe(schema.oneOf([schema.literal(null), schema.string()])), + metadata: schema.maybe(schema.recordOf(schema.string(), schema.any())), + enabled: schema.boolean({ defaultValue: true }), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + await clusterClient.asScoped(request).callAsCurrentUser('shield.putUser', { + username: request.params.username, + // Omit `username`, `enabled` and all fields with `null` value. + body: Object.fromEntries( + Object.entries(request.body).filter( + ([key, value]) => value !== null && key !== 'enabled' && key !== 'username' + ) + ), + }); + + return response.ok({ body: request.body }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/users/delete.ts b/x-pack/plugins/security/server/routes/users/delete.ts new file mode 100644 index 00000000000000..99a8d5c18ab3dd --- /dev/null +++ b/x-pack/plugins/security/server/routes/users/delete.ts @@ -0,0 +1,32 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { RouteDefinitionParams } from '../index'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; + +export function defineDeleteUserRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.delete( + { + path: '/internal/security/users/{username}', + validate: { + params: schema.object({ username: schema.string({ minLength: 1, maxLength: 1024 }) }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + await clusterClient + .asScoped(request) + .callAsCurrentUser('shield.deleteUser', { username: request.params.username }); + + return response.noContent(); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/users/get.ts b/x-pack/plugins/security/server/routes/users/get.ts new file mode 100644 index 00000000000000..08679103725465 --- /dev/null +++ b/x-pack/plugins/security/server/routes/users/get.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 { schema } from '@kbn/config-schema'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { RouteDefinitionParams } from '..'; + +export function defineGetUserRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.get( + { + path: '/internal/security/users/{username}', + validate: { + params: schema.object({ username: schema.string({ minLength: 1, maxLength: 1024 }) }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const username = request.params.username; + const users = (await clusterClient + .asScoped(request) + .callAsCurrentUser('shield.getUser', { username })) as Record; + + if (!users[username]) { + return response.notFound(); + } + + return response.ok({ body: users[username] }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/users/get_all.ts b/x-pack/plugins/security/server/routes/users/get_all.ts new file mode 100644 index 00000000000000..492ab27ab27adc --- /dev/null +++ b/x-pack/plugins/security/server/routes/users/get_all.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 { RouteDefinitionParams } from '../index'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; + +export function defineGetAllUsersRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.get( + { path: '/internal/security/users', validate: false }, + createLicensedRouteHandler(async (context, request, response) => { + try { + return response.ok({ + // Return only values since keys (user names) are already duplicated there. + body: Object.values( + await clusterClient.asScoped(request).callAsCurrentUser('shield.getUser') + ), + }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/users/index.ts b/x-pack/plugins/security/server/routes/users/index.ts new file mode 100644 index 00000000000000..931af0734b4164 --- /dev/null +++ b/x-pack/plugins/security/server/routes/users/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 { RouteDefinitionParams } from '../index'; +import { defineGetUserRoutes } from './get'; +import { defineGetAllUsersRoutes } from './get_all'; +import { defineCreateOrUpdateUserRoutes } from './create_or_update'; +import { defineDeleteUserRoutes } from './delete'; +import { defineChangeUserPasswordRoutes } from './change_password'; + +export function defineUsersRoutes(params: RouteDefinitionParams) { + defineGetUserRoutes(params); + defineGetAllUsersRoutes(params); + defineCreateOrUpdateUserRoutes(params); + defineDeleteUserRoutes(params); + defineChangeUserPasswordRoutes(params); +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2e7d4233dd7ff7..f047e6a506f827 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6433,7 +6433,6 @@ "xpack.maps.layerControl.tocEntry.hideDetailsButtonTitle": "レイヤー詳細を非表示", "xpack.maps.layerControl.tocEntry.showDetailsButtonAriaLabel": "レイヤー詳細を表示", "xpack.maps.layerControl.tocEntry.showDetailsButtonTitle": "レイヤー詳細を表示", - "xpack.maps.layerPanel.applyGlobalQueryCheckboxLabel": "レイヤーにグローバルフィルターを適用", "xpack.maps.layerPanel.filterEditor.addFilterButtonLabel": "フィルターを追加します", "xpack.maps.layerPanel.filterEditor.editFilterButtonLabel": "フィルターを編集", "xpack.maps.layerPanel.filterEditor.emptyState.description": "フィルターを追加してレイヤーデータを絞ります。", @@ -6468,7 +6467,6 @@ "xpack.maps.layerPanel.settingsPanel.unableToLoadTitle": "レイヤーを読み込めません", "xpack.maps.layerPanel.settingsPanel.visibleZoomLabel": "レイヤー表示のズーム範囲", "xpack.maps.layerPanel.sourceDetailsLabel": "ソースの詳細", - "xpack.maps.layerPanel.sourceSettingsTitle": "ソース設定", "xpack.maps.layerPanel.styleSettingsTitle": "レイヤースタイル", "xpack.maps.layerTocActions.cloneLayerTitle": "レイヤーおクローンを作成", "xpack.maps.layerTocActions.editLayerTitle": "レイヤーを編集", @@ -6690,7 +6688,6 @@ "xpack.maps.layerPanel.settingsPanel.percentageLabel": "%", "xpack.maps.layerPanel.settingsPanel.visibleZoom": "ズームレベル", "xpack.maps.source.esSearch.sortFieldSelectPlaceholder": "ソートフィールドを選択", - "xpack.maps.source.esSearch.sortLabel": "並べ替え", "xpack.maps.toolbarOverlay.drawBoundsLabelShort": "境界を描く", "xpack.maps.toolbarOverlay.drawShapeLabelShort": "図形を描く", "xpack.maps.tooltipSelector.addLabelWithCount": "{count} を追加", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fd5573901fb00b..1be193f83d3bd1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6435,7 +6435,6 @@ "xpack.maps.layerControl.tocEntry.hideDetailsButtonTitle": "隐藏图层详情", "xpack.maps.layerControl.tocEntry.showDetailsButtonAriaLabel": "显示图层详情", "xpack.maps.layerControl.tocEntry.showDetailsButtonTitle": "显示图层详情", - "xpack.maps.layerPanel.applyGlobalQueryCheckboxLabel": "将全局筛选应用到图层", "xpack.maps.layerPanel.filterEditor.addFilterButtonLabel": "添加筛选", "xpack.maps.layerPanel.filterEditor.editFilterButtonLabel": "编辑筛选", "xpack.maps.layerPanel.filterEditor.emptyState.description": "添加筛选以缩小图层数据范围。", @@ -6470,7 +6469,6 @@ "xpack.maps.layerPanel.settingsPanel.unableToLoadTitle": "无法加载图层", "xpack.maps.layerPanel.settingsPanel.visibleZoomLabel": "图层可见性的缩放范围", "xpack.maps.layerPanel.sourceDetailsLabel": "源详情", - "xpack.maps.layerPanel.sourceSettingsTitle": "源设置", "xpack.maps.layerPanel.styleSettingsTitle": "图层样式", "xpack.maps.layerTocActions.cloneLayerTitle": "克隆图层", "xpack.maps.layerTocActions.editLayerTitle": "编辑图层", @@ -6692,7 +6690,6 @@ "xpack.maps.layerPanel.settingsPanel.percentageLabel": "%", "xpack.maps.layerPanel.settingsPanel.visibleZoom": "缩放级别", "xpack.maps.source.esSearch.sortFieldSelectPlaceholder": "选择排序字段", - "xpack.maps.source.esSearch.sortLabel": "排序", "xpack.maps.toolbarOverlay.drawBoundsLabelShort": "绘制边界", "xpack.maps.toolbarOverlay.drawShapeLabelShort": "绘制形状", "xpack.maps.tooltipSelector.addLabelWithCount": "添加 {count} 个", diff --git a/x-pack/test/api_integration/apis/monitoring/setup/index.js b/x-pack/test/api_integration/apis/monitoring/setup/index.js index a6bb46740e940f..89e970c8002fd2 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/index.js +++ b/x-pack/test/api_integration/apis/monitoring/setup/index.js @@ -5,7 +5,10 @@ */ export default function ({ loadTestFile }) { - describe('Setup', () => { + describe('Setup', function () { + // Setup mode is not supported in cloud + this.tags(['skipCloud']); + loadTestFile(require.resolve('./collection')); }); } diff --git a/x-pack/test/api_integration/apis/security/basic_login.js b/x-pack/test/api_integration/apis/security/basic_login.js index 1d10b3f8803a56..cd85e6906d65ec 100644 --- a/x-pack/test/api_integration/apis/security/basic_login.js +++ b/x-pack/test/api_integration/apis/security/basic_login.js @@ -32,7 +32,7 @@ export default function ({ getService }) { it('should reject API requests if client is not authenticated', async () => { await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .expect(401); }); @@ -41,24 +41,24 @@ export default function ({ getService }) { const wrongUsername = `wrong-${validUsername}`; const wrongPassword = `wrong-${validPassword}`; - await supertest.post('/api/security/v1/login') + await supertest.post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: wrongUsername, password: wrongPassword }) .expect(401); - await supertest.post('/api/security/v1/login') + await supertest.post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: validUsername, password: wrongPassword }) .expect(401); - await supertest.post('/api/security/v1/login') + await supertest.post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: wrongUsername, password: validPassword }) .expect(401); }); it('should set authentication cookie for login with valid credentials', async () => { - const loginResponse = await supertest.post('/api/security/v1/login') + const loginResponse = await supertest.post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: validUsername, password: validPassword }) .expect(204); @@ -77,17 +77,17 @@ export default function ({ getService }) { const wrongUsername = `wrong-${validUsername}`; const wrongPassword = `wrong-${validPassword}`; - await supertest.get('/api/security/v1/me') + await supertest.get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Authorization', `Basic ${Buffer.from(`${wrongUsername}:${wrongPassword}`).toString('base64')}`) .expect(401); - await supertest.get('/api/security/v1/me') + await supertest.get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Authorization', `Basic ${Buffer.from(`${validUsername}:${wrongPassword}`).toString('base64')}`) .expect(401); - await supertest.get('/api/security/v1/me') + await supertest.get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Authorization', `Basic ${Buffer.from(`${wrongUsername}:${validPassword}`).toString('base64')}`) .expect(401); @@ -95,7 +95,7 @@ export default function ({ getService }) { it('should allow access to the API with valid credentials in the header', async () => { const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Authorization', `Basic ${Buffer.from(`${validUsername}:${validPassword}`).toString('base64')}`) .expect(200); @@ -116,7 +116,7 @@ export default function ({ getService }) { describe('with session cookie', () => { let sessionCookie; beforeEach(async () => { - const loginResponse = await supertest.post('/api/security/v1/login') + const loginResponse = await supertest.post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: validUsername, password: validPassword }) .expect(204); @@ -128,12 +128,12 @@ export default function ({ getService }) { // There is no session cookie provided and no server side session should have // been established, so request should be rejected. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .expect(401); const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -153,7 +153,7 @@ export default function ({ getService }) { it('should extend cookie on every successful non-system API call', async () => { const apiResponseOne = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -165,7 +165,7 @@ export default function ({ getService }) { expect(sessionCookieOne.value).to.not.equal(sessionCookie.value); const apiResponseTwo = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -179,7 +179,7 @@ export default function ({ getService }) { it('should not extend cookie for system API calls', async () => { const systemAPIResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('kbn-system-api', 'true') .set('Cookie', sessionCookie.cookieString()) @@ -190,7 +190,7 @@ export default function ({ getService }) { it('should fail and preserve session cookie if unsupported authentication schema is used', async () => { const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Authorization', 'Bearer AbCdEf') .set('Cookie', sessionCookie.cookieString()) @@ -200,7 +200,7 @@ export default function ({ getService }) { }); it('should clear cookie on logout and redirect to login', async ()=> { - const logoutResponse = await supertest.get('/api/security/v1/logout?next=%2Fabc%2Fxyz&msg=test') + const logoutResponse = await supertest.get('/api/security/logout?next=%2Fabc%2Fxyz&msg=test') .set('Cookie', sessionCookie.cookieString()) .expect(302); @@ -256,7 +256,7 @@ export default function ({ getService }) { }); it('should redirect to home page if cookie is not provided', async ()=> { - const logoutResponse = await supertest.get('/api/security/v1/logout') + const logoutResponse = await supertest.get('/api/security/logout') .expect(302); expect(logoutResponse.headers['set-cookie']).to.be(undefined); diff --git a/x-pack/test/api_integration/apis/security/change_password.ts b/x-pack/test/api_integration/apis/security/change_password.ts index 09751d4a3641af..3efb7eb2bb1ddf 100644 --- a/x-pack/test/api_integration/apis/security/change_password.ts +++ b/x-pack/test/api_integration/apis/security/change_password.ts @@ -20,7 +20,7 @@ export default function({ getService }: FtrProviderContext) { await security.user.create(mockUserName, { password: mockUserPassword, roles: [] }); const loginResponse = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: mockUserName, password: mockUserPassword }) .expect(204); @@ -34,7 +34,7 @@ export default function({ getService }: FtrProviderContext) { const newPassword = `xxx-${mockUserPassword}-xxx`; await supertest - .post(`/api/security/v1/users/${mockUserName}/password`) + .post(`/internal/security/users/${mockUserName}/password`) .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .send({ password: wrongPassword, newPassword }) @@ -42,21 +42,21 @@ export default function({ getService }: FtrProviderContext) { // Let's check that we can't login with wrong password, just in case. await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: mockUserName, password: wrongPassword }) .expect(401); // Let's check that we can't login with the password we were supposed to set. await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: mockUserName, password: newPassword }) .expect(401); // And can login with the current password. await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: mockUserName, password: mockUserPassword }) .expect(204); @@ -66,7 +66,7 @@ export default function({ getService }: FtrProviderContext) { const newPassword = `xxx-${mockUserPassword}-xxx`; const passwordChangeResponse = await supertest - .post(`/api/security/v1/users/${mockUserName}/password`) + .post(`/internal/security/users/${mockUserName}/password`) .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .send({ password: mockUserPassword, newPassword }) @@ -76,28 +76,28 @@ export default function({ getService }: FtrProviderContext) { // Let's check that previous cookie isn't valid anymore. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(401); // And that we can't login with the old password. await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: mockUserName, password: mockUserPassword }) .expect(401); // But new cookie should be valid. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', newSessionCookie.cookieString()) .expect(200); // And that we can login with new credentials. await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: mockUserName, password: newPassword }) .expect(204); diff --git a/x-pack/test/api_integration/apis/security/index_fields.ts b/x-pack/test/api_integration/apis/security/index_fields.ts index 60c6e800c40b23..7adc589fbec3ed 100644 --- a/x-pack/test/api_integration/apis/security/index_fields.ts +++ b/x-pack/test/api_integration/apis/security/index_fields.ts @@ -11,10 +11,10 @@ export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('Index Fields', () => { - describe('GET /api/security/v1/fields/{query}', () => { + describe('GET /internal/security/fields/{query}', () => { it('should return a list of available index mapping fields', async () => { await supertest - .get('/api/security/v1/fields/.kibana') + .get('/internal/security/fields/.kibana') .set('kbn-xsrf', 'xxx') .send() .expect(200) diff --git a/x-pack/test/api_integration/apis/security/session.ts b/x-pack/test/api_integration/apis/security/session.ts index 7c7883f58cb30e..5d0935bb1ae2d7 100644 --- a/x-pack/test/api_integration/apis/security/session.ts +++ b/x-pack/test/api_integration/apis/security/session.ts @@ -43,7 +43,7 @@ export default function({ getService }: FtrProviderContext) { beforeEach(async () => { await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username: validUsername, password: validPassword }) .expect(204) diff --git a/x-pack/test/api_integration/services/legacy_es.js b/x-pack/test/api_integration/services/legacy_es.js index 15185504075290..12a1576f789824 100644 --- a/x-pack/test/api_integration/services/legacy_es.js +++ b/x-pack/test/api_integration/services/legacy_es.js @@ -8,7 +8,7 @@ import { format as formatUrl } from 'url'; import * as legacyElasticsearch from 'elasticsearch'; -import shieldPlugin from '../../../legacy/server/lib/esjs_shield_plugin'; +import { elasticsearchClientPlugin } from '../../../plugins/security/server/elasticsearch_client_plugin'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { DEFAULT_API_VERSION } from '../../../../src/core/server/elasticsearch/elasticsearch_config'; @@ -19,6 +19,6 @@ export function LegacyEsProvider({ getService }) { apiVersion: DEFAULT_API_VERSION, host: formatUrl(config.get('servers.elasticsearch')), requestTimeout: config.get('timeouts.esRequestTimeout'), - plugins: [shieldPlugin], + plugins: [elasticsearchClientPlugin], }); } diff --git a/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js b/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js index aa96d712287990..2f6b1a91b7304d 100644 --- a/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js @@ -13,7 +13,7 @@ export default function ({ getService, getPageObjects }) { const clusterOverview = getService('monitoringClusterOverview'); const retry = getService('retry'); - describe('Monitoring is turned off', () => { + describe('Monitoring is turned off', function () { before(async () => { const browser = getService('browser'); await browser.setWindowSize(1600, 1000); diff --git a/x-pack/test/functional/services/monitoring/logstash_pipelines.js b/x-pack/test/functional/services/monitoring/logstash_pipelines.js index 759728555c86ad..6740d6352bbf7d 100644 --- a/x-pack/test/functional/services/monitoring/logstash_pipelines.js +++ b/x-pack/test/functional/services/monitoring/logstash_pipelines.js @@ -10,6 +10,7 @@ export function MonitoringLogstashPipelinesProvider({ getService, getPageObjects const testSubjects = getService('testSubjects'); const retry = getService('retry'); const PageObjects = getPageObjects(['monitoring']); + const find = getService('find'); const SUBJ_LISTING_PAGE = 'logstashPipelinesListing'; @@ -34,6 +35,12 @@ export function MonitoringLogstashPipelinesProvider({ getService, getPageObjects return PageObjects.monitoring.tableGetRowsFromContainer(SUBJ_TABLE_CONTAINER); } + async waitForTableToFinishLoading() { + await retry.try(async () => { + await find.waitForDeletedByCssSelector('.euiBasicTable-loading', 5000); + }); + } + async getPipelinesAll() { const ids = await testSubjects.getVisibleTextAll(SUBJ_PIPELINES_IDS); const eventsEmittedRates = await testSubjects.getVisibleTextAll(SUBJ_PIPELINES_EVENTS_EMITTED_RATES); @@ -57,21 +64,25 @@ export function MonitoringLogstashPipelinesProvider({ getService, getPageObjects async clickIdCol() { const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_ID_COL); const button = await headerCell.findByTagName('button'); - return button.click(); + await button.click(); + await this.waitForTableToFinishLoading(); } async clickEventsEmittedRateCol() { const headerCell = await testSubjects.find(SUBJ_TABLE_SORT_EVENTS_EMITTED_RATE_COL); const button = await headerCell.findByTagName('button'); - return button.click(); + await button.click(); + await this.waitForTableToFinishLoading(); } - setFilter(text) { - return PageObjects.monitoring.tableSetFilter(SUBJ_SEARCH_BAR, text); + async setFilter(text) { + await PageObjects.monitoring.tableSetFilter(SUBJ_SEARCH_BAR, text); + await this.waitForTableToFinishLoading(); } - clearFilter() { - return PageObjects.monitoring.tableClearFilter(SUBJ_SEARCH_BAR); + async clearFilter() { + await PageObjects.monitoring.tableClearFilter(SUBJ_SEARCH_BAR); + await this.waitForTableToFinishLoading(); } assertNoData() { diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index 450f7b1a427dc4..0346da334d2f2d 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -47,7 +47,7 @@ export default function({ getService }: FtrProviderContext) { it('should reject API requests if client is not authenticated', async () => { await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .expect(401); }); @@ -55,7 +55,7 @@ export default function({ getService }: FtrProviderContext) { it('does not prevent basic login', async () => { const [username, password] = config.get('servers.elasticsearch.auth').split(':'); const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username, password }) .expect(204); @@ -67,7 +67,7 @@ export default function({ getService }: FtrProviderContext) { checkCookieIsSet(cookie); const { body: user } = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', cookie.cookieString()) .expect(200); @@ -98,7 +98,7 @@ export default function({ getService }: FtrProviderContext) { describe('finishing SPNEGO', () => { it('should properly set cookie and authenticate user', async () => { const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -114,7 +114,7 @@ export default function({ getService }: FtrProviderContext) { checkCookieIsSet(sessionCookie); await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200, { @@ -134,7 +134,7 @@ export default function({ getService }: FtrProviderContext) { it('should re-initiate SPNEGO handshake if token is rejected with 401', async () => { const spnegoResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('Authorization', `Negotiate ${Buffer.from('Hello').toString('base64')}`) .expect(401); expect(spnegoResponse.headers['set-cookie']).to.be(undefined); @@ -143,7 +143,7 @@ export default function({ getService }: FtrProviderContext) { it('should fail if SPNEGO token is rejected because of unknown reason', async () => { const spnegoResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('Authorization', 'Negotiate (:I am malformed:)') .expect(500); expect(spnegoResponse.headers['set-cookie']).to.be(undefined); @@ -156,7 +156,7 @@ export default function({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -169,7 +169,7 @@ export default function({ getService }: FtrProviderContext) { it('should extend cookie on every successful non-system API call', async () => { const apiResponseOne = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -181,7 +181,7 @@ export default function({ getService }: FtrProviderContext) { expect(sessionCookieOne.value).to.not.equal(sessionCookie.value); const apiResponseTwo = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -195,7 +195,7 @@ export default function({ getService }: FtrProviderContext) { it('should not extend cookie for system API calls', async () => { const systemAPIResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('kbn-system-api', 'true') .set('Cookie', sessionCookie.cookieString()) @@ -206,7 +206,7 @@ export default function({ getService }: FtrProviderContext) { it('should fail and preserve session cookie if unsupported authentication schema is used', async () => { const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Authorization', 'Basic a3JiNTprcmI1') .set('Cookie', sessionCookie.cookieString()) @@ -220,7 +220,7 @@ export default function({ getService }: FtrProviderContext) { it('should redirect to `logged_out` page after successful logout', async () => { // First authenticate user to retrieve session cookie. const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -232,7 +232,7 @@ export default function({ getService }: FtrProviderContext) { // And then log user out. const logoutResponse = await supertest - .get('/api/security/v1/logout') + .get('/api/security/logout') .set('Cookie', sessionCookie.cookieString()) .expect(302); @@ -245,7 +245,7 @@ export default function({ getService }: FtrProviderContext) { // Token that was stored in the previous cookie should be invalidated as well and old // session cookie should not allow API access. const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(401); @@ -259,7 +259,7 @@ export default function({ getService }: FtrProviderContext) { }); it('should redirect to home page if session cookie is not provided', async () => { - const logoutResponse = await supertest.get('/api/security/v1/logout').expect(302); + const logoutResponse = await supertest.get('/api/security/logout').expect(302); expect(logoutResponse.headers['set-cookie']).to.be(undefined); expect(logoutResponse.headers.location).to.be('/'); @@ -271,7 +271,7 @@ export default function({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -292,7 +292,7 @@ export default function({ getService }: FtrProviderContext) { // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -305,7 +305,7 @@ export default function({ getService }: FtrProviderContext) { // The first new cookie with fresh pair of access and refresh tokens should work. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', refreshedCookie.cookieString()) .expect(200); @@ -335,7 +335,7 @@ export default function({ getService }: FtrProviderContext) { // The first new cookie with fresh pair of access and refresh tokens should work. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', refreshedCookie.cookieString()) .expect(200); @@ -349,7 +349,7 @@ export default function({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('Authorization', `Negotiate ${spnegoToken}`) .expect(200); @@ -374,7 +374,7 @@ export default function({ getService }: FtrProviderContext) { it('AJAX call should initiate SPNEGO and clear existing cookie', async function() { const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(401); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js index 80ef6bd6df4ff1..95958d12a42d7d 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js +++ b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js @@ -16,7 +16,7 @@ export default function ({ getService }) { describe('OpenID Connect authentication', () => { it('should reject API requests if client is not authenticated', async () => { await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .expect(401); }); @@ -46,7 +46,8 @@ export default function ({ getService }) { }); it('should properly set cookie, return all parameters and redirect user for Third Party initiated', async () => { - const handshakeResponse = await supertest.get('/api/security/v1/oidc?iss=https://test-op.elastic.co') + const handshakeResponse = await supertest.post('/api/security/oidc/initiate_login') + .send({ iss: 'https://test-op.elastic.co' }) .expect(302); const cookies = handshakeResponse.headers['set-cookie']; @@ -74,7 +75,7 @@ export default function ({ getService }) { const handshakeCookie = request.cookie(handshakeResponse.headers['set-cookie'][0]); await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(401); @@ -108,20 +109,20 @@ export default function ({ getService }) { }); it('should fail if OpenID Connect response is not complemented with handshake cookie', async () => { - await supertest.get(`/api/security/v1/oidc?code=thisisthecode&state=${stateAndNonce.state}`) + await supertest.get(`/api/security/oidc?code=thisisthecode&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .expect(401); }); it('should fail if state is not matching', async () => { - await supertest.get(`/api/security/v1/oidc?code=thisisthecode&state=someothervalue`) + await supertest.get(`/api/security/oidc?code=thisisthecode&state=someothervalue`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(401); }); it('should succeed if both the OpenID Connect response and the cookie are provided', async () => { - const oidcAuthenticationResponse = await supertest.get(`/api/security/v1/oidc?code=code1&state=${stateAndNonce.state}`) + const oidcAuthenticationResponse = await supertest.get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); @@ -139,7 +140,7 @@ export default function ({ getService }) { expect(sessionCookie.httpOnly).to.be(true); const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -160,7 +161,7 @@ export default function ({ getService }) { describe('Complete third party initiated authentication', () => { it('should authenticate a user when a third party initiates the authentication', async () => { - const handshakeResponse = await supertest.get('/api/security/v1/oidc?iss=https://test-op.elastic.co') + const handshakeResponse = await supertest.get('/api/security/oidc/initiate_login?iss=https://test-op.elastic.co') .expect(302); const handshakeCookie = request.cookie(handshakeResponse.headers['set-cookie'][0]); const stateAndNonce = getStateAndNonce(handshakeResponse.headers.location); @@ -172,7 +173,7 @@ export default function ({ getService }) { .send({ nonce: stateAndNonce.nonce }) .expect(200); - const oidcAuthenticationResponse = await supertest.get(`/api/security/v1/oidc?code=code2&state=${stateAndNonce.state}`) + const oidcAuthenticationResponse = await supertest.get(`/api/security/oidc?code=code2&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); @@ -186,7 +187,7 @@ export default function ({ getService }) { expect(sessionCookie.httpOnly).to.be(true); const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -222,7 +223,7 @@ export default function ({ getService }) { .send({ nonce: stateAndNonce.nonce }) .expect(200); - const oidcAuthenticationResponse = await supertest.get(`/api/security/v1/oidc?code=code1&state=${stateAndNonce.state}`) + const oidcAuthenticationResponse = await supertest.get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(302); @@ -232,7 +233,7 @@ export default function ({ getService }) { it('should extend cookie on every successful non-system API call', async () => { const apiResponseOne = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -244,7 +245,7 @@ export default function ({ getService }) { expect(sessionCookieOne.value).to.not.equal(sessionCookie.value); const apiResponseTwo = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -258,7 +259,7 @@ export default function ({ getService }) { it('should not extend cookie for system API calls', async () => { const systemAPIResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('kbn-system-api', 'true') .set('Cookie', sessionCookie.cookieString()) @@ -269,7 +270,7 @@ export default function ({ getService }) { it('should fail and preserve session cookie if unsupported authentication schema is used', async () => { const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Authorization', 'Basic AbCdEf') .set('Cookie', sessionCookie.cookieString()) @@ -295,7 +296,7 @@ export default function ({ getService }) { .send({ nonce: stateAndNonce.nonce }) .expect(200); - const oidcAuthenticationResponse = await supertest.get(`/api/security/v1/oidc?code=code1&state=${stateAndNonce.state}`) + const oidcAuthenticationResponse = await supertest.get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); @@ -307,7 +308,7 @@ export default function ({ getService }) { }); it('should redirect to home page if session cookie is not provided', async () => { - const logoutResponse = await supertest.get('/api/security/v1/logout') + const logoutResponse = await supertest.get('/api/security/logout') .expect(302); expect(logoutResponse.headers['set-cookie']).to.be(undefined); @@ -315,7 +316,7 @@ export default function ({ getService }) { }); it('should redirect to the OPs endsession endpoint to complete logout', async () => { - const logoutResponse = await supertest.get('/api/security/v1/logout') + const logoutResponse = await supertest.get('/api/security/logout') .set('Cookie', sessionCookie.cookieString()) .expect(302); @@ -336,7 +337,7 @@ export default function ({ getService }) { // Tokens that were stored in the previous cookie should be invalidated as well and old // session cookie should not allow API access. const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(400); @@ -349,7 +350,7 @@ export default function ({ getService }) { }); it('should reject AJAX requests', async () => { - const ajaxResponse = await supertest.get('/api/security/v1/logout') + const ajaxResponse = await supertest.get('/api/security/logout') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(400); @@ -379,7 +380,7 @@ export default function ({ getService }) { .send({ nonce: stateAndNonce.nonce }) .expect(200); - const oidcAuthenticationResponse = await supertest.get(`/api/security/v1/oidc?code=code1&state=${stateAndNonce.state}`) + const oidcAuthenticationResponse = await supertest.get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); @@ -408,7 +409,7 @@ export default function ({ getService }) { // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const firstResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -422,7 +423,7 @@ export default function ({ getService }) { // Request with old cookie should reuse the same refresh token if within 60 seconds. // Returned cookie will contain the same new access and refresh token pairs as the first request const secondResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -437,14 +438,14 @@ export default function ({ getService }) { // The first new cookie with fresh pair of access and refresh tokens should work. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', firstNewCookie.cookieString()) .expect(200); // The second new cookie with fresh pair of access and refresh tokens should work. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', secondNewCookie.cookieString()) .expect(200); @@ -467,7 +468,7 @@ export default function ({ getService }) { .send({ nonce: stateAndNonce.nonce }) .expect(200); - const oidcAuthenticationResponse = await supertest.get(`/api/security/v1/oidc?code=code1&state=${stateAndNonce.state}`) + const oidcAuthenticationResponse = await supertest.get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts index 23cbb312b092a7..0e07f01776713d 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts @@ -31,7 +31,7 @@ export default function({ getService }: FtrProviderContext) { }); it('should return an HTML page that will parse URL fragment', async () => { - const response = await supertest.get('/api/security/v1/oidc/implicit').expect(200); + const response = await supertest.get('/api/security/oidc/implicit').expect(200); const dom = new JSDOM(response.text, { url: formatURL({ ...config.get('servers.kibana'), auth: false }), runScripts: 'dangerously', @@ -44,7 +44,7 @@ export default function({ getService }: FtrProviderContext) { Object.defineProperty(window, 'location', { value: { href: - 'https://kibana.com/api/security/v1/oidc/implicit#token=some_token&access_token=some_access_token', + 'https://kibana.com/api/security/oidc/implicit#token=some_token&access_token=some_access_token', replace(newLocation: string) { this.href = newLocation; resolve(); @@ -66,17 +66,17 @@ export default function({ getService }: FtrProviderContext) { // Check that script that forwards URL fragment worked correctly. expect(dom.window.location.href).to.be( - '/api/security/v1/oidc?authenticationResponseURI=https%3A%2F%2Fkibana.com%2Fapi%2Fsecurity%2Fv1%2Foidc%2Fimplicit%23token%3Dsome_token%26access_token%3Dsome_access_token' + '/api/security/oidc?authenticationResponseURI=https%3A%2F%2Fkibana.com%2Fapi%2Fsecurity%2Foidc%2Fimplicit%23token%3Dsome_token%26access_token%3Dsome_access_token' ); }); it('should fail if OpenID Connect response is not complemented with handshake cookie', async () => { const { idToken, accessToken } = createTokens('1', stateAndNonce.nonce); - const authenticationResponse = `https://kibana.com/api/security/v1/oidc/implicit#id_token=${idToken}&state=${stateAndNonce.state}&token_type=bearer&access_token=${accessToken}`; + const authenticationResponse = `https://kibana.com/api/security/oidc/implicit#id_token=${idToken}&state=${stateAndNonce.state}&token_type=bearer&access_token=${accessToken}`; await supertest .get( - `/api/security/v1/oidc?authenticationResponseURI=${encodeURIComponent( + `/api/security/oidc?authenticationResponseURI=${encodeURIComponent( authenticationResponse )}` ) @@ -86,11 +86,11 @@ export default function({ getService }: FtrProviderContext) { it('should fail if state is not matching', async () => { const { idToken, accessToken } = createTokens('1', stateAndNonce.nonce); - const authenticationResponse = `https://kibana.com/api/security/v1/oidc/implicit#id_token=${idToken}&state=$someothervalue&token_type=bearer&access_token=${accessToken}`; + const authenticationResponse = `https://kibana.com/api/security/oidc/implicit#id_token=${idToken}&state=$someothervalue&token_type=bearer&access_token=${accessToken}`; await supertest .get( - `/api/security/v1/oidc?authenticationResponseURI=${encodeURIComponent( + `/api/security/oidc?authenticationResponseURI=${encodeURIComponent( authenticationResponse )}` ) @@ -102,11 +102,11 @@ export default function({ getService }: FtrProviderContext) { // FLAKY: https://github.com/elastic/kibana/issues/43938 it.skip('should succeed if both the OpenID Connect response and the cookie are provided', async () => { const { idToken, accessToken } = createTokens('1', stateAndNonce.nonce); - const authenticationResponse = `https://kibana.com/api/security/v1/oidc/implicit#id_token=${idToken}&state=${stateAndNonce.state}&token_type=bearer&access_token=${accessToken}`; + const authenticationResponse = `https://kibana.com/api/security/oidc/implicit#id_token=${idToken}&state=${stateAndNonce.state}&token_type=bearer&access_token=${accessToken}`; const oidcAuthenticationResponse = await supertest .get( - `/api/security/v1/oidc?authenticationResponseURI=${encodeURIComponent( + `/api/security/oidc?authenticationResponseURI=${encodeURIComponent( authenticationResponse )}` ) @@ -129,7 +129,7 @@ export default function({ getService }: FtrProviderContext) { expect(sessionCookie.httpOnly).to.be(true); const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); diff --git a/x-pack/test/oidc_api_integration/config.ts b/x-pack/test/oidc_api_integration/config.ts index f40db4ccbba0ac..184ccbcdfa6918 100644 --- a/x-pack/test/oidc_api_integration/config.ts +++ b/x-pack/test/oidc_api_integration/config.ts @@ -32,7 +32,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { `xpack.security.authc.realms.oidc.oidc1.rp.client_id=0oa8sqpov3TxMWJOt356`, `xpack.security.authc.realms.oidc.oidc1.rp.client_secret=0oa8sqpov3TxMWJOt356`, `xpack.security.authc.realms.oidc.oidc1.rp.response_type=code`, - `xpack.security.authc.realms.oidc.oidc1.rp.redirect_uri=http://localhost:${kibanaPort}/api/security/v1/oidc`, + `xpack.security.authc.realms.oidc.oidc1.rp.redirect_uri=http://localhost:${kibanaPort}/api/security/oidc`, `xpack.security.authc.realms.oidc.oidc1.op.authorization_endpoint=https://test-op.elastic.co/oauth2/v1/authorize`, `xpack.security.authc.realms.oidc.oidc1.op.endsession_endpoint=https://test-op.elastic.co/oauth2/v1/endsession`, `xpack.security.authc.realms.oidc.oidc1.op.token_endpoint=http://localhost:${kibanaPort}/api/oidc_provider/token_endpoint`, @@ -52,7 +52,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { '--xpack.security.authc.oidc.realm="oidc1"', '--server.xsrf.whitelist', JSON.stringify([ - '/api/security/v1/oidc', + '/api/security/oidc/initiate_login', '/api/oidc_provider/token_endpoint', '/api/oidc_provider/userinfo_endpoint', ]), diff --git a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts index ff075320031dca..d977a0fc64eb66 100644 --- a/x-pack/test/pki_api_integration/apis/security/pki_auth.ts +++ b/x-pack/test/pki_api_integration/apis/security/pki_auth.ts @@ -57,7 +57,7 @@ export default function({ getService }: FtrProviderContext) { it('should reject API requests that use untrusted certificate', async () => { await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(UNTRUSTED_CLIENT_CERT) .set('kbn-xsrf', 'xxx') @@ -67,7 +67,7 @@ export default function({ getService }: FtrProviderContext) { it('does not prevent basic login', async () => { const [username, password] = config.get('servers.elasticsearch.auth').split(':'); const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .ca(CA_CERT) .pfx(UNTRUSTED_CLIENT_CERT) .set('kbn-xsrf', 'xxx') @@ -81,7 +81,7 @@ export default function({ getService }: FtrProviderContext) { checkCookieIsSet(cookie); const { body: user } = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(UNTRUSTED_CLIENT_CERT) .set('kbn-xsrf', 'xxx') @@ -94,7 +94,7 @@ export default function({ getService }: FtrProviderContext) { it('should properly set cookie and authenticate user', async () => { const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -123,7 +123,7 @@ export default function({ getService }: FtrProviderContext) { // Cookie should be accepted. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('Cookie', sessionCookie.cookieString()) @@ -132,7 +132,7 @@ export default function({ getService }: FtrProviderContext) { it('should update session if new certificate is provided', async () => { let response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -144,7 +144,7 @@ export default function({ getService }: FtrProviderContext) { checkCookieIsSet(sessionCookie); response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(SECOND_CLIENT_CERT) .set('Cookie', sessionCookie.cookieString()) @@ -169,7 +169,7 @@ export default function({ getService }: FtrProviderContext) { it('should reject valid cookie if used with untrusted certificate', async () => { const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -181,7 +181,7 @@ export default function({ getService }: FtrProviderContext) { checkCookieIsSet(sessionCookie); await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(UNTRUSTED_CLIENT_CERT) .set('Cookie', sessionCookie.cookieString()) @@ -193,7 +193,7 @@ export default function({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -207,7 +207,7 @@ export default function({ getService }: FtrProviderContext) { it('should extend cookie on every successful non-system API call', async () => { const apiResponseOne = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('kbn-xsrf', 'xxx') @@ -221,7 +221,7 @@ export default function({ getService }: FtrProviderContext) { expect(sessionCookieOne.value).to.not.equal(sessionCookie.value); const apiResponseTwo = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('kbn-xsrf', 'xxx') @@ -237,7 +237,7 @@ export default function({ getService }: FtrProviderContext) { it('should not extend cookie for system API calls', async () => { const systemAPIResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('kbn-xsrf', 'xxx') @@ -250,7 +250,7 @@ export default function({ getService }: FtrProviderContext) { it('should fail and preserve session cookie if unsupported authentication schema is used', async () => { const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('kbn-xsrf', 'xxx') @@ -266,7 +266,7 @@ export default function({ getService }: FtrProviderContext) { it('should redirect to `logged_out` page after successful logout', async () => { // First authenticate user to retrieve session cookie. const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -279,7 +279,7 @@ export default function({ getService }: FtrProviderContext) { // And then log user out. const logoutResponse = await supertest - .get('/api/security/v1/logout') + .get('/api/security/logout') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('Cookie', sessionCookie.cookieString()) @@ -294,7 +294,7 @@ export default function({ getService }: FtrProviderContext) { it('should redirect to home page if session cookie is not provided', async () => { const logoutResponse = await supertest - .get('/api/security/v1/logout') + .get('/api/security/logout') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(302); @@ -309,7 +309,7 @@ export default function({ getService }: FtrProviderContext) { beforeEach(async () => { const response = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .expect(200); @@ -331,7 +331,7 @@ export default function({ getService }: FtrProviderContext) { // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access token. const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .ca(CA_CERT) .pfx(FIRST_CLIENT_CERT) .set('kbn-xsrf', 'xxx') diff --git a/x-pack/test/saml_api_integration/apis/security/saml_login.ts b/x-pack/test/saml_api_integration/apis/security/saml_login.ts index 3815788aa746ed..0436d59906ea87 100644 --- a/x-pack/test/saml_api_integration/apis/security/saml_login.ts +++ b/x-pack/test/saml_api_integration/apis/security/saml_login.ts @@ -42,7 +42,7 @@ export default function({ getService }: FtrProviderContext) { expect(sessionCookie.httpOnly).to.be(true); const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -64,7 +64,7 @@ export default function({ getService }: FtrProviderContext) { describe('SAML authentication', () => { it('should reject API requests if client is not authenticated', async () => { await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .expect(401); }); @@ -72,7 +72,7 @@ export default function({ getService }: FtrProviderContext) { it('does not prevent basic login', async () => { const [username, password] = config.get('servers.elasticsearch.auth').split(':'); const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'xxx') .send({ username, password }) .expect(204); @@ -81,7 +81,7 @@ export default function({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); const { body: user } = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', request.cookie(cookies[0])!.cookieString()) .expect(200); @@ -192,7 +192,7 @@ export default function({ getService }: FtrProviderContext) { const handshakeCookie = request.cookie(handshakeResponse.headers['set-cookie'][0])!; await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(401); @@ -300,7 +300,7 @@ export default function({ getService }: FtrProviderContext) { it('should extend cookie on every successful non-system API call', async () => { const apiResponseOne = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -312,7 +312,7 @@ export default function({ getService }: FtrProviderContext) { expect(sessionCookieOne.value).to.not.equal(sessionCookie.value); const apiResponseTwo = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -326,7 +326,7 @@ export default function({ getService }: FtrProviderContext) { it('should not extend cookie for system API calls', async () => { const systemAPIResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('kbn-system-api', 'true') .set('Cookie', sessionCookie.cookieString()) @@ -337,7 +337,7 @@ export default function({ getService }: FtrProviderContext) { it('should fail and preserve session cookie if unsupported authentication schema is used', async () => { const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Authorization', 'Basic AbCdEf') .set('Cookie', sessionCookie.cookieString()) @@ -383,7 +383,7 @@ export default function({ getService }: FtrProviderContext) { it('should redirect to IdP with SAML request to complete logout', async () => { const logoutResponse = await supertest - .get('/api/security/v1/logout') + .get('/api/security/logout') .set('Cookie', sessionCookie.cookieString()) .expect(302); @@ -404,7 +404,7 @@ export default function({ getService }: FtrProviderContext) { // Tokens that were stored in the previous cookie should be invalidated as well and old // session cookie should not allow API access. const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(400); @@ -417,7 +417,7 @@ export default function({ getService }: FtrProviderContext) { }); it('should redirect to home page if session cookie is not provided', async () => { - const logoutResponse = await supertest.get('/api/security/v1/logout').expect(302); + const logoutResponse = await supertest.get('/api/security/logout').expect(302); expect(logoutResponse.headers['set-cookie']).to.be(undefined); expect(logoutResponse.headers.location).to.be('/'); @@ -425,7 +425,7 @@ export default function({ getService }: FtrProviderContext) { it('should reject AJAX requests', async () => { const ajaxResponse = await supertest - .get('/api/security/v1/logout') + .get('/api/security/logout') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(400); @@ -441,7 +441,7 @@ export default function({ getService }: FtrProviderContext) { it('should invalidate access token on IdP initiated logout', async () => { const logoutRequest = await createLogoutRequest({ sessionIndex: idpSessionIndex }); const logoutResponse = await supertest - .get(`/api/security/v1/logout?${querystring.stringify(logoutRequest)}`) + .get(`/api/security/logout?${querystring.stringify(logoutRequest)}`) .set('Cookie', sessionCookie.cookieString()) .expect(302); @@ -462,7 +462,7 @@ export default function({ getService }: FtrProviderContext) { // Tokens that were stored in the previous cookie should be invalidated as well and old session // cookie should not allow API access. const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(400); @@ -477,7 +477,7 @@ export default function({ getService }: FtrProviderContext) { it('should invalidate access token on IdP initiated logout even if there is no Kibana session', async () => { const logoutRequest = await createLogoutRequest({ sessionIndex: idpSessionIndex }); const logoutResponse = await supertest - .get(`/api/security/v1/logout?${querystring.stringify(logoutRequest)}`) + .get(`/api/security/logout?${querystring.stringify(logoutRequest)}`) .expect(302); expect(logoutResponse.headers['set-cookie']).to.be(undefined); @@ -490,7 +490,7 @@ export default function({ getService }: FtrProviderContext) { // IdP session id (encoded in SAML LogoutRequest) even if Kibana doesn't provide them and session // cookie with these tokens should not allow API access. const apiResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(400); @@ -548,7 +548,7 @@ export default function({ getService }: FtrProviderContext) { // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const firstResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -562,7 +562,7 @@ export default function({ getService }: FtrProviderContext) { // Request with old cookie should reuse the same refresh token if within 60 seconds. // Returned cookie will contain the same new access and refresh token pairs as the first request const secondResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200); @@ -577,14 +577,14 @@ export default function({ getService }: FtrProviderContext) { // The first new cookie with fresh pair of access and refresh tokens should work. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', firstNewCookie.cookieString()) .expect(200); // The second new cookie with fresh pair of access and refresh tokens should work. await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', secondNewCookie.cookieString()) .expect(200); @@ -701,7 +701,7 @@ export default function({ getService }: FtrProviderContext) { // Tokens from old cookie are invalidated. const rejectedResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', existingSessionCookie.cookieString()) .expect(400); @@ -712,7 +712,7 @@ export default function({ getService }: FtrProviderContext) { // Only tokens from new session are valid. const acceptedResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', newSessionCookie.cookieString()) .expect(200); @@ -737,7 +737,7 @@ export default function({ getService }: FtrProviderContext) { // Tokens from old cookie are invalidated. const rejectedResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', existingSessionCookie.cookieString()) .expect(400); @@ -748,7 +748,7 @@ export default function({ getService }: FtrProviderContext) { // Only tokens from new session are valid. const acceptedResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', newSessionCookie.cookieString()) .expect(200); diff --git a/x-pack/test/saved_object_api_integration/common/services/legacy_es.js b/x-pack/test/saved_object_api_integration/common/services/legacy_es.js index 94aa6025aa6994..9267fa312ed065 100644 --- a/x-pack/test/saved_object_api_integration/common/services/legacy_es.js +++ b/x-pack/test/saved_object_api_integration/common/services/legacy_es.js @@ -8,7 +8,7 @@ import { format as formatUrl } from 'url'; import * as legacyElasticsearch from 'elasticsearch'; -import shieldPlugin from '../../../../legacy/server/lib/esjs_shield_plugin'; +import { elasticsearchClientPlugin } from '../../../../plugins/security/server/elasticsearch_client_plugin'; export function LegacyEsProvider({ getService }) { const config = getService('config'); @@ -16,6 +16,6 @@ export function LegacyEsProvider({ getService }) { return new legacyElasticsearch.Client({ host: formatUrl(config.get('servers.elasticsearch')), requestTimeout: config.get('timeouts.esRequestTimeout'), - plugins: [shieldPlugin], + plugins: [elasticsearchClientPlugin], }); } diff --git a/x-pack/test/spaces_api_integration/common/services/legacy_es.js b/x-pack/test/spaces_api_integration/common/services/legacy_es.js index 5e8137f0d11b5f..5862fe877ba5c3 100644 --- a/x-pack/test/spaces_api_integration/common/services/legacy_es.js +++ b/x-pack/test/spaces_api_integration/common/services/legacy_es.js @@ -8,7 +8,7 @@ import { format as formatUrl } from 'url'; import * as legacyElasticsearch from 'elasticsearch'; -import shieldPlugin from '../../../../legacy/server/lib/esjs_shield_plugin'; +import { elasticsearchClientPlugin } from '../../../../plugins/security/server/elasticsearch_client_plugin'; export function LegacyEsProvider({ getService }) { const config = getService('config'); @@ -16,6 +16,6 @@ export function LegacyEsProvider({ getService }) { return new legacyElasticsearch.Client({ host: formatUrl(config.get('servers.elasticsearch')), requestTimeout: config.get('timeouts.esRequestTimeout'), - plugins: [shieldPlugin] + plugins: [elasticsearchClientPlugin] }); } diff --git a/x-pack/test/token_api_integration/auth/header.js b/x-pack/test/token_api_integration/auth/header.js index 4b27fd5db31663..1c88f28a655417 100644 --- a/x-pack/test/token_api_integration/auth/header.js +++ b/x-pack/test/token_api_integration/auth/header.js @@ -25,7 +25,7 @@ export default function ({ getService }) { const token = await createToken(); await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('authorization', `Bearer ${token}`) .expect(200); @@ -36,14 +36,14 @@ export default function ({ getService }) { // try it once await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('authorization', `Bearer ${token}`) .expect(200); // try it again to verity it isn't invalidated after a single request await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('authorization', `Bearer ${token}`) .expect(200); @@ -51,7 +51,7 @@ export default function ({ getService }) { it('rejects invalid access token via authorization Bearer header', async () => { await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('authorization', 'Bearer notreal') .expect(401); @@ -67,7 +67,7 @@ export default function ({ getService }) { await new Promise(resolve => setTimeout(() => resolve(), 20000)); await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('authorization', `Bearer ${token}`) .expect(401); diff --git a/x-pack/test/token_api_integration/auth/login.js b/x-pack/test/token_api_integration/auth/login.js index 2e6a2e2f81e4ff..aba7e3852aa1f8 100644 --- a/x-pack/test/token_api_integration/auth/login.js +++ b/x-pack/test/token_api_integration/auth/login.js @@ -17,7 +17,7 @@ export default function ({ getService }) { describe('login', () => { it('accepts valid login credentials as 204 status', async () => { await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'true') .send({ username: 'elastic', password: 'changeme' }) .expect(204); @@ -25,7 +25,7 @@ export default function ({ getService }) { it('sets HttpOnly cookie with valid login', async () => { const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'true') .send({ username: 'elastic', password: 'changeme' }) .expect(204); @@ -42,7 +42,7 @@ export default function ({ getService }) { it('rejects without kbn-xsrf header as 400 status even if credentials are valid', async () => { const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .send({ username: 'elastic', password: 'changeme' }) .expect(400); @@ -53,7 +53,7 @@ export default function ({ getService }) { it('rejects without credentials as 400 status', async () => { const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'true') .expect(400); @@ -64,7 +64,7 @@ export default function ({ getService }) { it('rejects without password as 400 status', async () => { const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'true') .send({ username: 'elastic' }) .expect(400); @@ -76,7 +76,7 @@ export default function ({ getService }) { it('rejects without username as 400 status', async () => { const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'true') .send({ password: 'changme' }) .expect(400); @@ -88,7 +88,7 @@ export default function ({ getService }) { it('rejects invalid credentials as 401 status', async () => { const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'true') .send({ username: 'elastic', password: 'notvalidpassword' }) .expect(401); diff --git a/x-pack/test/token_api_integration/auth/logout.js b/x-pack/test/token_api_integration/auth/logout.js index 90634886819584..fa7a0606c3770f 100644 --- a/x-pack/test/token_api_integration/auth/logout.js +++ b/x-pack/test/token_api_integration/auth/logout.js @@ -16,7 +16,7 @@ export default function ({ getService }) { async function createSessionCookie() { const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'true') .send({ username: 'elastic', password: 'changeme' }); @@ -33,7 +33,7 @@ export default function ({ getService }) { const cookie = await createSessionCookie(); await supertest - .get('/api/security/v1/logout') + .get('/api/security/logout') .set('cookie', cookie.cookieString()) .expect(302) .expect('location', '/login?msg=LOGGED_OUT'); @@ -43,7 +43,7 @@ export default function ({ getService }) { const cookie = await createSessionCookie(); const response = await supertest - .get('/api/security/v1/logout') + .get('/api/security/logout') .set('cookie', cookie.cookieString()); const newCookie = extractSessionCookie(response); @@ -60,12 +60,12 @@ export default function ({ getService }) { // destroy it await supertest - .get('/api/security/v1/logout') + .get('/api/security/logout') .set('cookie', cookie.cookieString()); // verify that the cookie no longer works await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('cookie', cookie.cookieString()) .expect(400); diff --git a/x-pack/test/token_api_integration/auth/session.js b/x-pack/test/token_api_integration/auth/session.js index 8a9f1d7a3f2295..6e8e8c01f3da6b 100644 --- a/x-pack/test/token_api_integration/auth/session.js +++ b/x-pack/test/token_api_integration/auth/session.js @@ -19,7 +19,7 @@ export default function ({ getService }) { async function createSessionCookie() { const response = await supertest - .post('/api/security/v1/login') + .post('/internal/security/login') .set('kbn-xsrf', 'true') .send({ username: 'elastic', password: 'changeme' }); @@ -36,7 +36,7 @@ export default function ({ getService }) { const cookie = await createSessionCookie(); await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('cookie', cookie.cookieString()) .expect(200); @@ -47,14 +47,14 @@ export default function ({ getService }) { // try it once await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('cookie', cookie.cookieString()) .expect(200); // try it again to verity it isn't invalidated after a single request await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('cookie', cookie.cookieString()) .expect(200); @@ -85,7 +85,7 @@ export default function ({ getService }) { // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const firstResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('cookie', originalCookie.cookieString()) .expect(200); @@ -96,7 +96,7 @@ export default function ({ getService }) { // Request with old cookie should return another valid cookie we can use to authenticate requests // if it happens within 60 seconds of the refresh token being used const secondResponse = await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('Cookie', originalCookie.cookieString()) .expect(200); @@ -110,14 +110,14 @@ export default function ({ getService }) { // The first new cookie should authenticate a subsequent request await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('Cookie', firstNewCookie.cookieString()) .expect(200); // The second new cookie should authenticate a subsequent request await supertest - .get('/api/security/v1/me') + .get('/internal/security/me') .set('kbn-xsrf', 'true') .set('Cookie', secondNewCookie.cookieString()) .expect(200); diff --git a/yarn.lock b/yarn.lock index a88090f589afa5..44e336aedc3b2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@babel/cli@7.5.5", "@babel/cli@^7.5.5": +"@babel/cli@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.5.5.tgz#bdb6d9169e93e241a08f5f7b0265195bf38ef5ec" integrity sha512-UHI+7pHv/tk9g6WXQKYz+kmXTI77YtuY3vqC59KIqcoWEjsJJSG6rAxKaLsgj3LDyadsPrCB929gVOKM6Hui0w== @@ -1005,17 +1005,17 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@7.5.5", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5": +"@babel/runtime@7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.4.4", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2": - version "7.7.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a" - integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2": + version "7.7.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" + integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw== dependencies: regenerator-runtime "^0.13.2" @@ -3995,19 +3995,19 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@^4.3.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04" - integrity sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A== +"@types/react-router-dom@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" + integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA== dependencies: "@types/history" "*" "@types/react" "*" "@types/react-router" "*" -"@types/react-router@*": - version "4.0.32" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.32.tgz#501529e3d7aa7d5c738d339367e1a7dd5338b2a7" - integrity sha512-VLQSifCIKCTpfMFrJN/nO5a45LduB6qSMkO9ASbcGdCHiDwJnrLNzk91Q895yG0qWY7RqT2jR16giBRpRG1HQw== +"@types/react-router@*", "@types/react-router@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.3.tgz#7c7ca717399af64d8733d8cb338dd43641b96f2d" + integrity sha512-0gGhmerBqN8CzlnDmSgGNun3tuZFXerUclWkqEhozdLaJtfcJRUTGkKaEKk+/MpHd1KDS1+o2zb/3PkBUiv2qQ== dependencies: "@types/history" "*" "@types/react" "*" @@ -12516,16 +12516,16 @@ fast-stream-to-buffer@^1.0.0: dependencies: end-of-stream "^1.4.1" -fastest-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028" - integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg= - fast-xml-parser@^3.12.20: version "3.13.0" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.13.0.tgz#3d46f517c0ac6c87393c6b6946808e9c799bb368" integrity sha512-XYXC39VjE9dH6jpp5Gm5gsuR8Z4bMz44qMpT1PmqR+iyOJMhC0LcsN81asYkHP4OkEX8TTR8d6V/caCmy8Q8jQ== +fastest-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028" + integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg= + fastify-cors@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/fastify-cors/-/fastify-cors-2.1.3.tgz#5ae8fcc620a47270cd68d46acc3b8dd7919b538b" @@ -15163,17 +15163,6 @@ history@^3.0.0: query-string "^4.2.2" warning "^3.0.0" -history@^4.7.2: - version "4.7.2" - resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" - integrity sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA== - dependencies: - invariant "^2.2.1" - loose-envify "^1.2.0" - resolve-pathname "^2.2.0" - value-equal "^0.4.0" - warning "^3.0.0" - hjson@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/hjson/-/hjson-3.2.0.tgz#76203ea69bc1c7c88422b48402cc34df8ff8de0e" @@ -15220,6 +15209,13 @@ hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: dependencies: react-is "^16.7.0" +hoist-non-react-statics@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#101685d3aff3b23ea213163f6e8e12f4f111e19f" + integrity sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw== + dependencies: + react-is "^16.7.0" + homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" @@ -19780,6 +19776,15 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256" integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY= +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + mini-css-extract-plugin@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz#81d41ec4fe58c713a96ad7c723cdb2d0bd4d70e1" @@ -23942,23 +23947,40 @@ react-reverse-portal@^1.0.4: resolved "https://registry.yarnpkg.com/react-reverse-portal/-/react-reverse-portal-1.0.4.tgz#d127d2c9147549b25c4959aba1802eca4b144cd4" integrity sha512-WESex/wSjxHwdG7M0uwPNkdQXaLauXNHi4INQiRybmFIXVzAqgf/Ak2OzJ4MLf4UuCD/IzEwJOkML2SxnnontA== -react-router-dom@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" - integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== +react-router-dom@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== dependencies: - history "^4.7.2" - invariant "^2.2.4" + "@babel/runtime" "^7.1.2" + history "^4.9.0" loose-envify "^1.3.1" - prop-types "^15.6.1" - react-router "^4.3.1" - warning "^4.0.1" + prop-types "^15.6.2" + react-router "5.1.2" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" react-router-redux@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/react-router-redux/-/react-router-redux-4.0.8.tgz#227403596b5151e182377dab835b5d45f0f8054e" integrity sha1-InQDWWtRUeGCN32rg1tdRfD4BU4= +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + react-router@^3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.2.1.tgz#b9a3279962bdfbe684c8bd0482b81ef288f0f244" @@ -23972,19 +23994,6 @@ react-router@^3.2.0: prop-types "^15.5.6" warning "^3.0.0" -react-router@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" - integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== - dependencies: - history "^4.7.2" - hoist-non-react-statics "^2.5.0" - invariant "^2.2.4" - loose-envify "^1.3.1" - path-to-regexp "^1.7.0" - prop-types "^15.6.1" - warning "^4.0.1" - react-select@^3.0.0: version "3.0.8" resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.0.8.tgz#06ff764e29db843bcec439ef13e196865242e0c1" @@ -28063,6 +28072,11 @@ tiny-warning@^1.0.0: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" integrity sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q== +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tinycolor2@1.4.1, tinycolor2@^1.0.0, tinycolor2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" @@ -30381,13 +30395,6 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" -warning@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" - integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== - dependencies: - loose-envify "^1.0.0" - warning@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"