diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts index c6a506d2b4dd4..22980bd8e88e7 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_config.ts @@ -42,6 +42,13 @@ export const savedObjectsMigrationConfig: ServiceConfigDescriptor; @@ -50,11 +57,11 @@ export const savedObjectsConfig: ServiceConfigDescriptor path: 'savedObjects', schema: soSchema, }; - export class SavedObjectConfig { public maxImportPayloadBytes: number; public maxImportExportSize: number; - + /* @internal depend on env: see https://github.com/elastic/dev/issues/2200 */ + public allowHttpApiAccess: boolean; public migration: SavedObjectsMigrationConfigType; constructor( @@ -64,5 +71,6 @@ export class SavedObjectConfig { this.maxImportPayloadBytes = rawConfig.maxImportPayloadBytes.getValueInBytes(); this.maxImportExportSize = rawConfig.maxImportExportSize; this.migration = rawMigrationConfig; + this.allowHttpApiAccess = rawConfig.allowHttpApiAccess; } } diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts index 9c85b1aa0620c..c212dae936aed 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_create.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfAnyTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerBulkCreateRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.post( { path: '/_bulk_create', @@ -62,7 +65,9 @@ export const registerBulkCreateRoute = ( const { savedObjects } = await context.core; const typesToCheck = [...new Set(req.body.map(({ type }) => type))]; - throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); + if (!allowHttpApiAccess) { + throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); + } const result = await savedObjects.client.bulkCreate(req.body, { overwrite }); return res.ok({ body: result }); }) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts index cbd22f827a642..d0e80f75dc906 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_delete.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfAnyTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerBulkDeleteRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.post( { path: '/_bulk_delete', @@ -47,8 +50,9 @@ export const registerBulkDeleteRoute = ( const { savedObjects } = await context.core; const typesToCheck = [...new Set(req.body.map(({ type }) => type))]; - throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); - + if (!allowHttpApiAccess) { + throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); + } const statuses = await savedObjects.client.bulkDelete(req.body, { force }); return res.ok({ body: statuses }); }) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_get.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_get.ts index 455657d9ca640..c2e2bb6c6ec1f 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_get.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_get.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfAnyTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerBulkGetRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.post( { path: '/_bulk_get', @@ -42,8 +45,9 @@ export const registerBulkGetRoute = ( const { savedObjects } = await context.core; const typesToCheck = [...new Set(req.body.map(({ type }) => type))]; - throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); - + if (!allowHttpApiAccess) { + throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); + } const result = await savedObjects.client.bulkGet(req.body); return res.ok({ body: result }); }) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_resolve.ts index 874df9f59bb8c..231eb108b50e1 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_resolve.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfAnyTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerBulkResolveRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.post( { path: '/_bulk_resolve', @@ -42,7 +45,9 @@ export const registerBulkResolveRoute = ( const { savedObjects } = await context.core; const typesToCheck = [...new Set(req.body.map(({ type }) => type))]; - throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); + if (!allowHttpApiAccess) { + throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); + } const result = await savedObjects.client.bulkResolve(req.body); return res.ok({ body: result }); }) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_update.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_update.ts index b51e39fd1c6d5..699a997074bc7 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_update.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/bulk_update.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfAnyTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerBulkUpdateRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.put( { path: '/_bulk_update', @@ -55,8 +58,9 @@ export const registerBulkUpdateRoute = ( const { savedObjects } = await context.core; const typesToCheck = [...new Set(req.body.map(({ type }) => type))]; - throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); - + if (!allowHttpApiAccess) { + throwIfAnyTypeNotVisibleByAPI(typesToCheck, savedObjects.typeRegistry); + } const savedObject = await savedObjects.client.bulkUpdate(req.body); return res.ok({ body: savedObject }); }) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/create.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/create.ts index af0be90481c33..82533c8979636 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/create.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/create.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerCreateRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.post( { path: '/{type}/{id?}', @@ -60,9 +63,9 @@ export const registerCreateRoute = ( usageStatsClient.incrementSavedObjectsCreate({ request: req }).catch(() => {}); const { savedObjects } = await context.core; - - throwIfTypeNotVisibleByAPI(type, savedObjects.typeRegistry); - + if (!allowHttpApiAccess) { + throwIfTypeNotVisibleByAPI(type, savedObjects.typeRegistry); + } const options = { id, overwrite, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/delete.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/delete.ts index 71124fee2ca38..4f341e027e155 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/delete.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/delete.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerDeleteRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.delete( { path: '/{type}/{id}', @@ -42,8 +45,9 @@ export const registerDeleteRoute = ( const usageStatsClient = coreUsageData.getClient(); usageStatsClient.incrementSavedObjectsDelete({ request: req }).catch(() => {}); - throwIfTypeNotVisibleByAPI(type, typeRegistry); - + if (!allowHttpApiAccess) { + throwIfTypeNotVisibleByAPI(type, typeRegistry); + } const client = getClient(); const result = await client.delete(type, id, { force }); return res.ok({ body: result }); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts index 42cf0290b52d2..5768c04e40929 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts @@ -7,19 +7,21 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwOnHttpHiddenTypes } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerFindRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { const referenceSchema = schema.object({ type: schema.string(), @@ -28,7 +30,7 @@ export const registerFindRoute = ( const searchOperatorSchema = schema.oneOf([schema.literal('OR'), schema.literal('AND')], { defaultValue: 'OR', }); - + const { allowHttpApiAccess } = config; router.get( { path: '/_find', @@ -95,7 +97,7 @@ export const registerFindRoute = ( return fullType.name; } }); - if (unsupportedTypes.length > 0) { + if (unsupportedTypes.length > 0 && !allowHttpApiAccess) { throwOnHttpHiddenTypes(unsupportedTypes); } diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/get.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/get.ts index ecacdc4452c67..62f7c88a12b98 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/get.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/get.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerGetRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.get( { path: '/{type}/{id}', @@ -39,7 +42,10 @@ export const registerGetRoute = ( usageStatsClient.incrementSavedObjectsGet({ request: req }).catch(() => {}); const { savedObjects } = await context.core; - throwIfTypeNotVisibleByAPI(type, savedObjects.typeRegistry); + + if (!allowHttpApiAccess) { + throwIfTypeNotVisibleByAPI(type, savedObjects.typeRegistry); + } const object = await savedObjects.client.get(type, id); return res.ok({ body: object }); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/index.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/index.ts index 8c85017064498..3fc2ef2afd51c 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/index.ts @@ -53,17 +53,17 @@ export function registerRoutes({ const router = http.createRouter('/api/saved_objects/'); - registerGetRoute(router, { coreUsageData, logger }); - registerResolveRoute(router, { coreUsageData, logger }); - registerCreateRoute(router, { coreUsageData, logger }); - registerDeleteRoute(router, { coreUsageData, logger }); - registerFindRoute(router, { coreUsageData, logger }); - registerUpdateRoute(router, { coreUsageData, logger }); - registerBulkGetRoute(router, { coreUsageData, logger }); - registerBulkCreateRoute(router, { coreUsageData, logger }); - registerBulkResolveRoute(router, { coreUsageData, logger }); - registerBulkUpdateRoute(router, { coreUsageData, logger }); - registerBulkDeleteRoute(router, { coreUsageData, logger }); + registerGetRoute(router, { config, coreUsageData, logger }); + registerResolveRoute(router, { config, coreUsageData, logger }); + registerCreateRoute(router, { config, coreUsageData, logger }); + registerDeleteRoute(router, { config, coreUsageData, logger }); + registerFindRoute(router, { config, coreUsageData, logger }); + registerUpdateRoute(router, { config, coreUsageData, logger }); + registerBulkGetRoute(router, { config, coreUsageData, logger }); + registerBulkCreateRoute(router, { config, coreUsageData, logger }); + registerBulkResolveRoute(router, { config, coreUsageData, logger }); + registerBulkUpdateRoute(router, { config, coreUsageData, logger }); + registerBulkDeleteRoute(router, { config, coreUsageData, logger }); registerExportRoute(router, { config, coreUsageData }); registerImportRoute(router, { config, coreUsageData }); registerResolveImportErrorsRoute(router, { config, coreUsageData }); diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve.ts index be77423cba09b..527c7f13e5500 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve.ts @@ -7,20 +7,23 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalSavedObjectRouter } from '../internal_types'; import { throwIfTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerResolveRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.get( { path: '/resolve/{type}/{id}', @@ -40,9 +43,9 @@ export const registerResolveRoute = ( const usageStatsClient = coreUsageData.getClient(); usageStatsClient.incrementSavedObjectsResolve({ request: req }).catch(() => {}); - - throwIfTypeNotVisibleByAPI(type, savedObjects.typeRegistry); - + if (!allowHttpApiAccess) { + throwIfTypeNotVisibleByAPI(type, savedObjects.typeRegistry); + } const result = await savedObjects.client.resolve(type, id); return res.ok({ body: result }); }) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/update.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/update.ts index d1b544519efa3..327f856d580fe 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/update.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/update.ts @@ -9,19 +9,22 @@ import { schema } from '@kbn/config-schema'; import type { SavedObjectsUpdateOptions } from '@kbn/core-saved-objects-api-server'; import type { Logger } from '@kbn/logging'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, throwIfTypeNotVisibleByAPI } from './utils'; interface RouteDependencies { + config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; logger: Logger; } export const registerUpdateRoute = ( router: InternalSavedObjectRouter, - { coreUsageData, logger }: RouteDependencies + { config, coreUsageData, logger }: RouteDependencies ) => { + const { allowHttpApiAccess } = config; router.put( { path: '/{type}/{id}', @@ -55,9 +58,9 @@ export const registerUpdateRoute = ( const usageStatsClient = coreUsageData.getClient(); usageStatsClient.incrementSavedObjectsUpdate({ request: req }).catch(() => {}); const { savedObjects } = await context.core; - - throwIfTypeNotVisibleByAPI(type, savedObjects.typeRegistry); - + if (!allowHttpApiAccess) { + throwIfTypeNotVisibleByAPI(type, savedObjects.typeRegistry); + } const result = await savedObjects.client.update(type, id, attributes, options); return res.ok({ body: result }); }) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts index d6a451a105f7c..dbb04577d3f7e 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.ts @@ -123,7 +123,6 @@ export class SavedObjectsService this.coreContext.configService.atPath('migrations') ); this.config = new SavedObjectConfig(savedObjectsConfig, savedObjectsMigrationConfig); - deprecations.getRegistry('savedObjects').registerDeprecations( getSavedObjectsDeprecationsProvider({ kibanaIndex, diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_create.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_create.test.ts new file mode 100644 index 0000000000000..cefe42b8f3e7f --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_create.test.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; + +import { + registerBulkCreateRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('POST /api/saved_objects/_bulk_create with allowApiAccess true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + savedObjectsClient = handlerContext.savedObjects.client; + savedObjectsClient.bulkCreate.mockResolvedValue({ saved_objects: [] }); + + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkCreate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); + const config = setupConfig(true); + registerBulkCreateRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 200 when a type is hidden from the HTTP APIs', async () => { + await supertest(httpSetup.server.listener) + .post('/api/saved_objects/_bulk_create?overwrite=true') + .send([ + { + id: 'abc1234', + type: 'index-pattern', + attributes: { + title: 'foo', + }, + references: [], + }, + ]) + .expect(200); + + expect(savedObjectsClient.bulkCreate).toHaveBeenCalledTimes(1); + + const args = savedObjectsClient.bulkCreate.mock.calls[0]; + expect(args[1]).toEqual({ overwrite: true }); + const result = await supertest(httpSetup.server.listener) + .post('/api/saved_objects/_bulk_create') + .send([ + { + id: 'hiddenID', + type: 'hidden-from-http', + attributes: { + title: 'bar', + }, + references: [], + }, + ]) + .expect(200); + expect(result.body.saved_objects).toEqual([]); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_delete.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_delete.test.ts new file mode 100644 index 0000000000000..5189e70e26521 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_delete.test.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { savedObjectsClientMock } from '../../../../mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; +import { + registerBulkDeleteRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('POST /api/saved_objects/_bulk_delete with allowApiAccess as true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + savedObjectsClient = handlerContext.savedObjects.client; + + savedObjectsClient.bulkDelete.mockResolvedValue({ + statuses: [], + }); + + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkDelete.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + + const logger = loggerMock.create(); + + const config = setupConfig(true); + + registerBulkDeleteRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 200 when a type is hidden from the HTTP APIs', async () => { + const result = await supertest(httpSetup.server.listener) + .post('/api/saved_objects/_bulk_delete') + .send([ + { + id: 'hiddenID', + type: 'hidden-from-http', + }, + ]) + .expect(200); + expect(result.body.statuses).toEqual([]); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_get.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_get.test.ts new file mode 100644 index 0000000000000..2ec6342d601ec --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_get.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; +import { + registerBulkGetRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('POST /api/saved_objects/_bulk_get with allowApiAccess true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + savedObjectsClient = handlerContext.savedObjects.client; + + savedObjectsClient.bulkGet.mockResolvedValue({ + saved_objects: [], + }); + + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkGet.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); + + const config = setupConfig(true); + registerBulkGetRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 200 when a type is hidden from the HTTP APIs', async () => { + const result = await supertest(httpSetup.server.listener) + .post('/api/saved_objects/_bulk_get') + .send([ + { + id: 'hiddenID', + type: 'hidden-from-http', + }, + ]) + .expect(200); + expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1); + expect(result.body.saved_objects).toEqual([]); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_resolve.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_resolve.test.ts new file mode 100644 index 0000000000000..33dd5ca478a18 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_resolve.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; +import { + registerBulkResolveRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('POST /api/saved_objects/_bulk_resolve with allowApiAccess true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + savedObjectsClient = handlerContext.savedObjects.client; + + savedObjectsClient.bulkResolve.mockResolvedValue({ + resolved_objects: [], + }); + + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkResolve.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); + + const config = setupConfig(true); + registerBulkResolveRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 200 when a type is hidden from the HTTP APIs', async () => { + await supertest(httpSetup.server.listener) + .post('/api/saved_objects/_bulk_resolve') + .send([ + { + id: 'hiddenID', + type: 'hidden-from-http', + }, + ]) + .expect(200); + expect(savedObjectsClient.bulkResolve).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_update.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_update.test.ts new file mode 100644 index 0000000000000..3feea1a471fdd --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/bulk_update.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; +import { + registerBulkUpdateRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; +const testTypes = [ + { name: 'visualization', hide: false }, + { name: 'dashboard', hide: false }, + { name: 'index-pattern', hide: false }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('PUT /api/saved_objects/_bulk_update with allowApiAccess true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkUpdate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); + + const config = setupConfig(true); + registerBulkUpdateRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('uses config option allowHttpApiAccess to grant hiddenFromHttpApis type access', async () => { + const result = await supertest(httpSetup.server.listener) + .put('/api/saved_objects/_bulk_update') + .send([ + { + type: 'hidden-from-http', + id: 'hiddenID', + attributes: { + title: 'bar', + }, + }, + ]) + .expect(200); + expect(result.body).toEqual({}); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/create.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/create.test.ts new file mode 100644 index 0000000000000..02ffcdf2baaeb --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/create.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { setupServer, createHiddenTypeVariants } from '@kbn/core-test-helpers-test-utils'; +import { + registerCreateRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-type', hide: true }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; +describe('POST /api/saved_objects/{type} with allowApiAccess true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + const clientResponse = { + id: 'logstash-*', + type: 'index-pattern', + title: 'logstash-*', + version: 'foo', + references: [], + attributes: {}, + }; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + savedObjectsClient = handlerContext.savedObjects.client; + savedObjectsClient.create.mockImplementation(() => Promise.resolve(clientResponse)); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsCreate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); + const config = setupConfig(true); + + registerCreateRoute(router, { config, coreUsageData, logger }); + + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 200 when a type is hidden from the HTTP APIs', async () => { + const result = await supertest(httpSetup.server.listener) + .post('/api/saved_objects/hidden-from-http') + .send({ + attributes: { + properties: {}, + }, + }) + .expect(200); + + expect(result.body).toEqual(clientResponse); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/delete.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/delete.test.ts new file mode 100644 index 0000000000000..0cffad1bf2a7c --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/delete.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; +import { + registerDeleteRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-type', hide: true }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('DELETE /api/saved_objects/{type}/{id} with allowApiAccess true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + savedObjectsClient = handlerContext.savedObjects.getClient(); + handlerContext.savedObjects.getClient = jest.fn().mockImplementation(() => savedObjectsClient); + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsDelete.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); + const config = setupConfig(true); + registerDeleteRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 400 if a type is hidden from the HTTP APIs', async () => { + const result = await supertest(httpSetup.server.listener) + .delete('/api/saved_objects/hidden-from-http/hiddenId') + .expect(200); + expect(result.body).toEqual({}); + }); + + it('returns with status 400 if a type is hidden from the HTTP APIs with `force` option', async () => { + const result = await supertest(httpSetup.server.listener) + .delete('/api/saved_objects/hidden-from-http/hiddenId') + .query({ force: true }) + .expect(200); + expect(result.body).toEqual({}); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/find.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/find.test.ts new file mode 100644 index 0000000000000..0487f6a519130 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/find.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; + +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; +import { loggerMock } from '@kbn/logging-mocks'; +import { + registerFindRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'visualization', hide: false }, + { name: 'dashboard', hide: false }, + { name: 'foo', hide: false }, + { name: 'bar', hide: false }, + { name: 'hidden-type', hide: true }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; +describe('GET /api/saved_objects/_find with allowApiAccess true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + const clientResponse = { + total: 0, + saved_objects: [], + per_page: 0, + page: 0, + }; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + savedObjectsClient = handlerContext.savedObjects.client; + + savedObjectsClient.find.mockResolvedValue(clientResponse); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsFind.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + + const logger = loggerMock.create(); + + const config = setupConfig(true); + + registerFindRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 400 when type is hidden from the HTTP APIs', async () => { + const findResponse = { + total: 0, + per_page: 0, + page: 0, + saved_objects: [], + }; + const result = await supertest(httpSetup.server.listener) + .get('/api/saved_objects/_find?type=hidden-from-http') + .expect(200); + + expect(result.body).toEqual(findResponse); + }); + + it('returns with status 200 when type is hidden', async () => { + const findResponse = { + total: 0, + per_page: 0, + page: 0, + saved_objects: [], + }; + const result = await supertest(httpSetup.server.listener) + .get('/api/saved_objects/_find?type=hidden-type') + .expect(200); + + expect(result.body).toEqual(findResponse); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/get.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/get.test.ts new file mode 100644 index 0000000000000..bc23f404e928d --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/get.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { ContextService } from '@kbn/core-http-context-server-internal'; +import type { HttpService, InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import { createHttpServer, createCoreContext } from '@kbn/core-http-server-mocks'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { contextServiceMock, coreMock } from '../../../../mocks'; +import { + registerGetRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { createHiddenTypeVariants } from '@kbn/core-test-helpers-test-utils'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +const coreId = Symbol('core'); +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-type', hide: true }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('GET /api/saved_objects/{type}/{id} with allowApiAccess true', () => { + let server: HttpService; + let httpSetup: InternalHttpServiceSetup; + let handlerContext: ReturnType; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + const coreContext = createCoreContext({ coreId }); + server = createHttpServer(coreContext); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); + + const contextService = new ContextService(coreContext); + httpSetup = await server.setup({ + context: contextService.setup({ pluginDependencies: new Map() }), + executionContext: executionContextServiceMock.createInternalSetupContract(), + }); + + handlerContext = coreMock.createRequestHandlerContext(); + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + savedObjectsClient = handlerContext.savedObjects.client; + + httpSetup.registerRouteHandlerContext( + coreId, + 'core', + (ctx, req, res) => { + return handlerContext; + } + ); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsGet.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + + const logger = loggerMock.create(); + + const config = setupConfig(true); + registerGetRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 200 when a type is hidden from the http APIs', async () => { + const result = await supertest(httpSetup.server.listener) + .get('/api/saved_objects/hidden-from-http/hiddenId') + .expect(200); + expect(savedObjectsClient.get).toHaveBeenCalled(); + expect(result.body).toEqual({}); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/resolve.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/resolve.test.ts new file mode 100644 index 0000000000000..88814cb658608 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/resolve.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { ContextService } from '@kbn/core-http-context-server-internal'; +import type { HttpService, InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import { createHttpServer, createCoreContext } from '@kbn/core-http-server-mocks'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { executionContextServiceMock } from '@kbn/core-execution-context-server-mocks'; +import { contextServiceMock, coreMock } from '../../../../mocks'; +import { + registerResolveRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { createHiddenTypeVariants } from '@kbn/core-test-helpers-test-utils'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +const coreId = Symbol('core'); + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-type', hide: true }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('GET /api/saved_objects/resolve/{type}/{id} with allowApiAccess true', () => { + let server: HttpService; + let httpSetup: InternalHttpServiceSetup; + let handlerContext: ReturnType; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + const coreContext = createCoreContext({ coreId }); + server = createHttpServer(coreContext); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); + + const contextService = new ContextService(coreContext); + httpSetup = await server.setup({ + context: contextService.setup({ pluginDependencies: new Map() }), + executionContext: executionContextServiceMock.createInternalSetupContract(), + }); + + handlerContext = coreMock.createRequestHandlerContext(); + + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + savedObjectsClient = handlerContext.savedObjects.client; + + httpSetup.registerRouteHandlerContext( + coreId, + 'core', + (ctx, req, res) => { + return handlerContext; + } + ); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsResolve.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); + + const config = setupConfig(true); + + registerResolveRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 200 is a type is hidden from the HTTP APIs', async () => { + await supertest(httpSetup.server.listener) + .get('/api/saved_objects/resolve/hidden-from-http/hiddenId') + .expect(200); + expect(savedObjectsClient.resolve).toHaveBeenCalled(); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/allow_api_access/update.test.ts b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/update.test.ts new file mode 100644 index 0000000000000..41365962800d3 --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/allow_api_access/update.test.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; +import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ICoreUsageStatsClient } from '@kbn/core-usage-data-base-server-internal'; +import { + coreUsageStatsClientMock, + coreUsageDataServiceMock, +} from '@kbn/core-usage-data-server-mocks'; +import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; +import { + registerUpdateRoute, + type InternalSavedObjectsRequestHandlerContext, +} from '@kbn/core-saved-objects-server-internal'; +import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from '../routes_test_utils'; + +type SetupServerReturn = Awaited>; + +const testTypes = [ + { name: 'index-pattern', hide: false }, + { name: 'hidden-type', hide: true }, + { name: 'hidden-from-http', hide: false, hideFromHttpApis: true }, +]; + +describe('PUT /api/saved_objects/{type}/{id?} with allowApiAccess true', () => { + let server: SetupServerReturn['server']; + let httpSetup: SetupServerReturn['httpSetup']; + let handlerContext: SetupServerReturn['handlerContext']; + let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; + + beforeEach(async () => { + ({ server, httpSetup, handlerContext } = await setupServer()); + savedObjectsClient = handlerContext.savedObjects.client; + + handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { + return testTypes + .map((typeDesc) => createHiddenTypeVariants(typeDesc)) + .find((fullTest) => fullTest.name === typename); + }); + + const router = + httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsUpdate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); + + const config = setupConfig(true); + registerUpdateRoute(router, { config, coreUsageData, logger }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('returns with status 200 for types hidden from the HTTP APIs', async () => { + await supertest(httpSetup.server.listener) + .put('/api/saved_objects/hidden-from-http/hiddenId') + .send({ + attributes: { title: 'does not matter' }, + }) + .expect(200); + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/core/server/integration_tests/saved_objects/routes/bulk_create.test.ts b/src/core/server/integration_tests/saved_objects/routes/bulk_create.test.ts index 096d7f330abca..be0c91733ffbc 100644 --- a/src/core/server/integration_tests/saved_objects/routes/bulk_create.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/bulk_create.test.ts @@ -20,6 +20,7 @@ import { } from '@kbn/core-saved-objects-server-internal'; import { createHiddenTypeVariants, setupServer } from '@kbn/core-test-helpers-test-utils'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; @@ -52,9 +53,13 @@ describe('POST /api/saved_objects/_bulk_create', () => { coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsBulkCreate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerBulkCreateRoute(router, { coreUsageData, logger }); + + const config = setupConfig(); + + registerBulkCreateRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/bulk_delete.test.ts b/src/core/server/integration_tests/saved_objects/routes/bulk_delete.test.ts index 47559aecf9769..893c406e2f272 100644 --- a/src/core/server/integration_tests/saved_objects/routes/bulk_delete.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/bulk_delete.test.ts @@ -19,6 +19,7 @@ import { type InternalSavedObjectsRequestHandlerContext, } from '@kbn/core-saved-objects-server-internal'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; @@ -54,9 +55,13 @@ describe('POST /api/saved_objects/_bulk_delete', () => { coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsBulkDelete.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerBulkDeleteRoute(router, { coreUsageData, logger }); + + const config = setupConfig(); + + registerBulkDeleteRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/bulk_get.test.ts b/src/core/server/integration_tests/saved_objects/routes/bulk_get.test.ts index 7c894c250dceb..01a988bac4d9d 100644 --- a/src/core/server/integration_tests/saved_objects/routes/bulk_get.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/bulk_get.test.ts @@ -19,6 +19,7 @@ import { type InternalSavedObjectsRequestHandlerContext, } from '@kbn/core-saved-objects-server-internal'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; @@ -55,7 +56,9 @@ describe('POST /api/saved_objects/_bulk_get', () => { const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerBulkGetRoute(router, { coreUsageData, logger }); + + const config = setupConfig(); + registerBulkGetRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/bulk_resolve.test.ts b/src/core/server/integration_tests/saved_objects/routes/bulk_resolve.test.ts index 98253fabb2fa4..1a8e81d07e77a 100644 --- a/src/core/server/integration_tests/saved_objects/routes/bulk_resolve.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/bulk_resolve.test.ts @@ -19,6 +19,7 @@ import { type InternalSavedObjectsRequestHandlerContext, } from '@kbn/core-saved-objects-server-internal'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; @@ -56,7 +57,9 @@ describe('POST /api/saved_objects/_bulk_resolve', () => { const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerBulkResolveRoute(router, { coreUsageData, logger }); + + const config = setupConfig(); + registerBulkResolveRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/bulk_update.test.ts b/src/core/server/integration_tests/saved_objects/routes/bulk_update.test.ts index eb50fd141e2af..797885e2b2aca 100644 --- a/src/core/server/integration_tests/saved_objects/routes/bulk_update.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/bulk_update.test.ts @@ -19,9 +19,9 @@ import { type InternalSavedObjectsRequestHandlerContext, } from '@kbn/core-saved-objects-server-internal'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; - const testTypes = [ { name: 'visualization', hide: false }, { name: 'dashboard', hide: false }, @@ -55,7 +55,9 @@ describe('PUT /api/saved_objects/_bulk_update', () => { const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerBulkUpdateRoute(router, { coreUsageData, logger }); + + const config = setupConfig(); + registerBulkUpdateRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/create.test.ts b/src/core/server/integration_tests/saved_objects/routes/create.test.ts index bf67325a8e756..b4496dec915c0 100644 --- a/src/core/server/integration_tests/saved_objects/routes/create.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/create.test.ts @@ -19,6 +19,7 @@ import { type InternalSavedObjectsRequestHandlerContext, } from '@kbn/core-saved-objects-server-internal'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; @@ -56,7 +57,8 @@ describe('POST /api/saved_objects/{type}', () => { const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerCreateRoute(router, { coreUsageData, logger }); + const config = setupConfig(); + registerCreateRoute(router, { config, coreUsageData, logger }); handlerContext.savedObjects.typeRegistry.getType.mockImplementation((typename: string) => { return testTypes diff --git a/src/core/server/integration_tests/saved_objects/routes/delete.test.ts b/src/core/server/integration_tests/saved_objects/routes/delete.test.ts index 538cc2d721485..7ed9de92ab69a 100644 --- a/src/core/server/integration_tests/saved_objects/routes/delete.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/delete.test.ts @@ -19,6 +19,7 @@ import { type InternalSavedObjectsRequestHandlerContext, } from '@kbn/core-saved-objects-server-internal'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; @@ -53,7 +54,8 @@ describe('DELETE /api/saved_objects/{type}/{id}', () => { const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerDeleteRoute(router, { coreUsageData, logger }); + const config = setupConfig(); + registerDeleteRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/find.test.ts b/src/core/server/integration_tests/saved_objects/routes/find.test.ts index 25fd8a32fc9ef..7611e33171e4b 100644 --- a/src/core/server/integration_tests/saved_objects/routes/find.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/find.test.ts @@ -21,6 +21,7 @@ import { registerFindRoute, type InternalSavedObjectsRequestHandlerContext, } from '@kbn/core-saved-objects-server-internal'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; @@ -66,9 +67,13 @@ describe('GET /api/saved_objects/_find', () => { coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsFind.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerFindRoute(router, { coreUsageData, logger }); + + const config = setupConfig(); + + registerFindRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/get.test.ts b/src/core/server/integration_tests/saved_objects/routes/get.test.ts index 363f0406b2148..8a19aadac8a22 100644 --- a/src/core/server/integration_tests/saved_objects/routes/get.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/get.test.ts @@ -24,9 +24,9 @@ import { } from '@kbn/core-saved-objects-server-internal'; import { createHiddenTypeVariants } from '@kbn/core-test-helpers-test-utils'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; const coreId = Symbol('core'); - const testTypes = [ { name: 'index-pattern', hide: false }, { name: 'hidden-type', hide: true }, @@ -71,12 +71,16 @@ describe('GET /api/saved_objects/{type}/{id}', () => { const router = httpSetup.createRouter('/api/saved_objects/'); + coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsGet.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerGetRoute(router, { coreUsageData, logger }); + + const config = setupConfig(); + registerGetRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/resolve.test.ts b/src/core/server/integration_tests/saved_objects/routes/resolve.test.ts index 0ecc6221730ff..b85caa035e928 100644 --- a/src/core/server/integration_tests/saved_objects/routes/resolve.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/resolve.test.ts @@ -24,6 +24,7 @@ import { } from '@kbn/core-saved-objects-server-internal'; import { createHiddenTypeVariants } from '@kbn/core-test-helpers-test-utils'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; const coreId = Symbol('core'); @@ -77,7 +78,9 @@ describe('GET /api/saved_objects/resolve/{type}/{id}', () => { const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerResolveRoute(router, { coreUsageData, logger }); + const config = setupConfig(); + + registerResolveRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/core/server/integration_tests/saved_objects/routes/routes_test_utils.ts b/src/core/server/integration_tests/saved_objects/routes/routes_test_utils.ts new file mode 100644 index 0000000000000..5af72bf16484c --- /dev/null +++ b/src/core/server/integration_tests/saved_objects/routes/routes_test_utils.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; + +export function setupConfig(allowAccess: boolean = false) { + const config = { + allowHttpApiAccess: allowAccess, + } as SavedObjectConfig; + return config; +} diff --git a/src/core/server/integration_tests/saved_objects/routes/update.test.ts b/src/core/server/integration_tests/saved_objects/routes/update.test.ts index 8333159b8e1c6..c261584217a37 100644 --- a/src/core/server/integration_tests/saved_objects/routes/update.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/update.test.ts @@ -19,6 +19,7 @@ import { type InternalSavedObjectsRequestHandlerContext, } from '@kbn/core-saved-objects-server-internal'; import { loggerMock } from '@kbn/logging-mocks'; +import { setupConfig } from './routes_test_utils'; type SetupServerReturn = Awaited>; @@ -64,7 +65,9 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); const logger = loggerMock.create(); loggerWarnSpy = jest.spyOn(logger, 'warn').mockImplementation(); - registerUpdateRoute(router, { coreUsageData, logger }); + + const config = setupConfig(); + registerUpdateRoute(router, { config, coreUsageData, logger }); await server.start(); }); diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 9c5f2511f1617..f23da578d2921 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -139,6 +139,7 @@ kibana_vars=( regionmap savedObjects.maxImportExportSize savedObjects.maxImportPayloadBytes + savedObjects.allowHttpApiAccess security.showInsecureClusterWarning server.basePath server.compression.enabled diff --git a/test/functional/config.base.js b/test/functional/config.base.js index 5e4d89d6c195b..c4f1e3695f474 100644 --- a/test/functional/config.base.js +++ b/test/functional/config.base.js @@ -29,6 +29,8 @@ export default async function ({ readConfigFile }) { ...commonConfig.get('kbnTestServer.serverArgs'), '--telemetry.optIn=false', '--savedObjects.maxImportPayloadBytes=10485760', + // override default to not allow hiddenFromHttpApis saved object types access to the HTTP Apis. see https://github.com/elastic/dev/issues/2200 + '--savedObjects.allowHttpApiAccess=false', // to be re-enabled once kibana/issues/102552 is completed '--xpack.reporting.enabled=false', diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index ae6964d51d2b4..04c62d5e1204b 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -54,6 +54,7 @@ export default async function ({ readConfigFile }) { '--xpack.encryptedSavedObjects.encryptionKey="DkdXazszSCYexXqz4YktBGHCRkV6hyNK"', '--xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled=true', '--savedObjects.maxImportPayloadBytes=10485760', // for OSS test management/_import_objects, + '--savedObjects.allowHttpApiAccess=false', // override default to not allow hiddenFromHttpApis saved objects access to the http APIs see https://github.com/elastic/dev/issues/2200 ], }, uiSettings: {