[Saved Objects] Adds config flag to toggle hiddenFromHttpApis SO types conditionally (#151512)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Christiane (Tina) Heiligers 2023-02-22 07:59:50 -07:00 committed by GitHub
parent 20ee30207f
commit a8f10ed6cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1133 additions and 60 deletions

View file

@ -42,6 +42,13 @@ export const savedObjectsMigrationConfig: ServiceConfigDescriptor<SavedObjectsMi
const soSchema = schema.object({
maxImportPayloadBytes: schema.byteSize({ defaultValue: 26_214_400 }),
maxImportExportSize: schema.number({ defaultValue: 10_000 }),
/* @internal Conditionally set default, dependening on if kibana's running from a dist build or not */
allowHttpApiAccess: schema.conditional(
schema.contextRef('dist'),
true,
schema.boolean({ defaultValue: true }),
schema.boolean({ defaultValue: false })
),
});
export type SavedObjectsConfigType = TypeOf<typeof soSchema>;
@ -50,11 +57,11 @@ export const savedObjectsConfig: ServiceConfigDescriptor<SavedObjectsConfigType>
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;
}
}

View file

@ -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 });
})

View file

@ -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 });
})

View file

@ -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 });
})

View file

@ -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 });
})

View file

@ -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 });
})

View file

@ -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,

View file

@ -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 });

View file

@ -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);
}

View file

@ -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 });

View file

@ -53,17 +53,17 @@ export function registerRoutes({
const router =
http.createRouter<InternalSavedObjectsRequestHandlerContext>('/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 });

View file

@ -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 });
})

View file

@ -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 });
})

View file

@ -123,7 +123,6 @@ export class SavedObjectsService
this.coreContext.configService.atPath<SavedObjectsMigrationConfigType>('migrations')
);
this.config = new SavedObjectConfig(savedObjectsConfig, savedObjectsMigrationConfig);
deprecations.getRegistry('savedObjects').registerDeprecations(
getSavedObjectsDeprecationsProvider({
kibanaIndex,

View file

@ -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<ReturnType<typeof setupServer>>;
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<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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([]);
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
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<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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([]);
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
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<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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([]);
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
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<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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);
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
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<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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({});
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
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<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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);
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
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<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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({});
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
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<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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);
});
});

View file

@ -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<typeof coreMock.createRequestHandlerContext>;
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext, 'core'>(
coreId,
'core',
(ctx, req, res) => {
return handlerContext;
}
);
const router =
httpSetup.createRouter<InternalSavedObjectsRequestHandlerContext>('/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({});
});
});

View file

@ -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<typeof coreMock.createRequestHandlerContext>;
let savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext, 'core'>(
coreId,
'core',
(ctx, req, res) => {
return handlerContext;
}
);
const router =
httpSetup.createRouter<InternalSavedObjectsRequestHandlerContext>('/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();
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
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<typeof savedObjectsClientMock.create>;
let coreUsageStatsClient: jest.Mocked<ICoreUsageStatsClient>;
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<InternalSavedObjectsRequestHandlerContext>('/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);
});
});

View file

@ -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<ReturnType<typeof setupServer>>;
@ -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();
});

View file

@ -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<ReturnType<typeof setupServer>>;
@ -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();
});

View file

@ -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<ReturnType<typeof setupServer>>;
@ -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();
});

View file

@ -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<ReturnType<typeof setupServer>>;
@ -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();
});

View file

@ -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<ReturnType<typeof setupServer>>;
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();
});

View file

@ -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<ReturnType<typeof setupServer>>;
@ -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

View file

@ -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<ReturnType<typeof setupServer>>;
@ -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();
});

View file

@ -21,6 +21,7 @@ import {
registerFindRoute,
type InternalSavedObjectsRequestHandlerContext,
} from '@kbn/core-saved-objects-server-internal';
import { setupConfig } from './routes_test_utils';
type SetupServerReturn = Awaited<ReturnType<typeof setupServer>>;
@ -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();
});

View file

@ -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<InternalSavedObjectsRequestHandlerContext>('/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();
});

View file

@ -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();
});

View file

@ -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;
}

View file

@ -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<ReturnType<typeof setupServer>>;
@ -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();
});

View file

@ -139,6 +139,7 @@ kibana_vars=(
regionmap
savedObjects.maxImportExportSize
savedObjects.maxImportPayloadBytes
savedObjects.allowHttpApiAccess
security.showInsecureClusterWarning
server.basePath
server.compression.enabled

View file

@ -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',

View file

@ -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: {