diff --git a/src/core/server/http/router/response_adapter.ts b/src/core/server/http/router/response_adapter.ts index fe6cb32d21776..e5dabc99f4442 100644 --- a/src/core/server/http/router/response_adapter.ts +++ b/src/core/server/http/router/response_adapter.ts @@ -120,7 +120,11 @@ export class HapiResponseAdapter { }); error.output.payload.message = getErrorMessage(payload); - error.output.payload.attributes = getErrorAttributes(payload); + + const attributes = getErrorAttributes(payload); + if (attributes) { + error.output.payload.attributes = attributes; + } const headers = kibanaResponse.options.headers; if (headers) { diff --git a/src/core/server/ui_settings/routes/delete.ts b/src/core/server/ui_settings/routes/delete.ts new file mode 100644 index 0000000000000..ee4c05325fcd4 --- /dev/null +++ b/src/core/server/ui_settings/routes/delete.ts @@ -0,0 +1,61 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + params: schema.object({ + key: schema.string(), + }), +}; + +export function registerDeleteRoute(router: IRouter) { + router.delete( + { path: '/api/kibana/settings/{key}', validate }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + await uiSettingsClient.remove(request.params.key); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + } + ); +} diff --git a/src/legacy/ui/ui_settings/routes/delete.ts b/src/core/server/ui_settings/routes/get.ts similarity index 51% rename from src/legacy/ui/ui_settings/routes/delete.ts rename to src/core/server/ui_settings/routes/get.ts index 7825204e6b99b..d249369a1ace7 100644 --- a/src/legacy/ui/ui_settings/routes/delete.ts +++ b/src/core/server/ui_settings/routes/get.ts @@ -16,22 +16,30 @@ * specific language governing permissions and limitations * under the License. */ -import { Legacy } from 'kibana'; +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; -async function handleRequest(request: Legacy.Request) { - const { key } = request.params; - const uiSettings = request.getUiSettingsService(); +export function registerGetRoute(router: IRouter) { + router.get( + { path: '/api/kibana/settings', validate: false }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } - await uiSettings.remove(key); - return { - settings: await uiSettings.getUserProvided(), - }; + throw error; + } + } + ); } - -export const deleteRoute = { - path: '/api/kibana/settings/{key}', - method: 'DELETE', - handler: async (request: Legacy.Request) => { - return await handleRequest(request); - }, -}; diff --git a/src/legacy/ui/ui_settings/routes/get.ts b/src/core/server/ui_settings/routes/index.ts similarity index 67% rename from src/legacy/ui/ui_settings/routes/get.ts rename to src/core/server/ui_settings/routes/index.ts index 3e165a12522bb..d70d55d725938 100644 --- a/src/legacy/ui/ui_settings/routes/get.ts +++ b/src/core/server/ui_settings/routes/index.ts @@ -16,19 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { Legacy } from 'kibana'; +import { IRouter } from 'src/core/server'; -async function handleRequest(request: Legacy.Request) { - const uiSettings = request.getUiSettingsService(); - return { - settings: await uiSettings.getUserProvided(), - }; -} +import { registerDeleteRoute } from './delete'; +import { registerGetRoute } from './get'; +import { registerSetManyRoute } from './set_many'; +import { registerSetRoute } from './set'; -export const getRoute = { - path: '/api/kibana/settings', - method: 'GET', - handler(request: Legacy.Request) { - return handleRequest(request); - }, -}; +export function registerRoutes(router: IRouter) { + registerGetRoute(router); + registerDeleteRoute(router); + registerSetRoute(router); + registerSetManyRoute(router); +} diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_exists.ts b/src/core/server/ui_settings/routes/integration_tests/doc_exists.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_exists.ts rename to src/core/server/ui_settings/routes/integration_tests/doc_exists.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_missing.ts b/src/core/server/ui_settings/routes/integration_tests/doc_missing.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_missing.ts rename to src/core/server/ui_settings/routes/integration_tests/doc_missing.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_missing_and_index_read_only.ts b/src/core/server/ui_settings/routes/integration_tests/doc_missing_and_index_read_only.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_missing_and_index_read_only.ts rename to src/core/server/ui_settings/routes/integration_tests/doc_missing_and_index_read_only.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/index.test.ts b/src/core/server/ui_settings/routes/integration_tests/index.test.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/index.test.ts rename to src/core/server/ui_settings/routes/integration_tests/index.test.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/assert.ts b/src/core/server/ui_settings/routes/integration_tests/lib/assert.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/assert.ts rename to src/core/server/ui_settings/routes/integration_tests/lib/assert.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/chance.ts b/src/core/server/ui_settings/routes/integration_tests/lib/chance.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/chance.ts rename to src/core/server/ui_settings/routes/integration_tests/lib/chance.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/index.ts b/src/core/server/ui_settings/routes/integration_tests/lib/index.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/index.ts rename to src/core/server/ui_settings/routes/integration_tests/lib/index.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts b/src/core/server/ui_settings/routes/integration_tests/lib/servers.ts similarity index 97% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts rename to src/core/server/ui_settings/routes/integration_tests/lib/servers.ts index ae0ef1c91411e..cdcedc12b86e5 100644 --- a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/routes/integration_tests/lib/servers.ts @@ -20,7 +20,7 @@ import { UnwrapPromise } from '@kbn/utility-types'; import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/server'; -import KbnServer from '../../../../../server/kbn_server'; +import KbnServer from '../../../../../../legacy/server/kbn_server'; import { createTestServers } from '../../../../../../test_utils/kbn_server'; import { CallCluster } from '../../../../../../legacy/core_plugins/elasticsearch'; diff --git a/src/core/server/ui_settings/routes/set.ts b/src/core/server/ui_settings/routes/set.ts new file mode 100644 index 0000000000000..51ad256b51335 --- /dev/null +++ b/src/core/server/ui_settings/routes/set.ts @@ -0,0 +1,67 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + params: schema.object({ + key: schema.string(), + }), + body: schema.object({ + value: schema.any(), + }), +}; + +export function registerSetRoute(router: IRouter) { + router.post( + { path: '/api/kibana/settings/{key}', validate }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + const { key } = request.params; + const { value } = request.body; + + await uiSettingsClient.set(key, value); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + } + ); +} diff --git a/src/core/server/ui_settings/routes/set_many.ts b/src/core/server/ui_settings/routes/set_many.ts new file mode 100644 index 0000000000000..3794eba004bee --- /dev/null +++ b/src/core/server/ui_settings/routes/set_many.ts @@ -0,0 +1,60 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + body: schema.object({ + changes: schema.object({}, { allowUnknowns: true }), + }), +}; + +export function registerSetManyRoute(router: IRouter) { + router.post({ path: '/api/kibana/settings', validate }, async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + const { changes } = request.body; + + await uiSettingsClient.setMany(changes); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + }); +} diff --git a/src/core/server/ui_settings/ui_settings_client.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts index 59c13fbebee70..addec464e1b09 100644 --- a/src/core/server/ui_settings/ui_settings_client.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -24,6 +24,7 @@ import sinon from 'sinon'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { UiSettingsClient } from './ui_settings_client'; +import { CannotOverrideError } from './ui_settings_errors'; import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config'; import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub'; @@ -551,15 +552,14 @@ describe('ui settings', () => { const { uiSettings } = setup(); expect(uiSettings.assertUpdateAllowed('foo')).to.be(undefined); }); - it('throws 400 Boom error when keys is overridden', () => { + it('throws CannotOverrideError when key is overridden', () => { const { uiSettings } = setup({ overrides: { foo: true } }); expect(() => uiSettings.assertUpdateAllowed('foo')).to.throwError(error => { + expect(error).to.be.a(CannotOverrideError); expect(error).to.have.property( 'message', 'Unable to update "foo" because it is overridden' ); - expect(error).to.have.property('isBoom', true); - expect(error.output).to.have.property('statusCode', 400); }); }); }); diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index 2be7a0801a533..013586695f3b1 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -17,12 +17,12 @@ * under the License. */ import { defaultsDeep } from 'lodash'; -import Boom from 'boom'; import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; import { Logger } from '../logging'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; import { IUiSettingsClient, UiSettingsParams } from './types'; +import { CannotOverrideError } from './ui_settings_errors'; export interface UiSettingsServiceOptions { type: string; @@ -149,7 +149,7 @@ export class UiSettingsClient implements IUiSettingsClient { // NOTE: should be private method assertUpdateAllowed(key: string) { if (this.isOverridden(key)) { - throw Boom.badRequest(`Unable to update "${key}" because it is overridden`); + throw new CannotOverrideError(`Unable to update "${key}" because it is overridden`); } } diff --git a/src/legacy/ui/ui_settings/routes/index.ts b/src/core/server/ui_settings/ui_settings_errors.ts similarity index 66% rename from src/legacy/ui/ui_settings/routes/index.ts rename to src/core/server/ui_settings/ui_settings_errors.ts index f3c9d4f0d8d14..d8fc32b111e44 100644 --- a/src/legacy/ui/ui_settings/routes/index.ts +++ b/src/core/server/ui_settings/ui_settings_errors.ts @@ -17,7 +17,15 @@ * under the License. */ -export { deleteRoute } from './delete'; -export { getRoute } from './get'; -export { setManyRoute } from './set_many'; -export { setRoute } from './set'; +export class CannotOverrideError extends Error { + public cause?: Error; + + constructor(message: string, cause?: Error) { + super(message); + this.cause = cause; + + // Set the prototype explicitly, see: + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, CannotOverrideError.prototype); + } +} diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index c45f04837d26e..4c19386fcba7a 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -30,6 +30,8 @@ import { UiSettingsClient } from './ui_settings_client'; import { InternalUiSettingsServiceSetup, UiSettingsParams } from './types'; import { mapToObject } from '../../utils/'; +import { registerRoutes } from './routes'; + interface SetupDeps { http: InternalHttpServiceSetup; } @@ -46,6 +48,7 @@ export class UiSettingsService implements CoreService { + registerRoutes(deps.http.createRouter('')); this.log.debug('Setting up ui settings service'); const overrides = await this.getOverrides(deps); const { version, buildNum } = this.coreContext.env.packageInfo; diff --git a/src/legacy/ui/ui_settings/routes/set.ts b/src/legacy/ui/ui_settings/routes/set.ts deleted file mode 100644 index 1f1ab17a0daf7..0000000000000 --- a/src/legacy/ui/ui_settings/routes/set.ts +++ /dev/null @@ -1,55 +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 { Legacy } from 'kibana'; -import Joi from 'joi'; - -async function handleRequest(request: Legacy.Request) { - const { key } = request.params; - const { value } = request.payload as any; - const uiSettings = request.getUiSettingsService(); - - await uiSettings.set(key, value); - - return { - settings: await uiSettings.getUserProvided(), - }; -} - -export const setRoute = { - path: '/api/kibana/settings/{key}', - method: 'POST', - config: { - validate: { - params: Joi.object() - .keys({ - key: Joi.string().required(), - }) - .default(), - - payload: Joi.object() - .keys({ - value: Joi.any().required(), - }) - .required(), - }, - handler(request: Legacy.Request) { - return handleRequest(request); - }, - }, -}; diff --git a/src/legacy/ui/ui_settings/routes/set_many.ts b/src/legacy/ui/ui_settings/routes/set_many.ts deleted file mode 100644 index 18b1046417fec..0000000000000 --- a/src/legacy/ui/ui_settings/routes/set_many.ts +++ /dev/null @@ -1,50 +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 { Legacy } from 'kibana'; -import Joi from 'joi'; - -async function handleRequest(request: Legacy.Request) { - const { changes } = request.payload as any; - const uiSettings = request.getUiSettingsService(); - - await uiSettings.setMany(changes); - - return { - settings: await uiSettings.getUserProvided(), - }; -} - -export const setManyRoute = { - path: '/api/kibana/settings', - method: 'POST', - config: { - validate: { - payload: Joi.object() - .keys({ - changes: Joi.object() - .unknown(true) - .required(), - }) - .required(), - }, - handler(request: Legacy.Request) { - return handleRequest(request); - }, - }, -}; diff --git a/src/legacy/ui/ui_settings/ui_settings_mixin.js b/src/legacy/ui/ui_settings/ui_settings_mixin.js index 8c7ef25c6f8d7..ae9cde541b76f 100644 --- a/src/legacy/ui/ui_settings/ui_settings_mixin.js +++ b/src/legacy/ui/ui_settings/ui_settings_mixin.js @@ -19,12 +19,6 @@ import { uiSettingsServiceFactory } from './ui_settings_service_factory'; import { getUiSettingsServiceForRequest } from './ui_settings_service_for_request'; -import { - deleteRoute, - getRoute, - setManyRoute, - setRoute, -} from './routes'; export function uiSettingsMixin(kbnServer, server) { const { uiSettingDefaults = {} } = kbnServer.uiExports; @@ -58,9 +52,4 @@ export function uiSettingsMixin(kbnServer, server) { server.uiSettings has been removed, see https://github.com/elastic/kibana/pull/12243. `); }); - - server.route(deleteRoute); - server.route(getRoute); - server.route(setManyRoute); - server.route(setRoute); }